stash
[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] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : 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 <= zone ||
7327                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
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 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7371
7372 void
7373 LeftClick (ClickType clickType, int xPix, int yPix)
7374 {
7375     int x, y;
7376     Boolean saveAnimate;
7377     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7378     char promoChoice = NULLCHAR;
7379     ChessSquare piece;
7380     static TimeMark lastClickTime, prevClickTime;
7381
7382     x = EventToSquare(xPix, BOARD_WIDTH);
7383     y = EventToSquare(yPix, BOARD_HEIGHT);
7384     if (!flipView && y >= 0) {
7385         y = BOARD_HEIGHT - 1 - y;
7386     }
7387     if (flipView && x >= 0) {
7388         x = BOARD_WIDTH - 1 - x;
7389     }
7390
7391     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7392         static int dummy;
7393         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7394         right = TRUE;
7395         return;
7396     }
7397
7398     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7399
7400     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7401
7402     if (clickType == Press) ErrorPopDown();
7403     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7404
7405     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7406         defaultPromoChoice = promoSweep;
7407         promoSweep = EmptySquare;   // terminate sweep
7408         promoDefaultAltered = TRUE;
7409         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7410     }
7411
7412     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7413         if(clickType == Release) return; // ignore upclick of click-click destination
7414         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7415         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7416         if(gameInfo.holdingsWidth &&
7417                 (WhiteOnMove(currentMove)
7418                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7419                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7420             // click in right holdings, for determining promotion piece
7421             ChessSquare p = boards[currentMove][y][x];
7422             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7423             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7424             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7425                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7426                 fromX = fromY = -1;
7427                 return;
7428             }
7429         }
7430         DrawPosition(FALSE, boards[currentMove]);
7431         return;
7432     }
7433
7434     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7435     if(clickType == Press
7436             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7437               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7438               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7439         return;
7440
7441     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7442         // could be static click on premove from-square: abort premove
7443         gotPremove = 0;
7444         ClearPremoveHighlights();
7445     }
7446
7447     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7448         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7449
7450     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7451         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7452                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7453         defaultPromoChoice = DefaultPromoChoice(side);
7454     }
7455
7456     autoQueen = appData.alwaysPromoteToQueen;
7457
7458     if (fromX == -1) {
7459       int originalY = y;
7460       gatingPiece = EmptySquare;
7461       if (clickType != Press) {
7462         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7463             DragPieceEnd(xPix, yPix); dragging = 0;
7464             DrawPosition(FALSE, NULL);
7465         }
7466         return;
7467       }
7468       doubleClick = FALSE;
7469       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7470         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7471       }
7472       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7473       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7474          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7475          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7476             /* First square */
7477             if (OKToStartUserMove(fromX, fromY)) {
7478                 second = 0;
7479                 ReportClick("lift", x, y);
7480                 MarkTargetSquares(0);
7481                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7482                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7483                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7484                     promoSweep = defaultPromoChoice;
7485                     selectFlag = 0; lastX = xPix; lastY = yPix;
7486                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7487                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7488                 }
7489                 if (appData.highlightDragging) {
7490                     SetHighlights(fromX, fromY, -1, -1);
7491                 } else {
7492                     ClearHighlights();
7493                 }
7494             } else fromX = fromY = -1;
7495             return;
7496         }
7497     }
7498 printf("to click %d,%d\n",x,y);
7499     /* fromX != -1 */
7500     if (clickType == Press && gameMode != EditPosition) {
7501         ChessSquare fromP;
7502         ChessSquare toP;
7503         int frc;
7504
7505         // ignore off-board to clicks
7506         if(y < 0 || x < 0) return;
7507
7508         /* Check if clicking again on the same color piece */
7509         fromP = boards[currentMove][fromY][fromX];
7510         toP = boards[currentMove][y][x];
7511         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7512         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7513             legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7514            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7515              WhitePawn <= toP && toP <= WhiteKing &&
7516              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7517              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7518             (BlackPawn <= fromP && fromP <= BlackKing &&
7519              BlackPawn <= toP && toP <= BlackKing &&
7520              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7521              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7522             /* Clicked again on same color piece -- changed his mind */
7523             second = (x == fromX && y == fromY);
7524             killX = killY = -1;
7525             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7526                 second = FALSE; // first double-click rather than scond click
7527                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7528             }
7529             promoDefaultAltered = FALSE;
7530             MarkTargetSquares(1);
7531            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7532             if (appData.highlightDragging) {
7533                 SetHighlights(x, y, -1, -1);
7534             } else {
7535                 ClearHighlights();
7536             }
7537             if (OKToStartUserMove(x, y)) {
7538                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7539                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7540                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7541                  gatingPiece = boards[currentMove][fromY][fromX];
7542                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7543                 fromX = x;
7544                 fromY = y; dragging = 1;
7545                 ReportClick("lift", x, y);
7546                 MarkTargetSquares(0);
7547                 DragPieceBegin(xPix, yPix, FALSE);
7548                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7549                     promoSweep = defaultPromoChoice;
7550                     selectFlag = 0; lastX = xPix; lastY = yPix;
7551                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7552                 }
7553             }
7554            }
7555            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7556            second = FALSE;
7557         }
7558         // ignore clicks on holdings
7559         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7560     }
7561 printf("A type=%d\n",clickType);
7562
7563     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7564         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7565         return;
7566     }
7567
7568     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7569         DragPieceEnd(xPix, yPix); dragging = 0;
7570         if(clearFlag) {
7571             // a deferred attempt to click-click move an empty square on top of a piece
7572             boards[currentMove][y][x] = EmptySquare;
7573             ClearHighlights();
7574             DrawPosition(FALSE, boards[currentMove]);
7575             fromX = fromY = -1; clearFlag = 0;
7576             return;
7577         }
7578         if (appData.animateDragging) {
7579             /* Undo animation damage if any */
7580             DrawPosition(FALSE, NULL);
7581         }
7582         if (second || sweepSelecting) {
7583             /* Second up/down in same square; just abort move */
7584             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7585             second = sweepSelecting = 0;
7586             fromX = fromY = -1;
7587             gatingPiece = EmptySquare;
7588             MarkTargetSquares(1);
7589             ClearHighlights();
7590             gotPremove = 0;
7591             ClearPremoveHighlights();
7592         } else {
7593             /* First upclick in same square; start click-click mode */
7594             SetHighlights(x, y, -1, -1);
7595         }
7596         return;
7597     }
7598
7599     clearFlag = 0;
7600 printf("B\n");
7601     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7602        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7603         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7604         DisplayMessage(_("only marked squares are legal"),"");
7605         DrawPosition(TRUE, NULL);
7606         return; // ignore to-click
7607     }
7608 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7609     /* we now have a different from- and (possibly off-board) to-square */
7610     /* Completed move */
7611     if(!sweepSelecting) {
7612         toX = x;
7613         toY = y;
7614     }
7615
7616     piece = boards[currentMove][fromY][fromX];
7617
7618     saveAnimate = appData.animate;
7619     if (clickType == Press) {
7620         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7621         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7622             // must be Edit Position mode with empty-square selected
7623             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7624             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7625             return;
7626         }
7627         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7628             return;
7629         }
7630         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7631             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7632         } else
7633         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7634         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7635           if(appData.sweepSelect) {
7636             promoSweep = defaultPromoChoice;
7637             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7638             selectFlag = 0; lastX = xPix; lastY = yPix;
7639             Sweep(0); // Pawn that is going to promote: preview promotion piece
7640             sweepSelecting = 1;
7641             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7642             MarkTargetSquares(1);
7643           }
7644           return; // promo popup appears on up-click
7645         }
7646         /* Finish clickclick move */
7647         if (appData.animate || appData.highlightLastMove) {
7648             SetHighlights(fromX, fromY, toX, toY);
7649         } else {
7650             ClearHighlights();
7651         }
7652     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7653         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7654         if (appData.animate || appData.highlightLastMove) {
7655             SetHighlights(fromX, fromY, toX, toY);
7656         } else {
7657             ClearHighlights();
7658         }
7659     } else {
7660 #if 0
7661 // [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
7662         /* Finish drag move */
7663         if (appData.highlightLastMove) {
7664             SetHighlights(fromX, fromY, toX, toY);
7665         } else {
7666             ClearHighlights();
7667         }
7668 #endif
7669         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7670         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7671           dragging *= 2;            // flag button-less dragging if we are dragging
7672           MarkTargetSquares(1);
7673           if(x == killX && y == killY) killX = killY = -1; else {
7674             killX = x; killY = y;     //remeber this square as intermediate
7675             ReportClick("put", x, y); // and inform engine
7676             ReportClick("lift", x, y);
7677             MarkTargetSquares(0);
7678             return;
7679           }
7680         }
7681         DragPieceEnd(xPix, yPix); dragging = 0;
7682         /* Don't animate move and drag both */
7683         appData.animate = FALSE;
7684     }
7685
7686     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7687     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7688         ChessSquare piece = boards[currentMove][fromY][fromX];
7689         if(gameMode == EditPosition && piece != EmptySquare &&
7690            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7691             int n;
7692
7693             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7694                 n = PieceToNumber(piece - (int)BlackPawn);
7695                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7696                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7697                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7698             } else
7699             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7700                 n = PieceToNumber(piece);
7701                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7702                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7703                 boards[currentMove][n][BOARD_WIDTH-2]++;
7704             }
7705             boards[currentMove][fromY][fromX] = EmptySquare;
7706         }
7707         ClearHighlights();
7708         fromX = fromY = -1;
7709         MarkTargetSquares(1);
7710         DrawPosition(TRUE, boards[currentMove]);
7711         return;
7712     }
7713
7714     // off-board moves should not be highlighted
7715     if(x < 0 || y < 0) ClearHighlights();
7716     else ReportClick("put", x, y);
7717
7718     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7719
7720     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7721         SetHighlights(fromX, fromY, toX, toY);
7722         MarkTargetSquares(1);
7723         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7724             // [HGM] super: promotion to captured piece selected from holdings
7725             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7726             promotionChoice = TRUE;
7727             // kludge follows to temporarily execute move on display, without promoting yet
7728             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7729             boards[currentMove][toY][toX] = p;
7730             DrawPosition(FALSE, boards[currentMove]);
7731             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7732             boards[currentMove][toY][toX] = q;
7733             DisplayMessage("Click in holdings to choose piece", "");
7734             return;
7735         }
7736         PromotionPopUp(promoChoice);
7737     } else {
7738         int oldMove = currentMove;
7739         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7740         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7741         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7742         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7743            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7744             DrawPosition(TRUE, boards[currentMove]);
7745         MarkTargetSquares(1);
7746         fromX = fromY = -1;
7747     }
7748     appData.animate = saveAnimate;
7749     if (appData.animate || appData.animateDragging) {
7750         /* Undo animation damage if needed */
7751         DrawPosition(FALSE, NULL);
7752     }
7753 }
7754
7755 int
7756 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7757 {   // front-end-free part taken out of PieceMenuPopup
7758     int whichMenu; int xSqr, ySqr;
7759
7760     if(seekGraphUp) { // [HGM] seekgraph
7761         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7762         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7763         return -2;
7764     }
7765
7766     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7767          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7768         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7769         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7770         if(action == Press)   {
7771             originalFlip = flipView;
7772             flipView = !flipView; // temporarily flip board to see game from partners perspective
7773             DrawPosition(TRUE, partnerBoard);
7774             DisplayMessage(partnerStatus, "");
7775             partnerUp = TRUE;
7776         } else if(action == Release) {
7777             flipView = originalFlip;
7778             DrawPosition(TRUE, boards[currentMove]);
7779             partnerUp = FALSE;
7780         }
7781         return -2;
7782     }
7783
7784     xSqr = EventToSquare(x, BOARD_WIDTH);
7785     ySqr = EventToSquare(y, BOARD_HEIGHT);
7786     if (action == Release) {
7787         if(pieceSweep != EmptySquare) {
7788             EditPositionMenuEvent(pieceSweep, toX, toY);
7789             pieceSweep = EmptySquare;
7790         } else UnLoadPV(); // [HGM] pv
7791     }
7792     if (action != Press) return -2; // return code to be ignored
7793     switch (gameMode) {
7794       case IcsExamining:
7795         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7796       case EditPosition:
7797         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7798         if (xSqr < 0 || ySqr < 0) return -1;
7799         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7800         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7801         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7802         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7803         NextPiece(0);
7804         return 2; // grab
7805       case IcsObserving:
7806         if(!appData.icsEngineAnalyze) return -1;
7807       case IcsPlayingWhite:
7808       case IcsPlayingBlack:
7809         if(!appData.zippyPlay) goto noZip;
7810       case AnalyzeMode:
7811       case AnalyzeFile:
7812       case MachinePlaysWhite:
7813       case MachinePlaysBlack:
7814       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7815         if (!appData.dropMenu) {
7816           LoadPV(x, y);
7817           return 2; // flag front-end to grab mouse events
7818         }
7819         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7820            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7821       case EditGame:
7822       noZip:
7823         if (xSqr < 0 || ySqr < 0) return -1;
7824         if (!appData.dropMenu || appData.testLegality &&
7825             gameInfo.variant != VariantBughouse &&
7826             gameInfo.variant != VariantCrazyhouse) return -1;
7827         whichMenu = 1; // drop menu
7828         break;
7829       default:
7830         return -1;
7831     }
7832
7833     if (((*fromX = xSqr) < 0) ||
7834         ((*fromY = ySqr) < 0)) {
7835         *fromX = *fromY = -1;
7836         return -1;
7837     }
7838     if (flipView)
7839       *fromX = BOARD_WIDTH - 1 - *fromX;
7840     else
7841       *fromY = BOARD_HEIGHT - 1 - *fromY;
7842
7843     return whichMenu;
7844 }
7845
7846 void
7847 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7848 {
7849 //    char * hint = lastHint;
7850     FrontEndProgramStats stats;
7851
7852     stats.which = cps == &first ? 0 : 1;
7853     stats.depth = cpstats->depth;
7854     stats.nodes = cpstats->nodes;
7855     stats.score = cpstats->score;
7856     stats.time = cpstats->time;
7857     stats.pv = cpstats->movelist;
7858     stats.hint = lastHint;
7859     stats.an_move_index = 0;
7860     stats.an_move_count = 0;
7861
7862     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7863         stats.hint = cpstats->move_name;
7864         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7865         stats.an_move_count = cpstats->nr_moves;
7866     }
7867
7868     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
7869
7870     SetProgramStats( &stats );
7871 }
7872
7873 void
7874 ClearEngineOutputPane (int which)
7875 {
7876     static FrontEndProgramStats dummyStats;
7877     dummyStats.which = which;
7878     dummyStats.pv = "#";
7879     SetProgramStats( &dummyStats );
7880 }
7881
7882 #define MAXPLAYERS 500
7883
7884 char *
7885 TourneyStandings (int display)
7886 {
7887     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7888     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7889     char result, *p, *names[MAXPLAYERS];
7890
7891     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7892         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7893     names[0] = p = strdup(appData.participants);
7894     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7895
7896     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7897
7898     while(result = appData.results[nr]) {
7899         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7900         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7901         wScore = bScore = 0;
7902         switch(result) {
7903           case '+': wScore = 2; break;
7904           case '-': bScore = 2; break;
7905           case '=': wScore = bScore = 1; break;
7906           case ' ':
7907           case '*': return strdup("busy"); // tourney not finished
7908         }
7909         score[w] += wScore;
7910         score[b] += bScore;
7911         games[w]++;
7912         games[b]++;
7913         nr++;
7914     }
7915     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7916     for(w=0; w<nPlayers; w++) {
7917         bScore = -1;
7918         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7919         ranking[w] = b; points[w] = bScore; score[b] = -2;
7920     }
7921     p = malloc(nPlayers*34+1);
7922     for(w=0; w<nPlayers && w<display; w++)
7923         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7924     free(names[0]);
7925     return p;
7926 }
7927
7928 void
7929 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7930 {       // count all piece types
7931         int p, f, r;
7932         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7933         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7934         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7935                 p = board[r][f];
7936                 pCnt[p]++;
7937                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7938                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7939                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7940                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7941                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7942                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7943         }
7944 }
7945
7946 int
7947 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7948 {
7949         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7950         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7951
7952         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7953         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7954         if(myPawns == 2 && nMine == 3) // KPP
7955             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7956         if(myPawns == 1 && nMine == 2) // KP
7957             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7958         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7959             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7960         if(myPawns) return FALSE;
7961         if(pCnt[WhiteRook+side])
7962             return pCnt[BlackRook-side] ||
7963                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7964                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7965                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7966         if(pCnt[WhiteCannon+side]) {
7967             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7968             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7969         }
7970         if(pCnt[WhiteKnight+side])
7971             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7972         return FALSE;
7973 }
7974
7975 int
7976 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7977 {
7978         VariantClass v = gameInfo.variant;
7979
7980         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7981         if(v == VariantShatranj) return TRUE; // always winnable through baring
7982         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7983         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7984
7985         if(v == VariantXiangqi) {
7986                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7987
7988                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7989                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7990                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7991                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7992                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7993                 if(stale) // we have at least one last-rank P plus perhaps C
7994                     return majors // KPKX
7995                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7996                 else // KCA*E*
7997                     return pCnt[WhiteFerz+side] // KCAK
7998                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7999                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8000                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8001
8002         } else if(v == VariantKnightmate) {
8003                 if(nMine == 1) return FALSE;
8004                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8005         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8006                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8007
8008                 if(nMine == 1) return FALSE; // bare King
8009                 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
8010                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8011                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8012                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8013                 if(pCnt[WhiteKnight+side])
8014                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8015                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8016                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8017                 if(nBishops)
8018                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8019                 if(pCnt[WhiteAlfil+side])
8020                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8021                 if(pCnt[WhiteWazir+side])
8022                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8023         }
8024
8025         return TRUE;
8026 }
8027
8028 int
8029 CompareWithRights (Board b1, Board b2)
8030 {
8031     int rights = 0;
8032     if(!CompareBoards(b1, b2)) return FALSE;
8033     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8034     /* compare castling rights */
8035     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8036            rights++; /* King lost rights, while rook still had them */
8037     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8038         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8039            rights++; /* but at least one rook lost them */
8040     }
8041     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8042            rights++;
8043     if( b1[CASTLING][5] != NoRights ) {
8044         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8045            rights++;
8046     }
8047     return rights == 0;
8048 }
8049
8050 int
8051 Adjudicate (ChessProgramState *cps)
8052 {       // [HGM] some adjudications useful with buggy engines
8053         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8054         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8055         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8056         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8057         int k, drop, count = 0; static int bare = 1;
8058         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8059         Boolean canAdjudicate = !appData.icsActive;
8060
8061         // most tests only when we understand the game, i.e. legality-checking on
8062             if( appData.testLegality )
8063             {   /* [HGM] Some more adjudications for obstinate engines */
8064                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8065                 static int moveCount = 6;
8066                 ChessMove result;
8067                 char *reason = NULL;
8068
8069                 /* Count what is on board. */
8070                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8071
8072                 /* Some material-based adjudications that have to be made before stalemate test */
8073                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8074                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8075                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8076                      if(canAdjudicate && appData.checkMates) {
8077                          if(engineOpponent)
8078                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8079                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8080                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8081                          return 1;
8082                      }
8083                 }
8084
8085                 /* Bare King in Shatranj (loses) or Losers (wins) */
8086                 if( nrW == 1 || nrB == 1) {
8087                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8088                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8089                      if(canAdjudicate && appData.checkMates) {
8090                          if(engineOpponent)
8091                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8092                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8093                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8094                          return 1;
8095                      }
8096                   } else
8097                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8098                   {    /* bare King */
8099                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8100                         if(canAdjudicate && appData.checkMates) {
8101                             /* but only adjudicate if adjudication enabled */
8102                             if(engineOpponent)
8103                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8104                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8105                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8106                             return 1;
8107                         }
8108                   }
8109                 } else bare = 1;
8110
8111
8112             // don't wait for engine to announce game end if we can judge ourselves
8113             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8114               case MT_CHECK:
8115                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8116                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8117                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8118                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8119                             checkCnt++;
8120                         if(checkCnt >= 2) {
8121                             reason = "Xboard adjudication: 3rd check";
8122                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8123                             break;
8124                         }
8125                     }
8126                 }
8127               case MT_NONE:
8128               default:
8129                 break;
8130               case MT_STEALMATE:
8131               case MT_STALEMATE:
8132               case MT_STAINMATE:
8133                 reason = "Xboard adjudication: Stalemate";
8134                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8135                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8136                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8137                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8138                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8139                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8140                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8141                                                                         EP_CHECKMATE : EP_WINS);
8142                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8143                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8144                 }
8145                 break;
8146               case MT_CHECKMATE:
8147                 reason = "Xboard adjudication: Checkmate";
8148                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8149                 if(gameInfo.variant == VariantShogi) {
8150                     if(forwardMostMove > backwardMostMove
8151                        && moveList[forwardMostMove-1][1] == '@'
8152                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8153                         reason = "XBoard adjudication: pawn-drop mate";
8154                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8155                     }
8156                 }
8157                 break;
8158             }
8159
8160                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8161                     case EP_STALEMATE:
8162                         result = GameIsDrawn; break;
8163                     case EP_CHECKMATE:
8164                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8165                     case EP_WINS:
8166                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8167                     default:
8168                         result = EndOfFile;
8169                 }
8170                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8171                     if(engineOpponent)
8172                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8173                     GameEnds( result, reason, GE_XBOARD );
8174                     return 1;
8175                 }
8176
8177                 /* Next absolutely insufficient mating material. */
8178                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8179                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8180                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8181
8182                      /* always flag draws, for judging claims */
8183                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8184
8185                      if(canAdjudicate && appData.materialDraws) {
8186                          /* but only adjudicate them if adjudication enabled */
8187                          if(engineOpponent) {
8188                            SendToProgram("force\n", engineOpponent); // suppress reply
8189                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8190                          }
8191                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8192                          return 1;
8193                      }
8194                 }
8195
8196                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8197                 if(gameInfo.variant == VariantXiangqi ?
8198                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8199                  : nrW + nrB == 4 &&
8200                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8201                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8202                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8203                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8204                    ) ) {
8205                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8206                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8207                           if(engineOpponent) {
8208                             SendToProgram("force\n", engineOpponent); // suppress reply
8209                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8210                           }
8211                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8212                           return 1;
8213                      }
8214                 } else moveCount = 6;
8215             }
8216
8217         // Repetition draws and 50-move rule can be applied independently of legality testing
8218
8219                 /* Check for rep-draws */
8220                 count = 0;
8221                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8222                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8223                 for(k = forwardMostMove-2;
8224                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8225                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8226                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8227                     k-=2)
8228                 {   int rights=0;
8229                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8230                         /* compare castling rights */
8231                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8232                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8233                                 rights++; /* King lost rights, while rook still had them */
8234                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8235                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8236                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8237                                    rights++; /* but at least one rook lost them */
8238                         }
8239                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8240                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8241                                 rights++;
8242                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8243                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8244                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8245                                    rights++;
8246                         }
8247                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8248                             && appData.drawRepeats > 1) {
8249                              /* adjudicate after user-specified nr of repeats */
8250                              int result = GameIsDrawn;
8251                              char *details = "XBoard adjudication: repetition draw";
8252                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8253                                 // [HGM] xiangqi: check for forbidden perpetuals
8254                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8255                                 for(m=forwardMostMove; m>k; m-=2) {
8256                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8257                                         ourPerpetual = 0; // the current mover did not always check
8258                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8259                                         hisPerpetual = 0; // the opponent did not always check
8260                                 }
8261                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8262                                                                         ourPerpetual, hisPerpetual);
8263                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8264                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8265                                     details = "Xboard adjudication: perpetual checking";
8266                                 } else
8267                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8268                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8269                                 } else
8270                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8271                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8272                                         result = BlackWins;
8273                                         details = "Xboard adjudication: repetition";
8274                                     }
8275                                 } else // it must be XQ
8276                                 // Now check for perpetual chases
8277                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8278                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8279                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8280                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8281                                         static char resdet[MSG_SIZ];
8282                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8283                                         details = resdet;
8284                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8285                                     } else
8286                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8287                                         break; // Abort repetition-checking loop.
8288                                 }
8289                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8290                              }
8291                              if(engineOpponent) {
8292                                SendToProgram("force\n", engineOpponent); // suppress reply
8293                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8294                              }
8295                              GameEnds( result, details, GE_XBOARD );
8296                              return 1;
8297                         }
8298                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8299                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8300                     }
8301                 }
8302
8303                 /* Now we test for 50-move draws. Determine ply count */
8304                 count = forwardMostMove;
8305                 /* look for last irreversble move */
8306                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8307                     count--;
8308                 /* if we hit starting position, add initial plies */
8309                 if( count == backwardMostMove )
8310                     count -= initialRulePlies;
8311                 count = forwardMostMove - count;
8312                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8313                         // adjust reversible move counter for checks in Xiangqi
8314                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8315                         if(i < backwardMostMove) i = backwardMostMove;
8316                         while(i <= forwardMostMove) {
8317                                 lastCheck = inCheck; // check evasion does not count
8318                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8319                                 if(inCheck || lastCheck) count--; // check does not count
8320                                 i++;
8321                         }
8322                 }
8323                 if( count >= 100)
8324                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8325                          /* this is used to judge if draw claims are legal */
8326                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8327                          if(engineOpponent) {
8328                            SendToProgram("force\n", engineOpponent); // suppress reply
8329                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8330                          }
8331                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8332                          return 1;
8333                 }
8334
8335                 /* if draw offer is pending, treat it as a draw claim
8336                  * when draw condition present, to allow engines a way to
8337                  * claim draws before making their move to avoid a race
8338                  * condition occurring after their move
8339                  */
8340                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8341                          char *p = NULL;
8342                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8343                              p = "Draw claim: 50-move rule";
8344                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8345                              p = "Draw claim: 3-fold repetition";
8346                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8347                              p = "Draw claim: insufficient mating material";
8348                          if( p != NULL && canAdjudicate) {
8349                              if(engineOpponent) {
8350                                SendToProgram("force\n", engineOpponent); // suppress reply
8351                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8352                              }
8353                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8354                              return 1;
8355                          }
8356                 }
8357
8358                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8359                     if(engineOpponent) {
8360                       SendToProgram("force\n", engineOpponent); // suppress reply
8361                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8362                     }
8363                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8364                     return 1;
8365                 }
8366         return 0;
8367 }
8368
8369 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8370 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8371 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8372
8373 static int
8374 BitbaseProbe ()
8375 {
8376     int pieces[10], squares[10], cnt=0, r, f, res;
8377     static int loaded;
8378     static PPROBE_EGBB probeBB;
8379     if(!appData.testLegality) return 10;
8380     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8381     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8382     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8383     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8384         ChessSquare piece = boards[forwardMostMove][r][f];
8385         int black = (piece >= BlackPawn);
8386         int type = piece - black*BlackPawn;
8387         if(piece == EmptySquare) continue;
8388         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8389         if(type == WhiteKing) type = WhiteQueen + 1;
8390         type = egbbCode[type];
8391         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8392         pieces[cnt] = type + black*6;
8393         if(++cnt > 5) return 11;
8394     }
8395     pieces[cnt] = squares[cnt] = 0;
8396     // probe EGBB
8397     if(loaded == 2) return 13; // loading failed before
8398     if(loaded == 0) {
8399         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8400         HMODULE lib;
8401         PLOAD_EGBB loadBB;
8402         loaded = 2; // prepare for failure
8403         if(!path) return 13; // no egbb installed
8404         strncpy(buf, path + 8, MSG_SIZ);
8405         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8406         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8407         lib = LoadLibrary(buf);
8408         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8409         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8410         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8411         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8412         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8413         loaded = 1; // success!
8414     }
8415     res = probeBB(forwardMostMove & 1, pieces, squares);
8416     return res > 0 ? 1 : res < 0 ? -1 : 0;
8417 }
8418
8419 char *
8420 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8421 {   // [HGM] book: this routine intercepts moves to simulate book replies
8422     char *bookHit = NULL;
8423
8424     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8425         char buf[MSG_SIZ];
8426         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8427         SendToProgram(buf, cps);
8428     }
8429     //first determine if the incoming move brings opponent into his book
8430     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8431         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8432     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8433     if(bookHit != NULL && !cps->bookSuspend) {
8434         // make sure opponent is not going to reply after receiving move to book position
8435         SendToProgram("force\n", cps);
8436         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8437     }
8438     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8439     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8440     // now arrange restart after book miss
8441     if(bookHit) {
8442         // after a book hit we never send 'go', and the code after the call to this routine
8443         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8444         char buf[MSG_SIZ], *move = bookHit;
8445         if(cps->useSAN) {
8446             int fromX, fromY, toX, toY;
8447             char promoChar;
8448             ChessMove moveType;
8449             move = buf + 30;
8450             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8451                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8452                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8453                                     PosFlags(forwardMostMove),
8454                                     fromY, fromX, toY, toX, promoChar, move);
8455             } else {
8456                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8457                 bookHit = NULL;
8458             }
8459         }
8460         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8461         SendToProgram(buf, cps);
8462         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8463     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8464         SendToProgram("go\n", cps);
8465         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8466     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8467         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8468             SendToProgram("go\n", cps);
8469         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8470     }
8471     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8472 }
8473
8474 int
8475 LoadError (char *errmess, ChessProgramState *cps)
8476 {   // unloads engine and switches back to -ncp mode if it was first
8477     if(cps->initDone) return FALSE;
8478     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8479     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8480     cps->pr = NoProc;
8481     if(cps == &first) {
8482         appData.noChessProgram = TRUE;
8483         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8484         gameMode = BeginningOfGame; ModeHighlight();
8485         SetNCPMode();
8486     }
8487     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8488     DisplayMessage("", ""); // erase waiting message
8489     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8490     return TRUE;
8491 }
8492
8493 char *savedMessage;
8494 ChessProgramState *savedState;
8495 void
8496 DeferredBookMove (void)
8497 {
8498         if(savedState->lastPing != savedState->lastPong)
8499                     ScheduleDelayedEvent(DeferredBookMove, 10);
8500         else
8501         HandleMachineMove(savedMessage, savedState);
8502 }
8503
8504 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8505 static ChessProgramState *stalledEngine;
8506 static char stashedInputMove[MSG_SIZ];
8507
8508 void
8509 HandleMachineMove (char *message, ChessProgramState *cps)
8510 {
8511     static char firstLeg[20];
8512     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8513     char realname[MSG_SIZ];
8514     int fromX, fromY, toX, toY;
8515     ChessMove moveType;
8516     char promoChar, roar;
8517     char *p, *pv=buf1;
8518     int machineWhite, oldError;
8519     char *bookHit;
8520
8521     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8522         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8523         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8524             DisplayError(_("Invalid pairing from pairing engine"), 0);
8525             return;
8526         }
8527         pairingReceived = 1;
8528         NextMatchGame();
8529         return; // Skim the pairing messages here.
8530     }
8531
8532     oldError = cps->userError; cps->userError = 0;
8533
8534 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8535     /*
8536      * Kludge to ignore BEL characters
8537      */
8538     while (*message == '\007') message++;
8539
8540     /*
8541      * [HGM] engine debug message: ignore lines starting with '#' character
8542      */
8543     if(cps->debug && *message == '#') return;
8544
8545     /*
8546      * Look for book output
8547      */
8548     if (cps == &first && bookRequested) {
8549         if (message[0] == '\t' || message[0] == ' ') {
8550             /* Part of the book output is here; append it */
8551             strcat(bookOutput, message);
8552             strcat(bookOutput, "  \n");
8553             return;
8554         } else if (bookOutput[0] != NULLCHAR) {
8555             /* All of book output has arrived; display it */
8556             char *p = bookOutput;
8557             while (*p != NULLCHAR) {
8558                 if (*p == '\t') *p = ' ';
8559                 p++;
8560             }
8561             DisplayInformation(bookOutput);
8562             bookRequested = FALSE;
8563             /* Fall through to parse the current output */
8564         }
8565     }
8566
8567     /*
8568      * Look for machine move.
8569      */
8570     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8571         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8572     {
8573         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8574             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8575             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8576             stalledEngine = cps;
8577             if(appData.ponderNextMove) { // bring opponent out of ponder
8578                 if(gameMode == TwoMachinesPlay) {
8579                     if(cps->other->pause)
8580                         PauseEngine(cps->other);
8581                     else
8582                         SendToProgram("easy\n", cps->other);
8583                 }
8584             }
8585             StopClocks();
8586             return;
8587         }
8588
8589         /* This method is only useful on engines that support ping */
8590         if (cps->lastPing != cps->lastPong) {
8591           if (gameMode == BeginningOfGame) {
8592             /* Extra move from before last new; ignore */
8593             if (appData.debugMode) {
8594                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8595             }
8596           } else {
8597             if (appData.debugMode) {
8598                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8599                         cps->which, gameMode);
8600             }
8601
8602             SendToProgram("undo\n", cps);
8603           }
8604           return;
8605         }
8606
8607         switch (gameMode) {
8608           case BeginningOfGame:
8609             /* Extra move from before last reset; ignore */
8610             if (appData.debugMode) {
8611                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8612             }
8613             return;
8614
8615           case EndOfGame:
8616           case IcsIdle:
8617           default:
8618             /* Extra move after we tried to stop.  The mode test is
8619                not a reliable way of detecting this problem, but it's
8620                the best we can do on engines that don't support ping.
8621             */
8622             if (appData.debugMode) {
8623                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8624                         cps->which, gameMode);
8625             }
8626             SendToProgram("undo\n", cps);
8627             return;
8628
8629           case MachinePlaysWhite:
8630           case IcsPlayingWhite:
8631             machineWhite = TRUE;
8632             break;
8633
8634           case MachinePlaysBlack:
8635           case IcsPlayingBlack:
8636             machineWhite = FALSE;
8637             break;
8638
8639           case TwoMachinesPlay:
8640             machineWhite = (cps->twoMachinesColor[0] == 'w');
8641             break;
8642         }
8643         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8644             if (appData.debugMode) {
8645                 fprintf(debugFP,
8646                         "Ignoring move out of turn by %s, gameMode %d"
8647                         ", forwardMost %d\n",
8648                         cps->which, gameMode, forwardMostMove);
8649             }
8650             return;
8651         }
8652
8653         if(cps->alphaRank) AlphaRank(machineMove, 4);
8654
8655         // [HGM] lion: (some very limited) support for Alien protocol
8656         killX = killY = -1;
8657         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8658             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8659             return;
8660         } else if(firstLeg[0]) { // there was a previous leg;
8661             // only support case where same piece makes two step (and don't even test that!)
8662             char buf[20], *p = machineMove+1, *q = buf+1, f;
8663             safeStrCpy(buf, machineMove, 20);
8664             while(isdigit(*q)) q++; // find start of to-square
8665             safeStrCpy(machineMove, firstLeg, 20);
8666             while(isdigit(*p)) p++;
8667             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8668             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8669             firstLeg[0] = NULLCHAR;
8670         }
8671
8672         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8673                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8674             /* Machine move could not be parsed; ignore it. */
8675           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8676                     machineMove, _(cps->which));
8677             DisplayMoveError(buf1);
8678             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8679                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8680             if (gameMode == TwoMachinesPlay) {
8681               GameEnds(machineWhite ? BlackWins : WhiteWins,
8682                        buf1, GE_XBOARD);
8683             }
8684             return;
8685         }
8686
8687         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8688         /* So we have to redo legality test with true e.p. status here,  */
8689         /* to make sure an illegal e.p. capture does not slip through,   */
8690         /* to cause a forfeit on a justified illegal-move complaint      */
8691         /* of the opponent.                                              */
8692         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8693            ChessMove moveType;
8694            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8695                              fromY, fromX, toY, toX, promoChar);
8696             if(moveType == IllegalMove) {
8697               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8698                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8699                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8700                            buf1, GE_XBOARD);
8701                 return;
8702            } else if(!appData.fischerCastling)
8703            /* [HGM] Kludge to handle engines that send FRC-style castling
8704               when they shouldn't (like TSCP-Gothic) */
8705            switch(moveType) {
8706              case WhiteASideCastleFR:
8707              case BlackASideCastleFR:
8708                toX+=2;
8709                currentMoveString[2]++;
8710                break;
8711              case WhiteHSideCastleFR:
8712              case BlackHSideCastleFR:
8713                toX--;
8714                currentMoveString[2]--;
8715                break;
8716              default: ; // nothing to do, but suppresses warning of pedantic compilers
8717            }
8718         }
8719         hintRequested = FALSE;
8720         lastHint[0] = NULLCHAR;
8721         bookRequested = FALSE;
8722         /* Program may be pondering now */
8723         cps->maybeThinking = TRUE;
8724         if (cps->sendTime == 2) cps->sendTime = 1;
8725         if (cps->offeredDraw) cps->offeredDraw--;
8726
8727         /* [AS] Save move info*/
8728         pvInfoList[ forwardMostMove ].score = programStats.score;
8729         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8730         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8731
8732         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8733
8734         /* Test suites abort the 'game' after one move */
8735         if(*appData.finger) {
8736            static FILE *f;
8737            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8738            if(!f) f = fopen(appData.finger, "w");
8739            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8740            else { DisplayFatalError("Bad output file", errno, 0); return; }
8741            free(fen);
8742            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8743         }
8744
8745         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8746         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8747             int count = 0;
8748
8749             while( count < adjudicateLossPlies ) {
8750                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8751
8752                 if( count & 1 ) {
8753                     score = -score; /* Flip score for winning side */
8754                 }
8755
8756                 if( score > appData.adjudicateLossThreshold ) {
8757                     break;
8758                 }
8759
8760                 count++;
8761             }
8762
8763             if( count >= adjudicateLossPlies ) {
8764                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8765
8766                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8767                     "Xboard adjudication",
8768                     GE_XBOARD );
8769
8770                 return;
8771             }
8772         }
8773
8774         if(Adjudicate(cps)) {
8775             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8776             return; // [HGM] adjudicate: for all automatic game ends
8777         }
8778
8779 #if ZIPPY
8780         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8781             first.initDone) {
8782           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8783                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8784                 SendToICS("draw ");
8785                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8786           }
8787           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8788           ics_user_moved = 1;
8789           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8790                 char buf[3*MSG_SIZ];
8791
8792                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8793                         programStats.score / 100.,
8794                         programStats.depth,
8795                         programStats.time / 100.,
8796                         (unsigned int)programStats.nodes,
8797                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8798                         programStats.movelist);
8799                 SendToICS(buf);
8800           }
8801         }
8802 #endif
8803
8804         /* [AS] Clear stats for next move */
8805         ClearProgramStats();
8806         thinkOutput[0] = NULLCHAR;
8807         hiddenThinkOutputState = 0;
8808
8809         bookHit = NULL;
8810         if (gameMode == TwoMachinesPlay) {
8811             /* [HGM] relaying draw offers moved to after reception of move */
8812             /* and interpreting offer as claim if it brings draw condition */
8813             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8814                 SendToProgram("draw\n", cps->other);
8815             }
8816             if (cps->other->sendTime) {
8817                 SendTimeRemaining(cps->other,
8818                                   cps->other->twoMachinesColor[0] == 'w');
8819             }
8820             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8821             if (firstMove && !bookHit) {
8822                 firstMove = FALSE;
8823                 if (cps->other->useColors) {
8824                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8825                 }
8826                 SendToProgram("go\n", cps->other);
8827             }
8828             cps->other->maybeThinking = TRUE;
8829         }
8830
8831         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8832
8833         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8834
8835         if (!pausing && appData.ringBellAfterMoves) {
8836             if(!roar) RingBell();
8837         }
8838
8839         /*
8840          * Reenable menu items that were disabled while
8841          * machine was thinking
8842          */
8843         if (gameMode != TwoMachinesPlay)
8844             SetUserThinkingEnables();
8845
8846         // [HGM] book: after book hit opponent has received move and is now in force mode
8847         // force the book reply into it, and then fake that it outputted this move by jumping
8848         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8849         if(bookHit) {
8850                 static char bookMove[MSG_SIZ]; // a bit generous?
8851
8852                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8853                 strcat(bookMove, bookHit);
8854                 message = bookMove;
8855                 cps = cps->other;
8856                 programStats.nodes = programStats.depth = programStats.time =
8857                 programStats.score = programStats.got_only_move = 0;
8858                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8859
8860                 if(cps->lastPing != cps->lastPong) {
8861                     savedMessage = message; // args for deferred call
8862                     savedState = cps;
8863                     ScheduleDelayedEvent(DeferredBookMove, 10);
8864                     return;
8865                 }
8866                 goto FakeBookMove;
8867         }
8868
8869         return;
8870     }
8871
8872     /* Set special modes for chess engines.  Later something general
8873      *  could be added here; for now there is just one kludge feature,
8874      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8875      *  when "xboard" is given as an interactive command.
8876      */
8877     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8878         cps->useSigint = FALSE;
8879         cps->useSigterm = FALSE;
8880     }
8881     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8882       ParseFeatures(message+8, cps);
8883       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8884     }
8885
8886     if (!strncmp(message, "setup ", 6) && 
8887         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8888           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8889                                         ) { // [HGM] allow first engine to define opening position
8890       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8891       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8892       *buf = NULLCHAR;
8893       if(sscanf(message, "setup (%s", buf) == 1) {
8894         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8895         ASSIGN(appData.pieceToCharTable, buf);
8896       }
8897       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8898       if(dummy >= 3) {
8899         while(message[s] && message[s++] != ' ');
8900         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8901            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8902             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8903             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8904           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8905           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8906           startedFromSetupPosition = FALSE;
8907         }
8908       }
8909       if(startedFromSetupPosition) return;
8910       ParseFEN(boards[0], &dummy, message+s, FALSE);
8911       DrawPosition(TRUE, boards[0]);
8912       CopyBoard(initialPosition, boards[0]);
8913       startedFromSetupPosition = TRUE;
8914       return;
8915     }
8916     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8917       ChessSquare piece = WhitePawn;
8918       char *p=buf2;
8919       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8920       piece += CharToPiece(*p) - WhitePawn;
8921       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8922       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8923       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8924       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8925       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8926       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8927                                                && gameInfo.variant != VariantGreat
8928                                                && gameInfo.variant != VariantFairy    ) return;
8929       if(piece < EmptySquare) {
8930         pieceDefs = TRUE;
8931         ASSIGN(pieceDesc[piece], buf1);
8932         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8933       }
8934       return;
8935     }
8936     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8937      * want this, I was asked to put it in, and obliged.
8938      */
8939     if (!strncmp(message, "setboard ", 9)) {
8940         Board initial_position;
8941
8942         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8943
8944         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8945             DisplayError(_("Bad FEN received from engine"), 0);
8946             return ;
8947         } else {
8948            Reset(TRUE, FALSE);
8949            CopyBoard(boards[0], initial_position);
8950            initialRulePlies = FENrulePlies;
8951            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8952            else gameMode = MachinePlaysBlack;
8953            DrawPosition(FALSE, boards[currentMove]);
8954         }
8955         return;
8956     }
8957
8958     /*
8959      * Look for communication commands
8960      */
8961     if (!strncmp(message, "telluser ", 9)) {
8962         if(message[9] == '\\' && message[10] == '\\')
8963             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8964         PlayTellSound();
8965         DisplayNote(message + 9);
8966         return;
8967     }
8968     if (!strncmp(message, "tellusererror ", 14)) {
8969         cps->userError = 1;
8970         if(message[14] == '\\' && message[15] == '\\')
8971             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8972         PlayTellSound();
8973         DisplayError(message + 14, 0);
8974         return;
8975     }
8976     if (!strncmp(message, "tellopponent ", 13)) {
8977       if (appData.icsActive) {
8978         if (loggedOn) {
8979           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8980           SendToICS(buf1);
8981         }
8982       } else {
8983         DisplayNote(message + 13);
8984       }
8985       return;
8986     }
8987     if (!strncmp(message, "tellothers ", 11)) {
8988       if (appData.icsActive) {
8989         if (loggedOn) {
8990           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8991           SendToICS(buf1);
8992         }
8993       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8994       return;
8995     }
8996     if (!strncmp(message, "tellall ", 8)) {
8997       if (appData.icsActive) {
8998         if (loggedOn) {
8999           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9000           SendToICS(buf1);
9001         }
9002       } else {
9003         DisplayNote(message + 8);
9004       }
9005       return;
9006     }
9007     if (strncmp(message, "warning", 7) == 0) {
9008         /* Undocumented feature, use tellusererror in new code */
9009         DisplayError(message, 0);
9010         return;
9011     }
9012     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9013         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9014         strcat(realname, " query");
9015         AskQuestion(realname, buf2, buf1, cps->pr);
9016         return;
9017     }
9018     /* Commands from the engine directly to ICS.  We don't allow these to be
9019      *  sent until we are logged on. Crafty kibitzes have been known to
9020      *  interfere with the login process.
9021      */
9022     if (loggedOn) {
9023         if (!strncmp(message, "tellics ", 8)) {
9024             SendToICS(message + 8);
9025             SendToICS("\n");
9026             return;
9027         }
9028         if (!strncmp(message, "tellicsnoalias ", 15)) {
9029             SendToICS(ics_prefix);
9030             SendToICS(message + 15);
9031             SendToICS("\n");
9032             return;
9033         }
9034         /* The following are for backward compatibility only */
9035         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9036             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9037             SendToICS(ics_prefix);
9038             SendToICS(message);
9039             SendToICS("\n");
9040             return;
9041         }
9042     }
9043     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9044         if(initPing == cps->lastPong) {
9045             if(gameInfo.variant == VariantUnknown) {
9046                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9047                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9048                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9049             }
9050             initPing = -1;
9051         }
9052         return;
9053     }
9054     if(!strncmp(message, "highlight ", 10)) {
9055         if(appData.testLegality && appData.markers) return;
9056         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9057         return;
9058     }
9059     if(!strncmp(message, "click ", 6)) {
9060         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9061         if(appData.testLegality || !appData.oneClick) return;
9062         sscanf(message+6, "%c%d%c", &f, &y, &c);
9063         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9064         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9065         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9066         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9067         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9068         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9069             LeftClick(Release, lastLeftX, lastLeftY);
9070         controlKey  = (c == ',');
9071         LeftClick(Press, x, y);
9072         LeftClick(Release, x, y);
9073         first.highlight = f;
9074         return;
9075     }
9076     /*
9077      * If the move is illegal, cancel it and redraw the board.
9078      * Also deal with other error cases.  Matching is rather loose
9079      * here to accommodate engines written before the spec.
9080      */
9081     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9082         strncmp(message, "Error", 5) == 0) {
9083         if (StrStr(message, "name") ||
9084             StrStr(message, "rating") || StrStr(message, "?") ||
9085             StrStr(message, "result") || StrStr(message, "board") ||
9086             StrStr(message, "bk") || StrStr(message, "computer") ||
9087             StrStr(message, "variant") || StrStr(message, "hint") ||
9088             StrStr(message, "random") || StrStr(message, "depth") ||
9089             StrStr(message, "accepted")) {
9090             return;
9091         }
9092         if (StrStr(message, "protover")) {
9093           /* Program is responding to input, so it's apparently done
9094              initializing, and this error message indicates it is
9095              protocol version 1.  So we don't need to wait any longer
9096              for it to initialize and send feature commands. */
9097           FeatureDone(cps, 1);
9098           cps->protocolVersion = 1;
9099           return;
9100         }
9101         cps->maybeThinking = FALSE;
9102
9103         if (StrStr(message, "draw")) {
9104             /* Program doesn't have "draw" command */
9105             cps->sendDrawOffers = 0;
9106             return;
9107         }
9108         if (cps->sendTime != 1 &&
9109             (StrStr(message, "time") || StrStr(message, "otim"))) {
9110           /* Program apparently doesn't have "time" or "otim" command */
9111           cps->sendTime = 0;
9112           return;
9113         }
9114         if (StrStr(message, "analyze")) {
9115             cps->analysisSupport = FALSE;
9116             cps->analyzing = FALSE;
9117 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9118             EditGameEvent(); // [HGM] try to preserve loaded game
9119             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9120             DisplayError(buf2, 0);
9121             return;
9122         }
9123         if (StrStr(message, "(no matching move)st")) {
9124           /* Special kludge for GNU Chess 4 only */
9125           cps->stKludge = TRUE;
9126           SendTimeControl(cps, movesPerSession, timeControl,
9127                           timeIncrement, appData.searchDepth,
9128                           searchTime);
9129           return;
9130         }
9131         if (StrStr(message, "(no matching move)sd")) {
9132           /* Special kludge for GNU Chess 4 only */
9133           cps->sdKludge = TRUE;
9134           SendTimeControl(cps, movesPerSession, timeControl,
9135                           timeIncrement, appData.searchDepth,
9136                           searchTime);
9137           return;
9138         }
9139         if (!StrStr(message, "llegal")) {
9140             return;
9141         }
9142         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9143             gameMode == IcsIdle) return;
9144         if (forwardMostMove <= backwardMostMove) return;
9145         if (pausing) PauseEvent();
9146       if(appData.forceIllegal) {
9147             // [HGM] illegal: machine refused move; force position after move into it
9148           SendToProgram("force\n", cps);
9149           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9150                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9151                 // when black is to move, while there might be nothing on a2 or black
9152                 // might already have the move. So send the board as if white has the move.
9153                 // But first we must change the stm of the engine, as it refused the last move
9154                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9155                 if(WhiteOnMove(forwardMostMove)) {
9156                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9157                     SendBoard(cps, forwardMostMove); // kludgeless board
9158                 } else {
9159                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9160                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9161                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9162                 }
9163           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9164             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9165                  gameMode == TwoMachinesPlay)
9166               SendToProgram("go\n", cps);
9167             return;
9168       } else
9169         if (gameMode == PlayFromGameFile) {
9170             /* Stop reading this game file */
9171             gameMode = EditGame;
9172             ModeHighlight();
9173         }
9174         /* [HGM] illegal-move claim should forfeit game when Xboard */
9175         /* only passes fully legal moves                            */
9176         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9177             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9178                                 "False illegal-move claim", GE_XBOARD );
9179             return; // do not take back move we tested as valid
9180         }
9181         currentMove = forwardMostMove-1;
9182         DisplayMove(currentMove-1); /* before DisplayMoveError */
9183         SwitchClocks(forwardMostMove-1); // [HGM] race
9184         DisplayBothClocks();
9185         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9186                 parseList[currentMove], _(cps->which));
9187         DisplayMoveError(buf1);
9188         DrawPosition(FALSE, boards[currentMove]);
9189
9190         SetUserThinkingEnables();
9191         return;
9192     }
9193     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9194         /* Program has a broken "time" command that
9195            outputs a string not ending in newline.
9196            Don't use it. */
9197         cps->sendTime = 0;
9198     }
9199     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9200         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9201             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9202     }
9203
9204     /*
9205      * If chess program startup fails, exit with an error message.
9206      * Attempts to recover here are futile. [HGM] Well, we try anyway
9207      */
9208     if ((StrStr(message, "unknown host") != NULL)
9209         || (StrStr(message, "No remote directory") != NULL)
9210         || (StrStr(message, "not found") != NULL)
9211         || (StrStr(message, "No such file") != NULL)
9212         || (StrStr(message, "can't alloc") != NULL)
9213         || (StrStr(message, "Permission denied") != NULL)) {
9214
9215         cps->maybeThinking = FALSE;
9216         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9217                 _(cps->which), cps->program, cps->host, message);
9218         RemoveInputSource(cps->isr);
9219         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9220             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9221             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9222         }
9223         return;
9224     }
9225
9226     /*
9227      * Look for hint output
9228      */
9229     if (sscanf(message, "Hint: %s", buf1) == 1) {
9230         if (cps == &first && hintRequested) {
9231             hintRequested = FALSE;
9232             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9233                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9234                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9235                                     PosFlags(forwardMostMove),
9236                                     fromY, fromX, toY, toX, promoChar, buf1);
9237                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9238                 DisplayInformation(buf2);
9239             } else {
9240                 /* Hint move could not be parsed!? */
9241               snprintf(buf2, sizeof(buf2),
9242                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9243                         buf1, _(cps->which));
9244                 DisplayError(buf2, 0);
9245             }
9246         } else {
9247           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9248         }
9249         return;
9250     }
9251
9252     /*
9253      * Ignore other messages if game is not in progress
9254      */
9255     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9256         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9257
9258     /*
9259      * look for win, lose, draw, or draw offer
9260      */
9261     if (strncmp(message, "1-0", 3) == 0) {
9262         char *p, *q, *r = "";
9263         p = strchr(message, '{');
9264         if (p) {
9265             q = strchr(p, '}');
9266             if (q) {
9267                 *q = NULLCHAR;
9268                 r = p + 1;
9269             }
9270         }
9271         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9272         return;
9273     } else if (strncmp(message, "0-1", 3) == 0) {
9274         char *p, *q, *r = "";
9275         p = strchr(message, '{');
9276         if (p) {
9277             q = strchr(p, '}');
9278             if (q) {
9279                 *q = NULLCHAR;
9280                 r = p + 1;
9281             }
9282         }
9283         /* Kludge for Arasan 4.1 bug */
9284         if (strcmp(r, "Black resigns") == 0) {
9285             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9286             return;
9287         }
9288         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9289         return;
9290     } else if (strncmp(message, "1/2", 3) == 0) {
9291         char *p, *q, *r = "";
9292         p = strchr(message, '{');
9293         if (p) {
9294             q = strchr(p, '}');
9295             if (q) {
9296                 *q = NULLCHAR;
9297                 r = p + 1;
9298             }
9299         }
9300
9301         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9302         return;
9303
9304     } else if (strncmp(message, "White resign", 12) == 0) {
9305         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9306         return;
9307     } else if (strncmp(message, "Black resign", 12) == 0) {
9308         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9309         return;
9310     } else if (strncmp(message, "White matches", 13) == 0 ||
9311                strncmp(message, "Black matches", 13) == 0   ) {
9312         /* [HGM] ignore GNUShogi noises */
9313         return;
9314     } else if (strncmp(message, "White", 5) == 0 &&
9315                message[5] != '(' &&
9316                StrStr(message, "Black") == NULL) {
9317         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9318         return;
9319     } else if (strncmp(message, "Black", 5) == 0 &&
9320                message[5] != '(') {
9321         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9322         return;
9323     } else if (strcmp(message, "resign") == 0 ||
9324                strcmp(message, "computer resigns") == 0) {
9325         switch (gameMode) {
9326           case MachinePlaysBlack:
9327           case IcsPlayingBlack:
9328             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9329             break;
9330           case MachinePlaysWhite:
9331           case IcsPlayingWhite:
9332             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9333             break;
9334           case TwoMachinesPlay:
9335             if (cps->twoMachinesColor[0] == 'w')
9336               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9337             else
9338               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9339             break;
9340           default:
9341             /* can't happen */
9342             break;
9343         }
9344         return;
9345     } else if (strncmp(message, "opponent mates", 14) == 0) {
9346         switch (gameMode) {
9347           case MachinePlaysBlack:
9348           case IcsPlayingBlack:
9349             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9350             break;
9351           case MachinePlaysWhite:
9352           case IcsPlayingWhite:
9353             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9354             break;
9355           case TwoMachinesPlay:
9356             if (cps->twoMachinesColor[0] == 'w')
9357               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9358             else
9359               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9360             break;
9361           default:
9362             /* can't happen */
9363             break;
9364         }
9365         return;
9366     } else if (strncmp(message, "computer mates", 14) == 0) {
9367         switch (gameMode) {
9368           case MachinePlaysBlack:
9369           case IcsPlayingBlack:
9370             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9371             break;
9372           case MachinePlaysWhite:
9373           case IcsPlayingWhite:
9374             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9375             break;
9376           case TwoMachinesPlay:
9377             if (cps->twoMachinesColor[0] == 'w')
9378               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9379             else
9380               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9381             break;
9382           default:
9383             /* can't happen */
9384             break;
9385         }
9386         return;
9387     } else if (strncmp(message, "checkmate", 9) == 0) {
9388         if (WhiteOnMove(forwardMostMove)) {
9389             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9390         } else {
9391             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9392         }
9393         return;
9394     } else if (strstr(message, "Draw") != NULL ||
9395                strstr(message, "game is a draw") != NULL) {
9396         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9397         return;
9398     } else if (strstr(message, "offer") != NULL &&
9399                strstr(message, "draw") != NULL) {
9400 #if ZIPPY
9401         if (appData.zippyPlay && first.initDone) {
9402             /* Relay offer to ICS */
9403             SendToICS(ics_prefix);
9404             SendToICS("draw\n");
9405         }
9406 #endif
9407         cps->offeredDraw = 2; /* valid until this engine moves twice */
9408         if (gameMode == TwoMachinesPlay) {
9409             if (cps->other->offeredDraw) {
9410                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9411             /* [HGM] in two-machine mode we delay relaying draw offer      */
9412             /* until after we also have move, to see if it is really claim */
9413             }
9414         } else if (gameMode == MachinePlaysWhite ||
9415                    gameMode == MachinePlaysBlack) {
9416           if (userOfferedDraw) {
9417             DisplayInformation(_("Machine accepts your draw offer"));
9418             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9419           } else {
9420             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9421           }
9422         }
9423     }
9424
9425
9426     /*
9427      * Look for thinking output
9428      */
9429     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9430           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9431                                 ) {
9432         int plylev, mvleft, mvtot, curscore, time;
9433         char mvname[MOVE_LEN];
9434         u64 nodes; // [DM]
9435         char plyext;
9436         int ignore = FALSE;
9437         int prefixHint = FALSE;
9438         mvname[0] = NULLCHAR;
9439
9440         switch (gameMode) {
9441           case MachinePlaysBlack:
9442           case IcsPlayingBlack:
9443             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9444             break;
9445           case MachinePlaysWhite:
9446           case IcsPlayingWhite:
9447             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9448             break;
9449           case AnalyzeMode:
9450           case AnalyzeFile:
9451             break;
9452           case IcsObserving: /* [DM] icsEngineAnalyze */
9453             if (!appData.icsEngineAnalyze) ignore = TRUE;
9454             break;
9455           case TwoMachinesPlay:
9456             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9457                 ignore = TRUE;
9458             }
9459             break;
9460           default:
9461             ignore = TRUE;
9462             break;
9463         }
9464
9465         if (!ignore) {
9466             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9467             buf1[0] = NULLCHAR;
9468             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9469                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9470
9471                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9472                     nodes += u64Const(0x100000000);
9473
9474                 if (plyext != ' ' && plyext != '\t') {
9475                     time *= 100;
9476                 }
9477
9478                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9479                 if( cps->scoreIsAbsolute &&
9480                     ( gameMode == MachinePlaysBlack ||
9481                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9482                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9483                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9484                      !WhiteOnMove(currentMove)
9485                     ) )
9486                 {
9487                     curscore = -curscore;
9488                 }
9489
9490                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9491
9492                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9493                         char buf[MSG_SIZ];
9494                         FILE *f;
9495                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9496                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9497                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9498                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9499                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9500                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9501                                 fclose(f);
9502                         }
9503                         else
9504                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9505                           DisplayError(_("failed writing PV"), 0);
9506                 }
9507
9508                 tempStats.depth = plylev;
9509                 tempStats.nodes = nodes;
9510                 tempStats.time = time;
9511                 tempStats.score = curscore;
9512                 tempStats.got_only_move = 0;
9513
9514                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9515                         int ticklen;
9516
9517                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9518                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9519                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9520                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9521                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9522                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9523                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9524                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9525                 }
9526
9527                 /* Buffer overflow protection */
9528                 if (pv[0] != NULLCHAR) {
9529                     if (strlen(pv) >= sizeof(tempStats.movelist)
9530                         && appData.debugMode) {
9531                         fprintf(debugFP,
9532                                 "PV is too long; using the first %u bytes.\n",
9533                                 (unsigned) sizeof(tempStats.movelist) - 1);
9534                     }
9535
9536                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9537                 } else {
9538                     sprintf(tempStats.movelist, " no PV\n");
9539                 }
9540
9541                 if (tempStats.seen_stat) {
9542                     tempStats.ok_to_send = 1;
9543                 }
9544
9545                 if (strchr(tempStats.movelist, '(') != NULL) {
9546                     tempStats.line_is_book = 1;
9547                     tempStats.nr_moves = 0;
9548                     tempStats.moves_left = 0;
9549                 } else {
9550                     tempStats.line_is_book = 0;
9551                 }
9552
9553                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9554                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9555
9556                 SendProgramStatsToFrontend( cps, &tempStats );
9557
9558                 /*
9559                     [AS] Protect the thinkOutput buffer from overflow... this
9560                     is only useful if buf1 hasn't overflowed first!
9561                 */
9562                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9563                          plylev,
9564                          (gameMode == TwoMachinesPlay ?
9565                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9566                          ((double) curscore) / 100.0,
9567                          prefixHint ? lastHint : "",
9568                          prefixHint ? " " : "" );
9569
9570                 if( buf1[0] != NULLCHAR ) {
9571                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9572
9573                     if( strlen(pv) > max_len ) {
9574                         if( appData.debugMode) {
9575                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9576                         }
9577                         pv[max_len+1] = '\0';
9578                     }
9579
9580                     strcat( thinkOutput, pv);
9581                 }
9582
9583                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9584                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9585                     DisplayMove(currentMove - 1);
9586                 }
9587                 return;
9588
9589             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9590                 /* crafty (9.25+) says "(only move) <move>"
9591                  * if there is only 1 legal move
9592                  */
9593                 sscanf(p, "(only move) %s", buf1);
9594                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9595                 sprintf(programStats.movelist, "%s (only move)", buf1);
9596                 programStats.depth = 1;
9597                 programStats.nr_moves = 1;
9598                 programStats.moves_left = 1;
9599                 programStats.nodes = 1;
9600                 programStats.time = 1;
9601                 programStats.got_only_move = 1;
9602
9603                 /* Not really, but we also use this member to
9604                    mean "line isn't going to change" (Crafty
9605                    isn't searching, so stats won't change) */
9606                 programStats.line_is_book = 1;
9607
9608                 SendProgramStatsToFrontend( cps, &programStats );
9609
9610                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9611                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9612                     DisplayMove(currentMove - 1);
9613                 }
9614                 return;
9615             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9616                               &time, &nodes, &plylev, &mvleft,
9617                               &mvtot, mvname) >= 5) {
9618                 /* The stat01: line is from Crafty (9.29+) in response
9619                    to the "." command */
9620                 programStats.seen_stat = 1;
9621                 cps->maybeThinking = TRUE;
9622
9623                 if (programStats.got_only_move || !appData.periodicUpdates)
9624                   return;
9625
9626                 programStats.depth = plylev;
9627                 programStats.time = time;
9628                 programStats.nodes = nodes;
9629                 programStats.moves_left = mvleft;
9630                 programStats.nr_moves = mvtot;
9631                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9632                 programStats.ok_to_send = 1;
9633                 programStats.movelist[0] = '\0';
9634
9635                 SendProgramStatsToFrontend( cps, &programStats );
9636
9637                 return;
9638
9639             } else if (strncmp(message,"++",2) == 0) {
9640                 /* Crafty 9.29+ outputs this */
9641                 programStats.got_fail = 2;
9642                 return;
9643
9644             } else if (strncmp(message,"--",2) == 0) {
9645                 /* Crafty 9.29+ outputs this */
9646                 programStats.got_fail = 1;
9647                 return;
9648
9649             } else if (thinkOutput[0] != NULLCHAR &&
9650                        strncmp(message, "    ", 4) == 0) {
9651                 unsigned message_len;
9652
9653                 p = message;
9654                 while (*p && *p == ' ') p++;
9655
9656                 message_len = strlen( p );
9657
9658                 /* [AS] Avoid buffer overflow */
9659                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9660                     strcat(thinkOutput, " ");
9661                     strcat(thinkOutput, p);
9662                 }
9663
9664                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9665                     strcat(programStats.movelist, " ");
9666                     strcat(programStats.movelist, p);
9667                 }
9668
9669                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9670                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9671                     DisplayMove(currentMove - 1);
9672                 }
9673                 return;
9674             }
9675         }
9676         else {
9677             buf1[0] = NULLCHAR;
9678
9679             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9680                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9681             {
9682                 ChessProgramStats cpstats;
9683
9684                 if (plyext != ' ' && plyext != '\t') {
9685                     time *= 100;
9686                 }
9687
9688                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9689                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9690                     curscore = -curscore;
9691                 }
9692
9693                 cpstats.depth = plylev;
9694                 cpstats.nodes = nodes;
9695                 cpstats.time = time;
9696                 cpstats.score = curscore;
9697                 cpstats.got_only_move = 0;
9698                 cpstats.movelist[0] = '\0';
9699
9700                 if (buf1[0] != NULLCHAR) {
9701                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9702                 }
9703
9704                 cpstats.ok_to_send = 0;
9705                 cpstats.line_is_book = 0;
9706                 cpstats.nr_moves = 0;
9707                 cpstats.moves_left = 0;
9708
9709                 SendProgramStatsToFrontend( cps, &cpstats );
9710             }
9711         }
9712     }
9713 }
9714
9715
9716 /* Parse a game score from the character string "game", and
9717    record it as the history of the current game.  The game
9718    score is NOT assumed to start from the standard position.
9719    The display is not updated in any way.
9720    */
9721 void
9722 ParseGameHistory (char *game)
9723 {
9724     ChessMove moveType;
9725     int fromX, fromY, toX, toY, boardIndex;
9726     char promoChar;
9727     char *p, *q;
9728     char buf[MSG_SIZ];
9729
9730     if (appData.debugMode)
9731       fprintf(debugFP, "Parsing game history: %s\n", game);
9732
9733     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9734     gameInfo.site = StrSave(appData.icsHost);
9735     gameInfo.date = PGNDate();
9736     gameInfo.round = StrSave("-");
9737
9738     /* Parse out names of players */
9739     while (*game == ' ') game++;
9740     p = buf;
9741     while (*game != ' ') *p++ = *game++;
9742     *p = NULLCHAR;
9743     gameInfo.white = StrSave(buf);
9744     while (*game == ' ') game++;
9745     p = buf;
9746     while (*game != ' ' && *game != '\n') *p++ = *game++;
9747     *p = NULLCHAR;
9748     gameInfo.black = StrSave(buf);
9749
9750     /* Parse moves */
9751     boardIndex = blackPlaysFirst ? 1 : 0;
9752     yynewstr(game);
9753     for (;;) {
9754         yyboardindex = boardIndex;
9755         moveType = (ChessMove) Myylex();
9756         switch (moveType) {
9757           case IllegalMove:             /* maybe suicide chess, etc. */
9758   if (appData.debugMode) {
9759     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9761     setbuf(debugFP, NULL);
9762   }
9763           case WhitePromotion:
9764           case BlackPromotion:
9765           case WhiteNonPromotion:
9766           case BlackNonPromotion:
9767           case NormalMove:
9768           case FirstLeg:
9769           case WhiteCapturesEnPassant:
9770           case BlackCapturesEnPassant:
9771           case WhiteKingSideCastle:
9772           case WhiteQueenSideCastle:
9773           case BlackKingSideCastle:
9774           case BlackQueenSideCastle:
9775           case WhiteKingSideCastleWild:
9776           case WhiteQueenSideCastleWild:
9777           case BlackKingSideCastleWild:
9778           case BlackQueenSideCastleWild:
9779           /* PUSH Fabien */
9780           case WhiteHSideCastleFR:
9781           case WhiteASideCastleFR:
9782           case BlackHSideCastleFR:
9783           case BlackASideCastleFR:
9784           /* POP Fabien */
9785             fromX = currentMoveString[0] - AAA;
9786             fromY = currentMoveString[1] - ONE;
9787             toX = currentMoveString[2] - AAA;
9788             toY = currentMoveString[3] - ONE;
9789             promoChar = currentMoveString[4];
9790             break;
9791           case WhiteDrop:
9792           case BlackDrop:
9793             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9794             fromX = moveType == WhiteDrop ?
9795               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9796             (int) CharToPiece(ToLower(currentMoveString[0]));
9797             fromY = DROP_RANK;
9798             toX = currentMoveString[2] - AAA;
9799             toY = currentMoveString[3] - ONE;
9800             promoChar = NULLCHAR;
9801             break;
9802           case AmbiguousMove:
9803             /* bug? */
9804             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9805   if (appData.debugMode) {
9806     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9807     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9808     setbuf(debugFP, NULL);
9809   }
9810             DisplayError(buf, 0);
9811             return;
9812           case ImpossibleMove:
9813             /* bug? */
9814             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9815   if (appData.debugMode) {
9816     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9817     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9818     setbuf(debugFP, NULL);
9819   }
9820             DisplayError(buf, 0);
9821             return;
9822           case EndOfFile:
9823             if (boardIndex < backwardMostMove) {
9824                 /* Oops, gap.  How did that happen? */
9825                 DisplayError(_("Gap in move list"), 0);
9826                 return;
9827             }
9828             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9829             if (boardIndex > forwardMostMove) {
9830                 forwardMostMove = boardIndex;
9831             }
9832             return;
9833           case ElapsedTime:
9834             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9835                 strcat(parseList[boardIndex-1], " ");
9836                 strcat(parseList[boardIndex-1], yy_text);
9837             }
9838             continue;
9839           case Comment:
9840           case PGNTag:
9841           case NAG:
9842           default:
9843             /* ignore */
9844             continue;
9845           case WhiteWins:
9846           case BlackWins:
9847           case GameIsDrawn:
9848           case GameUnfinished:
9849             if (gameMode == IcsExamining) {
9850                 if (boardIndex < backwardMostMove) {
9851                     /* Oops, gap.  How did that happen? */
9852                     return;
9853                 }
9854                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9855                 return;
9856             }
9857             gameInfo.result = moveType;
9858             p = strchr(yy_text, '{');
9859             if (p == NULL) p = strchr(yy_text, '(');
9860             if (p == NULL) {
9861                 p = yy_text;
9862                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9863             } else {
9864                 q = strchr(p, *p == '{' ? '}' : ')');
9865                 if (q != NULL) *q = NULLCHAR;
9866                 p++;
9867             }
9868             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9869             gameInfo.resultDetails = StrSave(p);
9870             continue;
9871         }
9872         if (boardIndex >= forwardMostMove &&
9873             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9874             backwardMostMove = blackPlaysFirst ? 1 : 0;
9875             return;
9876         }
9877         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9878                                  fromY, fromX, toY, toX, promoChar,
9879                                  parseList[boardIndex]);
9880         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9881         /* currentMoveString is set as a side-effect of yylex */
9882         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9883         strcat(moveList[boardIndex], "\n");
9884         boardIndex++;
9885         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9886         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9887           case MT_NONE:
9888           case MT_STALEMATE:
9889           default:
9890             break;
9891           case MT_CHECK:
9892             if(!IS_SHOGI(gameInfo.variant))
9893                 strcat(parseList[boardIndex - 1], "+");
9894             break;
9895           case MT_CHECKMATE:
9896           case MT_STAINMATE:
9897             strcat(parseList[boardIndex - 1], "#");
9898             break;
9899         }
9900     }
9901 }
9902
9903
9904 /* Apply a move to the given board  */
9905 void
9906 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9907 {
9908   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9909   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9910
9911     /* [HGM] compute & store e.p. status and castling rights for new position */
9912     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9913
9914       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9915       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9916       board[EP_STATUS] = EP_NONE;
9917       board[EP_FILE] = board[EP_RANK] = 100;
9918
9919   if (fromY == DROP_RANK) {
9920         /* must be first */
9921         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9922             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9923             return;
9924         }
9925         piece = board[toY][toX] = (ChessSquare) fromX;
9926   } else {
9927 //      ChessSquare victim;
9928       int i;
9929
9930       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9931 //           victim = board[killY][killX],
9932            killed = board[killY][killX],
9933            board[killY][killX] = EmptySquare,
9934            board[EP_STATUS] = EP_CAPTURE;
9935
9936       if( board[toY][toX] != EmptySquare ) {
9937            board[EP_STATUS] = EP_CAPTURE;
9938            if( (fromX != toX || fromY != toY) && // not igui!
9939                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9940                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9941                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9942            }
9943       }
9944
9945       pawn = board[fromY][fromX];
9946       if( pawn == WhiteLance || pawn == BlackLance ) {
9947            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9948                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9949                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9950            }
9951       }
9952       if( pawn == WhitePawn ) {
9953            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9954                board[EP_STATUS] = EP_PAWN_MOVE;
9955            if( toY-fromY>=2) {
9956                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9957                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9958                         gameInfo.variant != VariantBerolina || toX < fromX)
9959                       board[EP_STATUS] = toX | berolina;
9960                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9961                         gameInfo.variant != VariantBerolina || toX > fromX)
9962                       board[EP_STATUS] = toX;
9963            }
9964       } else
9965       if( pawn == BlackPawn ) {
9966            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9967                board[EP_STATUS] = EP_PAWN_MOVE;
9968            if( toY-fromY<= -2) {
9969                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9970                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9971                         gameInfo.variant != VariantBerolina || toX < fromX)
9972                       board[EP_STATUS] = toX | berolina;
9973                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9974                         gameInfo.variant != VariantBerolina || toX > fromX)
9975                       board[EP_STATUS] = toX;
9976            }
9977        }
9978
9979        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9980        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9981        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9982        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9983
9984        for(i=0; i<nrCastlingRights; i++) {
9985            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9986               board[CASTLING][i] == toX   && castlingRank[i] == toY
9987              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9988        }
9989
9990        if(gameInfo.variant == VariantSChess) { // update virginity
9991            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9992            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9993            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9994            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9995        }
9996
9997      if (fromX == toX && fromY == toY) return;
9998
9999      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10000      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10001      if(gameInfo.variant == VariantKnightmate)
10002          king += (int) WhiteUnicorn - (int) WhiteKing;
10003
10004     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10005        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
10006         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10007         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10008         board[EP_STATUS] = EP_NONE; // capture was fake!
10009     } else
10010     /* Code added by Tord: */
10011     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10012     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10013         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10014       board[EP_STATUS] = EP_NONE; // capture was fake!
10015       board[fromY][fromX] = EmptySquare;
10016       board[toY][toX] = EmptySquare;
10017       if((toX > fromX) != (piece == WhiteRook)) {
10018         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10019       } else {
10020         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10021       }
10022     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10023                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10024       board[EP_STATUS] = EP_NONE;
10025       board[fromY][fromX] = EmptySquare;
10026       board[toY][toX] = EmptySquare;
10027       if((toX > fromX) != (piece == BlackRook)) {
10028         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10029       } else {
10030         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10031       }
10032     /* End of code added by Tord */
10033
10034     } else if (board[fromY][fromX] == king
10035         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10036         && toY == fromY && toX > fromX+1) {
10037         board[fromY][fromX] = EmptySquare;
10038         board[toY][toX] = king;
10039         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10040         board[toY][toX-1] = board[fromY][rookX];
10041         board[fromY][rookX] = EmptySquare;
10042     } else if (board[fromY][fromX] == king
10043         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10044                && toY == fromY && toX < fromX-1) {
10045         board[fromY][fromX] = EmptySquare;
10046         board[toY][toX] = king;
10047         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10048         board[toY][toX+1] = board[fromY][rookX];
10049         board[fromY][rookX] = EmptySquare;
10050     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10051                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10052                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10053                ) {
10054         /* white pawn promotion */
10055         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10056         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10057             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10058         board[fromY][fromX] = EmptySquare;
10059     } else if ((fromY >= BOARD_HEIGHT>>1)
10060                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10061                && (toX != fromX)
10062                && gameInfo.variant != VariantXiangqi
10063                && gameInfo.variant != VariantBerolina
10064                && (pawn == WhitePawn)
10065                && (board[toY][toX] == EmptySquare)) {
10066         board[fromY][fromX] = EmptySquare;
10067         board[toY][toX] = piece;
10068         if(toY == epRank - 128 + 1)
10069             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10070         else
10071             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10072     } else if ((fromY == BOARD_HEIGHT-4)
10073                && (toX == fromX)
10074                && gameInfo.variant == VariantBerolina
10075                && (board[fromY][fromX] == WhitePawn)
10076                && (board[toY][toX] == EmptySquare)) {
10077         board[fromY][fromX] = EmptySquare;
10078         board[toY][toX] = WhitePawn;
10079         if(oldEP & EP_BEROLIN_A) {
10080                 captured = board[fromY][fromX-1];
10081                 board[fromY][fromX-1] = EmptySquare;
10082         }else{  captured = board[fromY][fromX+1];
10083                 board[fromY][fromX+1] = EmptySquare;
10084         }
10085     } else if (board[fromY][fromX] == king
10086         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10087                && toY == fromY && toX > fromX+1) {
10088         board[fromY][fromX] = EmptySquare;
10089         board[toY][toX] = king;
10090         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10091         board[toY][toX-1] = board[fromY][rookX];
10092         board[fromY][rookX] = EmptySquare;
10093     } else if (board[fromY][fromX] == king
10094         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10095                && toY == fromY && toX < fromX-1) {
10096         board[fromY][fromX] = EmptySquare;
10097         board[toY][toX] = king;
10098         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10099         board[toY][toX+1] = board[fromY][rookX];
10100         board[fromY][rookX] = EmptySquare;
10101     } else if (fromY == 7 && fromX == 3
10102                && board[fromY][fromX] == BlackKing
10103                && toY == 7 && toX == 5) {
10104         board[fromY][fromX] = EmptySquare;
10105         board[toY][toX] = BlackKing;
10106         board[fromY][7] = EmptySquare;
10107         board[toY][4] = BlackRook;
10108     } else if (fromY == 7 && fromX == 3
10109                && board[fromY][fromX] == BlackKing
10110                && toY == 7 && toX == 1) {
10111         board[fromY][fromX] = EmptySquare;
10112         board[toY][toX] = BlackKing;
10113         board[fromY][0] = EmptySquare;
10114         board[toY][2] = BlackRook;
10115     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10116                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10117                && toY < promoRank && promoChar
10118                ) {
10119         /* black pawn promotion */
10120         board[toY][toX] = CharToPiece(ToLower(promoChar));
10121         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10122             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10123         board[fromY][fromX] = EmptySquare;
10124     } else if ((fromY < BOARD_HEIGHT>>1)
10125                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10126                && (toX != fromX)
10127                && gameInfo.variant != VariantXiangqi
10128                && gameInfo.variant != VariantBerolina
10129                && (pawn == BlackPawn)
10130                && (board[toY][toX] == EmptySquare)) {
10131         board[fromY][fromX] = EmptySquare;
10132         board[toY][toX] = piece;
10133         if(toY == epRank - 128 - 1)
10134             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10135         else
10136             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10137     } else if ((fromY == 3)
10138                && (toX == fromX)
10139                && gameInfo.variant == VariantBerolina
10140                && (board[fromY][fromX] == BlackPawn)
10141                && (board[toY][toX] == EmptySquare)) {
10142         board[fromY][fromX] = EmptySquare;
10143         board[toY][toX] = BlackPawn;
10144         if(oldEP & EP_BEROLIN_A) {
10145                 captured = board[fromY][fromX-1];
10146                 board[fromY][fromX-1] = EmptySquare;
10147         }else{  captured = board[fromY][fromX+1];
10148                 board[fromY][fromX+1] = EmptySquare;
10149         }
10150     } else {
10151         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10152         board[fromY][fromX] = EmptySquare;
10153         board[toY][toX] = piece;
10154     }
10155   }
10156
10157     if (gameInfo.holdingsWidth != 0) {
10158
10159       /* !!A lot more code needs to be written to support holdings  */
10160       /* [HGM] OK, so I have written it. Holdings are stored in the */
10161       /* penultimate board files, so they are automaticlly stored   */
10162       /* in the game history.                                       */
10163       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10164                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10165         /* Delete from holdings, by decreasing count */
10166         /* and erasing image if necessary            */
10167         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10168         if(p < (int) BlackPawn) { /* white drop */
10169              p -= (int)WhitePawn;
10170                  p = PieceToNumber((ChessSquare)p);
10171              if(p >= gameInfo.holdingsSize) p = 0;
10172              if(--board[p][BOARD_WIDTH-2] <= 0)
10173                   board[p][BOARD_WIDTH-1] = EmptySquare;
10174              if((int)board[p][BOARD_WIDTH-2] < 0)
10175                         board[p][BOARD_WIDTH-2] = 0;
10176         } else {                  /* black drop */
10177              p -= (int)BlackPawn;
10178                  p = PieceToNumber((ChessSquare)p);
10179              if(p >= gameInfo.holdingsSize) p = 0;
10180              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10181                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10182              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10183                         board[BOARD_HEIGHT-1-p][1] = 0;
10184         }
10185       }
10186       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10187           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10188         /* [HGM] holdings: Add to holdings, if holdings exist */
10189         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10190                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10191                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10192         }
10193         p = (int) captured;
10194         if (p >= (int) BlackPawn) {
10195           p -= (int)BlackPawn;
10196           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10197                   /* Restore shogi-promoted piece to its original  first */
10198                   captured = (ChessSquare) (DEMOTED captured);
10199                   p = DEMOTED p;
10200           }
10201           p = PieceToNumber((ChessSquare)p);
10202           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10203           board[p][BOARD_WIDTH-2]++;
10204           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10205         } else {
10206           p -= (int)WhitePawn;
10207           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10208                   captured = (ChessSquare) (DEMOTED captured);
10209                   p = DEMOTED p;
10210           }
10211           p = PieceToNumber((ChessSquare)p);
10212           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10213           board[BOARD_HEIGHT-1-p][1]++;
10214           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10215         }
10216       }
10217     } else if (gameInfo.variant == VariantAtomic) {
10218       if (captured != EmptySquare) {
10219         int y, x;
10220         for (y = toY-1; y <= toY+1; y++) {
10221           for (x = toX-1; x <= toX+1; x++) {
10222             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10223                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10224               board[y][x] = EmptySquare;
10225             }
10226           }
10227         }
10228         board[toY][toX] = EmptySquare;
10229       }
10230     }
10231
10232     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10233         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10234     } else
10235     if(promoChar == '+') {
10236         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10237         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10238         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10239           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10240     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10241         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10242         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10243            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10244         board[toY][toX] = newPiece;
10245     }
10246     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10247                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10248         // [HGM] superchess: take promotion piece out of holdings
10249         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10250         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10251             if(!--board[k][BOARD_WIDTH-2])
10252                 board[k][BOARD_WIDTH-1] = EmptySquare;
10253         } else {
10254             if(!--board[BOARD_HEIGHT-1-k][1])
10255                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10256         }
10257     }
10258 }
10259
10260 /* Updates forwardMostMove */
10261 void
10262 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10263 {
10264     int x = toX, y = toY;
10265     char *s = parseList[forwardMostMove];
10266     ChessSquare p = boards[forwardMostMove][toY][toX];
10267 //    forwardMostMove++; // [HGM] bare: moved downstream
10268
10269     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10270     (void) CoordsToAlgebraic(boards[forwardMostMove],
10271                              PosFlags(forwardMostMove),
10272                              fromY, fromX, y, x, promoChar,
10273                              s);
10274     if(killX >= 0 && killY >= 0)
10275         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10276
10277     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10278         int timeLeft; static int lastLoadFlag=0; int king, piece;
10279         piece = boards[forwardMostMove][fromY][fromX];
10280         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10281         if(gameInfo.variant == VariantKnightmate)
10282             king += (int) WhiteUnicorn - (int) WhiteKing;
10283         if(forwardMostMove == 0) {
10284             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10285                 fprintf(serverMoves, "%s;", UserName());
10286             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10287                 fprintf(serverMoves, "%s;", second.tidy);
10288             fprintf(serverMoves, "%s;", first.tidy);
10289             if(gameMode == MachinePlaysWhite)
10290                 fprintf(serverMoves, "%s;", UserName());
10291             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10292                 fprintf(serverMoves, "%s;", second.tidy);
10293         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10294         lastLoadFlag = loadFlag;
10295         // print base move
10296         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10297         // print castling suffix
10298         if( toY == fromY && piece == king ) {
10299             if(toX-fromX > 1)
10300                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10301             if(fromX-toX >1)
10302                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10303         }
10304         // e.p. suffix
10305         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10306              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10307              boards[forwardMostMove][toY][toX] == EmptySquare
10308              && fromX != toX && fromY != toY)
10309                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10310         // promotion suffix
10311         if(promoChar != NULLCHAR) {
10312             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10313                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10314                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10315             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10316         }
10317         if(!loadFlag) {
10318                 char buf[MOVE_LEN*2], *p; int len;
10319             fprintf(serverMoves, "/%d/%d",
10320                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10321             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10322             else                      timeLeft = blackTimeRemaining/1000;
10323             fprintf(serverMoves, "/%d", timeLeft);
10324                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10325                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10326                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10327                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10328             fprintf(serverMoves, "/%s", buf);
10329         }
10330         fflush(serverMoves);
10331     }
10332
10333     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10334         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10335       return;
10336     }
10337     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10338     if (commentList[forwardMostMove+1] != NULL) {
10339         free(commentList[forwardMostMove+1]);
10340         commentList[forwardMostMove+1] = NULL;
10341     }
10342     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10343     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10344     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10345     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10346     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10347     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10348     adjustedClock = FALSE;
10349     gameInfo.result = GameUnfinished;
10350     if (gameInfo.resultDetails != NULL) {
10351         free(gameInfo.resultDetails);
10352         gameInfo.resultDetails = NULL;
10353     }
10354     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10355                               moveList[forwardMostMove - 1]);
10356     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10357       case MT_NONE:
10358       case MT_STALEMATE:
10359       default:
10360         break;
10361       case MT_CHECK:
10362         if(!IS_SHOGI(gameInfo.variant))
10363             strcat(parseList[forwardMostMove - 1], "+");
10364         break;
10365       case MT_CHECKMATE:
10366       case MT_STAINMATE:
10367         strcat(parseList[forwardMostMove - 1], "#");
10368         break;
10369     }
10370 }
10371
10372 /* Updates currentMove if not pausing */
10373 void
10374 ShowMove (int fromX, int fromY, int toX, int toY)
10375 {
10376     int instant = (gameMode == PlayFromGameFile) ?
10377         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10378     if(appData.noGUI) return;
10379     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10380         if (!instant) {
10381             if (forwardMostMove == currentMove + 1) {
10382                 AnimateMove(boards[forwardMostMove - 1],
10383                             fromX, fromY, toX, toY);
10384             }
10385         }
10386         currentMove = forwardMostMove;
10387     }
10388
10389     killX = killY = -1; // [HGM] lion: used up
10390
10391     if (instant) return;
10392
10393     DisplayMove(currentMove - 1);
10394     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10395             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10396                 SetHighlights(fromX, fromY, toX, toY);
10397             }
10398     }
10399     DrawPosition(FALSE, boards[currentMove]);
10400     DisplayBothClocks();
10401     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10402 }
10403
10404 void
10405 SendEgtPath (ChessProgramState *cps)
10406 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10407         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10408
10409         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10410
10411         while(*p) {
10412             char c, *q = name+1, *r, *s;
10413
10414             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10415             while(*p && *p != ',') *q++ = *p++;
10416             *q++ = ':'; *q = 0;
10417             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10418                 strcmp(name, ",nalimov:") == 0 ) {
10419                 // take nalimov path from the menu-changeable option first, if it is defined
10420               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10421                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10422             } else
10423             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10424                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10425                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10426                 s = r = StrStr(s, ":") + 1; // beginning of path info
10427                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10428                 c = *r; *r = 0;             // temporarily null-terminate path info
10429                     *--q = 0;               // strip of trailig ':' from name
10430                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10431                 *r = c;
10432                 SendToProgram(buf,cps);     // send egtbpath command for this format
10433             }
10434             if(*p == ',') p++; // read away comma to position for next format name
10435         }
10436 }
10437
10438 static int
10439 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10440 {
10441       int width = 8, height = 8, holdings = 0;             // most common sizes
10442       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10443       // correct the deviations default for each variant
10444       if( v == VariantXiangqi ) width = 9,  height = 10;
10445       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10446       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10447       if( v == VariantCapablanca || v == VariantCapaRandom ||
10448           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10449                                 width = 10;
10450       if( v == VariantCourier ) width = 12;
10451       if( v == VariantSuper )                            holdings = 8;
10452       if( v == VariantGreat )   width = 10,              holdings = 8;
10453       if( v == VariantSChess )                           holdings = 7;
10454       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10455       if( v == VariantChuChess) width = 10, height = 10;
10456       if( v == VariantChu )     width = 12, height = 12;
10457       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10458              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10459              holdingsSize >= 0 && holdingsSize != holdings;
10460 }
10461
10462 char variantError[MSG_SIZ];
10463
10464 char *
10465 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10466 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10467       char *p, *variant = VariantName(v);
10468       static char b[MSG_SIZ];
10469       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10470            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10471                                                holdingsSize, variant); // cook up sized variant name
10472            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10473            if(StrStr(list, b) == NULL) {
10474                // specific sized variant not known, check if general sizing allowed
10475                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10476                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10477                             boardWidth, boardHeight, holdingsSize, engine);
10478                    return NULL;
10479                }
10480                /* [HGM] here we really should compare with the maximum supported board size */
10481            }
10482       } else snprintf(b, MSG_SIZ,"%s", variant);
10483       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10484       p = StrStr(list, b);
10485       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10486       if(p == NULL) {
10487           // occurs not at all in list, or only as sub-string
10488           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10489           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10490               int l = strlen(variantError);
10491               char *q;
10492               while(p != list && p[-1] != ',') p--;
10493               q = strchr(p, ',');
10494               if(q) *q = NULLCHAR;
10495               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10496               if(q) *q= ',';
10497           }
10498           return NULL;
10499       }
10500       return b;
10501 }
10502
10503 void
10504 InitChessProgram (ChessProgramState *cps, int setup)
10505 /* setup needed to setup FRC opening position */
10506 {
10507     char buf[MSG_SIZ], *b;
10508     if (appData.noChessProgram) return;
10509     hintRequested = FALSE;
10510     bookRequested = FALSE;
10511
10512     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10513     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10514     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10515     if(cps->memSize) { /* [HGM] memory */
10516       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10517         SendToProgram(buf, cps);
10518     }
10519     SendEgtPath(cps); /* [HGM] EGT */
10520     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10521       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10522         SendToProgram(buf, cps);
10523     }
10524
10525     setboardSpoiledMachineBlack = FALSE;
10526     SendToProgram(cps->initString, cps);
10527     if (gameInfo.variant != VariantNormal &&
10528         gameInfo.variant != VariantLoadable
10529         /* [HGM] also send variant if board size non-standard */
10530         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10531
10532       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10533                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10534       if (b == NULL) {
10535         VariantClass v;
10536         char c, *q = cps->variants, *p = strchr(q, ',');
10537         if(p) *p = NULLCHAR;
10538         v = StringToVariant(q);
10539         DisplayError(variantError, 0);
10540         if(v != VariantUnknown && cps == &first) {
10541             int w, h, s;
10542             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10543                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10544             ASSIGN(appData.variant, q);
10545             Reset(TRUE, FALSE);
10546         }
10547         if(p) *p = ',';
10548         return;
10549       }
10550
10551       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10552       SendToProgram(buf, cps);
10553     }
10554     currentlyInitializedVariant = gameInfo.variant;
10555
10556     /* [HGM] send opening position in FRC to first engine */
10557     if(setup) {
10558           SendToProgram("force\n", cps);
10559           SendBoard(cps, 0);
10560           /* engine is now in force mode! Set flag to wake it up after first move. */
10561           setboardSpoiledMachineBlack = 1;
10562     }
10563
10564     if (cps->sendICS) {
10565       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10566       SendToProgram(buf, cps);
10567     }
10568     cps->maybeThinking = FALSE;
10569     cps->offeredDraw = 0;
10570     if (!appData.icsActive) {
10571         SendTimeControl(cps, movesPerSession, timeControl,
10572                         timeIncrement, appData.searchDepth,
10573                         searchTime);
10574     }
10575     if (appData.showThinking
10576         // [HGM] thinking: four options require thinking output to be sent
10577         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10578                                 ) {
10579         SendToProgram("post\n", cps);
10580     }
10581     SendToProgram("hard\n", cps);
10582     if (!appData.ponderNextMove) {
10583         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10584            it without being sure what state we are in first.  "hard"
10585            is not a toggle, so that one is OK.
10586          */
10587         SendToProgram("easy\n", cps);
10588     }
10589     if (cps->usePing) {
10590       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10591       SendToProgram(buf, cps);
10592     }
10593     cps->initDone = TRUE;
10594     ClearEngineOutputPane(cps == &second);
10595 }
10596
10597
10598 void
10599 ResendOptions (ChessProgramState *cps)
10600 { // send the stored value of the options
10601   int i;
10602   char buf[MSG_SIZ];
10603   Option *opt = cps->option;
10604   for(i=0; i<cps->nrOptions; i++, opt++) {
10605       switch(opt->type) {
10606         case Spin:
10607         case Slider:
10608         case CheckBox:
10609             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10610           break;
10611         case ComboBox:
10612           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10613           break;
10614         default:
10615             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10616           break;
10617         case Button:
10618         case SaveButton:
10619           continue;
10620       }
10621       SendToProgram(buf, cps);
10622   }
10623 }
10624
10625 void
10626 StartChessProgram (ChessProgramState *cps)
10627 {
10628     char buf[MSG_SIZ];
10629     int err;
10630
10631     if (appData.noChessProgram) return;
10632     cps->initDone = FALSE;
10633
10634     if (strcmp(cps->host, "localhost") == 0) {
10635         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10636     } else if (*appData.remoteShell == NULLCHAR) {
10637         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10638     } else {
10639         if (*appData.remoteUser == NULLCHAR) {
10640           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10641                     cps->program);
10642         } else {
10643           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10644                     cps->host, appData.remoteUser, cps->program);
10645         }
10646         err = StartChildProcess(buf, "", &cps->pr);
10647     }
10648
10649     if (err != 0) {
10650       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10651         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10652         if(cps != &first) return;
10653         appData.noChessProgram = TRUE;
10654         ThawUI();
10655         SetNCPMode();
10656 //      DisplayFatalError(buf, err, 1);
10657 //      cps->pr = NoProc;
10658 //      cps->isr = NULL;
10659         return;
10660     }
10661
10662     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10663     if (cps->protocolVersion > 1) {
10664       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10665       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10666         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10667         cps->comboCnt = 0;  //                and values of combo boxes
10668       }
10669       SendToProgram(buf, cps);
10670       if(cps->reload) ResendOptions(cps);
10671     } else {
10672       SendToProgram("xboard\n", cps);
10673     }
10674 }
10675
10676 void
10677 TwoMachinesEventIfReady P((void))
10678 {
10679   static int curMess = 0;
10680   if (first.lastPing != first.lastPong) {
10681     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10682     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10683     return;
10684   }
10685   if (second.lastPing != second.lastPong) {
10686     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10687     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10688     return;
10689   }
10690   DisplayMessage("", ""); curMess = 0;
10691   TwoMachinesEvent();
10692 }
10693
10694 char *
10695 MakeName (char *template)
10696 {
10697     time_t clock;
10698     struct tm *tm;
10699     static char buf[MSG_SIZ];
10700     char *p = buf;
10701     int i;
10702
10703     clock = time((time_t *)NULL);
10704     tm = localtime(&clock);
10705
10706     while(*p++ = *template++) if(p[-1] == '%') {
10707         switch(*template++) {
10708           case 0:   *p = 0; return buf;
10709           case 'Y': i = tm->tm_year+1900; break;
10710           case 'y': i = tm->tm_year-100; break;
10711           case 'M': i = tm->tm_mon+1; break;
10712           case 'd': i = tm->tm_mday; break;
10713           case 'h': i = tm->tm_hour; break;
10714           case 'm': i = tm->tm_min; break;
10715           case 's': i = tm->tm_sec; break;
10716           default:  i = 0;
10717         }
10718         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10719     }
10720     return buf;
10721 }
10722
10723 int
10724 CountPlayers (char *p)
10725 {
10726     int n = 0;
10727     while(p = strchr(p, '\n')) p++, n++; // count participants
10728     return n;
10729 }
10730
10731 FILE *
10732 WriteTourneyFile (char *results, FILE *f)
10733 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10734     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10735     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10736         // create a file with tournament description
10737         fprintf(f, "-participants {%s}\n", appData.participants);
10738         fprintf(f, "-seedBase %d\n", appData.seedBase);
10739         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10740         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10741         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10742         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10743         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10744         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10745         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10746         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10747         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10748         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10749         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10750         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10751         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10752         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10753         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10754         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10755         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10756         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10757         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10758         fprintf(f, "-smpCores %d\n", appData.smpCores);
10759         if(searchTime > 0)
10760                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10761         else {
10762                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10763                 fprintf(f, "-tc %s\n", appData.timeControl);
10764                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10765         }
10766         fprintf(f, "-results \"%s\"\n", results);
10767     }
10768     return f;
10769 }
10770
10771 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10772
10773 void
10774 Substitute (char *participants, int expunge)
10775 {
10776     int i, changed, changes=0, nPlayers=0;
10777     char *p, *q, *r, buf[MSG_SIZ];
10778     if(participants == NULL) return;
10779     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10780     r = p = participants; q = appData.participants;
10781     while(*p && *p == *q) {
10782         if(*p == '\n') r = p+1, nPlayers++;
10783         p++; q++;
10784     }
10785     if(*p) { // difference
10786         while(*p && *p++ != '\n');
10787         while(*q && *q++ != '\n');
10788       changed = nPlayers;
10789         changes = 1 + (strcmp(p, q) != 0);
10790     }
10791     if(changes == 1) { // a single engine mnemonic was changed
10792         q = r; while(*q) nPlayers += (*q++ == '\n');
10793         p = buf; while(*r && (*p = *r++) != '\n') p++;
10794         *p = NULLCHAR;
10795         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10796         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10797         if(mnemonic[i]) { // The substitute is valid
10798             FILE *f;
10799             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10800                 flock(fileno(f), LOCK_EX);
10801                 ParseArgsFromFile(f);
10802                 fseek(f, 0, SEEK_SET);
10803                 FREE(appData.participants); appData.participants = participants;
10804                 if(expunge) { // erase results of replaced engine
10805                     int len = strlen(appData.results), w, b, dummy;
10806                     for(i=0; i<len; i++) {
10807                         Pairing(i, nPlayers, &w, &b, &dummy);
10808                         if((w == changed || b == changed) && appData.results[i] == '*') {
10809                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10810                             fclose(f);
10811                             return;
10812                         }
10813                     }
10814                     for(i=0; i<len; i++) {
10815                         Pairing(i, nPlayers, &w, &b, &dummy);
10816                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10817                     }
10818                 }
10819                 WriteTourneyFile(appData.results, f);
10820                 fclose(f); // release lock
10821                 return;
10822             }
10823         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10824     }
10825     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10826     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10827     free(participants);
10828     return;
10829 }
10830
10831 int
10832 CheckPlayers (char *participants)
10833 {
10834         int i;
10835         char buf[MSG_SIZ], *p;
10836         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10837         while(p = strchr(participants, '\n')) {
10838             *p = NULLCHAR;
10839             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10840             if(!mnemonic[i]) {
10841                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10842                 *p = '\n';
10843                 DisplayError(buf, 0);
10844                 return 1;
10845             }
10846             *p = '\n';
10847             participants = p + 1;
10848         }
10849         return 0;
10850 }
10851
10852 int
10853 CreateTourney (char *name)
10854 {
10855         FILE *f;
10856         if(matchMode && strcmp(name, appData.tourneyFile)) {
10857              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10858         }
10859         if(name[0] == NULLCHAR) {
10860             if(appData.participants[0])
10861                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10862             return 0;
10863         }
10864         f = fopen(name, "r");
10865         if(f) { // file exists
10866             ASSIGN(appData.tourneyFile, name);
10867             ParseArgsFromFile(f); // parse it
10868         } else {
10869             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10870             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10871                 DisplayError(_("Not enough participants"), 0);
10872                 return 0;
10873             }
10874             if(CheckPlayers(appData.participants)) return 0;
10875             ASSIGN(appData.tourneyFile, name);
10876             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10877             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10878         }
10879         fclose(f);
10880         appData.noChessProgram = FALSE;
10881         appData.clockMode = TRUE;
10882         SetGNUMode();
10883         return 1;
10884 }
10885
10886 int
10887 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10888 {
10889     char buf[MSG_SIZ], *p, *q;
10890     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10891     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10892     skip = !all && group[0]; // if group requested, we start in skip mode
10893     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10894         p = names; q = buf; header = 0;
10895         while(*p && *p != '\n') *q++ = *p++;
10896         *q = 0;
10897         if(*p == '\n') p++;
10898         if(buf[0] == '#') {
10899             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10900             depth++; // we must be entering a new group
10901             if(all) continue; // suppress printing group headers when complete list requested
10902             header = 1;
10903             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10904         }
10905         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10906         if(engineList[i]) free(engineList[i]);
10907         engineList[i] = strdup(buf);
10908         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10909         if(engineMnemonic[i]) free(engineMnemonic[i]);
10910         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10911             strcat(buf, " (");
10912             sscanf(q + 8, "%s", buf + strlen(buf));
10913             strcat(buf, ")");
10914         }
10915         engineMnemonic[i] = strdup(buf);
10916         i++;
10917     }
10918     engineList[i] = engineMnemonic[i] = NULL;
10919     return i;
10920 }
10921
10922 // following implemented as macro to avoid type limitations
10923 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10924
10925 void
10926 SwapEngines (int n)
10927 {   // swap settings for first engine and other engine (so far only some selected options)
10928     int h;
10929     char *p;
10930     if(n == 0) return;
10931     SWAP(directory, p)
10932     SWAP(chessProgram, p)
10933     SWAP(isUCI, h)
10934     SWAP(hasOwnBookUCI, h)
10935     SWAP(protocolVersion, h)
10936     SWAP(reuse, h)
10937     SWAP(scoreIsAbsolute, h)
10938     SWAP(timeOdds, h)
10939     SWAP(logo, p)
10940     SWAP(pgnName, p)
10941     SWAP(pvSAN, h)
10942     SWAP(engOptions, p)
10943     SWAP(engInitString, p)
10944     SWAP(computerString, p)
10945     SWAP(features, p)
10946     SWAP(fenOverride, p)
10947     SWAP(NPS, h)
10948     SWAP(accumulateTC, h)
10949     SWAP(drawDepth, h)
10950     SWAP(host, p)
10951     SWAP(pseudo, h)
10952 }
10953
10954 int
10955 GetEngineLine (char *s, int n)
10956 {
10957     int i;
10958     char buf[MSG_SIZ];
10959     extern char *icsNames;
10960     if(!s || !*s) return 0;
10961     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10962     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10963     if(!mnemonic[i]) return 0;
10964     if(n == 11) return 1; // just testing if there was a match
10965     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10966     if(n == 1) SwapEngines(n);
10967     ParseArgsFromString(buf);
10968     if(n == 1) SwapEngines(n);
10969     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10970         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10971         ParseArgsFromString(buf);
10972     }
10973     return 1;
10974 }
10975
10976 int
10977 SetPlayer (int player, char *p)
10978 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10979     int i;
10980     char buf[MSG_SIZ], *engineName;
10981     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10982     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10983     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10984     if(mnemonic[i]) {
10985         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10986         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10987         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10988         ParseArgsFromString(buf);
10989     } else { // no engine with this nickname is installed!
10990         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10991         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10992         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10993         ModeHighlight();
10994         DisplayError(buf, 0);
10995         return 0;
10996     }
10997     free(engineName);
10998     return i;
10999 }
11000
11001 char *recentEngines;
11002
11003 void
11004 RecentEngineEvent (int nr)
11005 {
11006     int n;
11007 //    SwapEngines(1); // bump first to second
11008 //    ReplaceEngine(&second, 1); // and load it there
11009     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11010     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11011     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11012         ReplaceEngine(&first, 0);
11013         FloatToFront(&appData.recentEngineList, command[n]);
11014     }
11015 }
11016
11017 int
11018 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11019 {   // determine players from game number
11020     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11021
11022     if(appData.tourneyType == 0) {
11023         roundsPerCycle = (nPlayers - 1) | 1;
11024         pairingsPerRound = nPlayers / 2;
11025     } else if(appData.tourneyType > 0) {
11026         roundsPerCycle = nPlayers - appData.tourneyType;
11027         pairingsPerRound = appData.tourneyType;
11028     }
11029     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11030     gamesPerCycle = gamesPerRound * roundsPerCycle;
11031     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11032     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11033     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11034     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11035     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11036     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11037
11038     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11039     if(appData.roundSync) *syncInterval = gamesPerRound;
11040
11041     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11042
11043     if(appData.tourneyType == 0) {
11044         if(curPairing == (nPlayers-1)/2 ) {
11045             *whitePlayer = curRound;
11046             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11047         } else {
11048             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11049             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11050             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11051             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11052         }
11053     } else if(appData.tourneyType > 1) {
11054         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11055         *whitePlayer = curRound + appData.tourneyType;
11056     } else if(appData.tourneyType > 0) {
11057         *whitePlayer = curPairing;
11058         *blackPlayer = curRound + appData.tourneyType;
11059     }
11060
11061     // take care of white/black alternation per round.
11062     // For cycles and games this is already taken care of by default, derived from matchGame!
11063     return curRound & 1;
11064 }
11065
11066 int
11067 NextTourneyGame (int nr, int *swapColors)
11068 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11069     char *p, *q;
11070     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11071     FILE *tf;
11072     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11073     tf = fopen(appData.tourneyFile, "r");
11074     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11075     ParseArgsFromFile(tf); fclose(tf);
11076     InitTimeControls(); // TC might be altered from tourney file
11077
11078     nPlayers = CountPlayers(appData.participants); // count participants
11079     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11080     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11081
11082     if(syncInterval) {
11083         p = q = appData.results;
11084         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11085         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11086             DisplayMessage(_("Waiting for other game(s)"),"");
11087             waitingForGame = TRUE;
11088             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11089             return 0;
11090         }
11091         waitingForGame = FALSE;
11092     }
11093
11094     if(appData.tourneyType < 0) {
11095         if(nr>=0 && !pairingReceived) {
11096             char buf[1<<16];
11097             if(pairing.pr == NoProc) {
11098                 if(!appData.pairingEngine[0]) {
11099                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11100                     return 0;
11101                 }
11102                 StartChessProgram(&pairing); // starts the pairing engine
11103             }
11104             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11105             SendToProgram(buf, &pairing);
11106             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11107             SendToProgram(buf, &pairing);
11108             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11109         }
11110         pairingReceived = 0;                              // ... so we continue here
11111         *swapColors = 0;
11112         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11113         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11114         matchGame = 1; roundNr = nr / syncInterval + 1;
11115     }
11116
11117     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11118
11119     // redefine engines, engine dir, etc.
11120     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11121     if(first.pr == NoProc) {
11122       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11123       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11124     }
11125     if(second.pr == NoProc) {
11126       SwapEngines(1);
11127       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11128       SwapEngines(1);         // and make that valid for second engine by swapping
11129       InitEngine(&second, 1);
11130     }
11131     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11132     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11133     return OK;
11134 }
11135
11136 void
11137 NextMatchGame ()
11138 {   // performs game initialization that does not invoke engines, and then tries to start the game
11139     int res, firstWhite, swapColors = 0;
11140     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11141     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
11142         char buf[MSG_SIZ];
11143         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11144         if(strcmp(buf, currentDebugFile)) { // name has changed
11145             FILE *f = fopen(buf, "w");
11146             if(f) { // if opening the new file failed, just keep using the old one
11147                 ASSIGN(currentDebugFile, buf);
11148                 fclose(debugFP);
11149                 debugFP = f;
11150             }
11151             if(appData.serverFileName) {
11152                 if(serverFP) fclose(serverFP);
11153                 serverFP = fopen(appData.serverFileName, "w");
11154                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11155                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11156             }
11157         }
11158     }
11159     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11160     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11161     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11162     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11163     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11164     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11165     Reset(FALSE, first.pr != NoProc);
11166     res = LoadGameOrPosition(matchGame); // setup game
11167     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11168     if(!res) return; // abort when bad game/pos file
11169     TwoMachinesEvent();
11170 }
11171
11172 void
11173 UserAdjudicationEvent (int result)
11174 {
11175     ChessMove gameResult = GameIsDrawn;
11176
11177     if( result > 0 ) {
11178         gameResult = WhiteWins;
11179     }
11180     else if( result < 0 ) {
11181         gameResult = BlackWins;
11182     }
11183
11184     if( gameMode == TwoMachinesPlay ) {
11185         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11186     }
11187 }
11188
11189
11190 // [HGM] save: calculate checksum of game to make games easily identifiable
11191 int
11192 StringCheckSum (char *s)
11193 {
11194         int i = 0;
11195         if(s==NULL) return 0;
11196         while(*s) i = i*259 + *s++;
11197         return i;
11198 }
11199
11200 int
11201 GameCheckSum ()
11202 {
11203         int i, sum=0;
11204         for(i=backwardMostMove; i<forwardMostMove; i++) {
11205                 sum += pvInfoList[i].depth;
11206                 sum += StringCheckSum(parseList[i]);
11207                 sum += StringCheckSum(commentList[i]);
11208                 sum *= 261;
11209         }
11210         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11211         return sum + StringCheckSum(commentList[i]);
11212 } // end of save patch
11213
11214 void
11215 GameEnds (ChessMove result, char *resultDetails, int whosays)
11216 {
11217     GameMode nextGameMode;
11218     int isIcsGame;
11219     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11220
11221     if(endingGame) return; /* [HGM] crash: forbid recursion */
11222     endingGame = 1;
11223     if(twoBoards) { // [HGM] dual: switch back to one board
11224         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11225         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11226     }
11227     if (appData.debugMode) {
11228       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11229               result, resultDetails ? resultDetails : "(null)", whosays);
11230     }
11231
11232     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11233
11234     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11235
11236     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11237         /* If we are playing on ICS, the server decides when the
11238            game is over, but the engine can offer to draw, claim
11239            a draw, or resign.
11240          */
11241 #if ZIPPY
11242         if (appData.zippyPlay && first.initDone) {
11243             if (result == GameIsDrawn) {
11244                 /* In case draw still needs to be claimed */
11245                 SendToICS(ics_prefix);
11246                 SendToICS("draw\n");
11247             } else if (StrCaseStr(resultDetails, "resign")) {
11248                 SendToICS(ics_prefix);
11249                 SendToICS("resign\n");
11250             }
11251         }
11252 #endif
11253         endingGame = 0; /* [HGM] crash */
11254         return;
11255     }
11256
11257     /* If we're loading the game from a file, stop */
11258     if (whosays == GE_FILE) {
11259       (void) StopLoadGameTimer();
11260       gameFileFP = NULL;
11261     }
11262
11263     /* Cancel draw offers */
11264     first.offeredDraw = second.offeredDraw = 0;
11265
11266     /* If this is an ICS game, only ICS can really say it's done;
11267        if not, anyone can. */
11268     isIcsGame = (gameMode == IcsPlayingWhite ||
11269                  gameMode == IcsPlayingBlack ||
11270                  gameMode == IcsObserving    ||
11271                  gameMode == IcsExamining);
11272
11273     if (!isIcsGame || whosays == GE_ICS) {
11274         /* OK -- not an ICS game, or ICS said it was done */
11275         StopClocks();
11276         if (!isIcsGame && !appData.noChessProgram)
11277           SetUserThinkingEnables();
11278
11279         /* [HGM] if a machine claims the game end we verify this claim */
11280         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11281             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11282                 char claimer;
11283                 ChessMove trueResult = (ChessMove) -1;
11284
11285                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11286                                             first.twoMachinesColor[0] :
11287                                             second.twoMachinesColor[0] ;
11288
11289                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11290                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11291                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11292                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11293                 } else
11294                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11295                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11296                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11297                 } else
11298                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11299                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11300                 }
11301
11302                 // now verify win claims, but not in drop games, as we don't understand those yet
11303                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11304                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11305                     (result == WhiteWins && claimer == 'w' ||
11306                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11307                       if (appData.debugMode) {
11308                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11309                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11310                       }
11311                       if(result != trueResult) {
11312                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11313                               result = claimer == 'w' ? BlackWins : WhiteWins;
11314                               resultDetails = buf;
11315                       }
11316                 } else
11317                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11318                     && (forwardMostMove <= backwardMostMove ||
11319                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11320                         (claimer=='b')==(forwardMostMove&1))
11321                                                                                   ) {
11322                       /* [HGM] verify: draws that were not flagged are false claims */
11323                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11324                       result = claimer == 'w' ? BlackWins : WhiteWins;
11325                       resultDetails = buf;
11326                 }
11327                 /* (Claiming a loss is accepted no questions asked!) */
11328             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11329                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11330                 result = GameUnfinished;
11331                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11332             }
11333             /* [HGM] bare: don't allow bare King to win */
11334             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11335                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11336                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11337                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11338                && result != GameIsDrawn)
11339             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11340                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11341                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11342                         if(p >= 0 && p <= (int)WhiteKing) k++;
11343                 }
11344                 if (appData.debugMode) {
11345                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11346                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11347                 }
11348                 if(k <= 1) {
11349                         result = GameIsDrawn;
11350                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11351                         resultDetails = buf;
11352                 }
11353             }
11354         }
11355
11356
11357         if(serverMoves != NULL && !loadFlag) { char c = '=';
11358             if(result==WhiteWins) c = '+';
11359             if(result==BlackWins) c = '-';
11360             if(resultDetails != NULL)
11361                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11362         }
11363         if (resultDetails != NULL) {
11364             gameInfo.result = result;
11365             gameInfo.resultDetails = StrSave(resultDetails);
11366
11367             /* display last move only if game was not loaded from file */
11368             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11369                 DisplayMove(currentMove - 1);
11370
11371             if (forwardMostMove != 0) {
11372                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11373                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11374                                                                 ) {
11375                     if (*appData.saveGameFile != NULLCHAR) {
11376                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11377                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11378                         else
11379                         SaveGameToFile(appData.saveGameFile, TRUE);
11380                     } else if (appData.autoSaveGames) {
11381                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11382                     }
11383                     if (*appData.savePositionFile != NULLCHAR) {
11384                         SavePositionToFile(appData.savePositionFile);
11385                     }
11386                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11387                 }
11388             }
11389
11390             /* Tell program how game ended in case it is learning */
11391             /* [HGM] Moved this to after saving the PGN, just in case */
11392             /* engine died and we got here through time loss. In that */
11393             /* case we will get a fatal error writing the pipe, which */
11394             /* would otherwise lose us the PGN.                       */
11395             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11396             /* output during GameEnds should never be fatal anymore   */
11397             if (gameMode == MachinePlaysWhite ||
11398                 gameMode == MachinePlaysBlack ||
11399                 gameMode == TwoMachinesPlay ||
11400                 gameMode == IcsPlayingWhite ||
11401                 gameMode == IcsPlayingBlack ||
11402                 gameMode == BeginningOfGame) {
11403                 char buf[MSG_SIZ];
11404                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11405                         resultDetails);
11406                 if (first.pr != NoProc) {
11407                     SendToProgram(buf, &first);
11408                 }
11409                 if (second.pr != NoProc &&
11410                     gameMode == TwoMachinesPlay) {
11411                     SendToProgram(buf, &second);
11412                 }
11413             }
11414         }
11415
11416         if (appData.icsActive) {
11417             if (appData.quietPlay &&
11418                 (gameMode == IcsPlayingWhite ||
11419                  gameMode == IcsPlayingBlack)) {
11420                 SendToICS(ics_prefix);
11421                 SendToICS("set shout 1\n");
11422             }
11423             nextGameMode = IcsIdle;
11424             ics_user_moved = FALSE;
11425             /* clean up premove.  It's ugly when the game has ended and the
11426              * premove highlights are still on the board.
11427              */
11428             if (gotPremove) {
11429               gotPremove = FALSE;
11430               ClearPremoveHighlights();
11431               DrawPosition(FALSE, boards[currentMove]);
11432             }
11433             if (whosays == GE_ICS) {
11434                 switch (result) {
11435                 case WhiteWins:
11436                     if (gameMode == IcsPlayingWhite)
11437                         PlayIcsWinSound();
11438                     else if(gameMode == IcsPlayingBlack)
11439                         PlayIcsLossSound();
11440                     break;
11441                 case BlackWins:
11442                     if (gameMode == IcsPlayingBlack)
11443                         PlayIcsWinSound();
11444                     else if(gameMode == IcsPlayingWhite)
11445                         PlayIcsLossSound();
11446                     break;
11447                 case GameIsDrawn:
11448                     PlayIcsDrawSound();
11449                     break;
11450                 default:
11451                     PlayIcsUnfinishedSound();
11452                 }
11453             }
11454             if(appData.quitNext) { ExitEvent(0); return; }
11455         } else if (gameMode == EditGame ||
11456                    gameMode == PlayFromGameFile ||
11457                    gameMode == AnalyzeMode ||
11458                    gameMode == AnalyzeFile) {
11459             nextGameMode = gameMode;
11460         } else {
11461             nextGameMode = EndOfGame;
11462         }
11463         pausing = FALSE;
11464         ModeHighlight();
11465     } else {
11466         nextGameMode = gameMode;
11467     }
11468
11469     if (appData.noChessProgram) {
11470         gameMode = nextGameMode;
11471         ModeHighlight();
11472         endingGame = 0; /* [HGM] crash */
11473         return;
11474     }
11475
11476     if (first.reuse) {
11477         /* Put first chess program into idle state */
11478         if (first.pr != NoProc &&
11479             (gameMode == MachinePlaysWhite ||
11480              gameMode == MachinePlaysBlack ||
11481              gameMode == TwoMachinesPlay ||
11482              gameMode == IcsPlayingWhite ||
11483              gameMode == IcsPlayingBlack ||
11484              gameMode == BeginningOfGame)) {
11485             SendToProgram("force\n", &first);
11486             if (first.usePing) {
11487               char buf[MSG_SIZ];
11488               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11489               SendToProgram(buf, &first);
11490             }
11491         }
11492     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11493         /* Kill off first chess program */
11494         if (first.isr != NULL)
11495           RemoveInputSource(first.isr);
11496         first.isr = NULL;
11497
11498         if (first.pr != NoProc) {
11499             ExitAnalyzeMode();
11500             DoSleep( appData.delayBeforeQuit );
11501             SendToProgram("quit\n", &first);
11502             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11503             first.reload = TRUE;
11504         }
11505         first.pr = NoProc;
11506     }
11507     if (second.reuse) {
11508         /* Put second chess program into idle state */
11509         if (second.pr != NoProc &&
11510             gameMode == TwoMachinesPlay) {
11511             SendToProgram("force\n", &second);
11512             if (second.usePing) {
11513               char buf[MSG_SIZ];
11514               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11515               SendToProgram(buf, &second);
11516             }
11517         }
11518     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11519         /* Kill off second chess program */
11520         if (second.isr != NULL)
11521           RemoveInputSource(second.isr);
11522         second.isr = NULL;
11523
11524         if (second.pr != NoProc) {
11525             DoSleep( appData.delayBeforeQuit );
11526             SendToProgram("quit\n", &second);
11527             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11528             second.reload = TRUE;
11529         }
11530         second.pr = NoProc;
11531     }
11532
11533     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11534         char resChar = '=';
11535         switch (result) {
11536         case WhiteWins:
11537           resChar = '+';
11538           if (first.twoMachinesColor[0] == 'w') {
11539             first.matchWins++;
11540           } else {
11541             second.matchWins++;
11542           }
11543           break;
11544         case BlackWins:
11545           resChar = '-';
11546           if (first.twoMachinesColor[0] == 'b') {
11547             first.matchWins++;
11548           } else {
11549             second.matchWins++;
11550           }
11551           break;
11552         case GameUnfinished:
11553           resChar = ' ';
11554         default:
11555           break;
11556         }
11557
11558         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11559         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11560             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11561             ReserveGame(nextGame, resChar); // sets nextGame
11562             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11563             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11564         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11565
11566         if (nextGame <= appData.matchGames && !abortMatch) {
11567             gameMode = nextGameMode;
11568             matchGame = nextGame; // this will be overruled in tourney mode!
11569             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11570             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11571             endingGame = 0; /* [HGM] crash */
11572             return;
11573         } else {
11574             gameMode = nextGameMode;
11575             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11576                      first.tidy, second.tidy,
11577                      first.matchWins, second.matchWins,
11578                      appData.matchGames - (first.matchWins + second.matchWins));
11579             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11580             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11581             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11582             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11583                 first.twoMachinesColor = "black\n";
11584                 second.twoMachinesColor = "white\n";
11585             } else {
11586                 first.twoMachinesColor = "white\n";
11587                 second.twoMachinesColor = "black\n";
11588             }
11589         }
11590     }
11591     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11592         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11593       ExitAnalyzeMode();
11594     gameMode = nextGameMode;
11595     ModeHighlight();
11596     endingGame = 0;  /* [HGM] crash */
11597     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11598         if(matchMode == TRUE) { // match through command line: exit with or without popup
11599             if(ranking) {
11600                 ToNrEvent(forwardMostMove);
11601                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11602                 else ExitEvent(0);
11603             } else DisplayFatalError(buf, 0, 0);
11604         } else { // match through menu; just stop, with or without popup
11605             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11606             ModeHighlight();
11607             if(ranking){
11608                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11609             } else DisplayNote(buf);
11610       }
11611       if(ranking) free(ranking);
11612     }
11613 }
11614
11615 /* Assumes program was just initialized (initString sent).
11616    Leaves program in force mode. */
11617 void
11618 FeedMovesToProgram (ChessProgramState *cps, int upto)
11619 {
11620     int i;
11621
11622     if (appData.debugMode)
11623       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11624               startedFromSetupPosition ? "position and " : "",
11625               backwardMostMove, upto, cps->which);
11626     if(currentlyInitializedVariant != gameInfo.variant) {
11627       char buf[MSG_SIZ];
11628         // [HGM] variantswitch: make engine aware of new variant
11629         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11630                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11631                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11632         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11633         SendToProgram(buf, cps);
11634         currentlyInitializedVariant = gameInfo.variant;
11635     }
11636     SendToProgram("force\n", cps);
11637     if (startedFromSetupPosition) {
11638         SendBoard(cps, backwardMostMove);
11639     if (appData.debugMode) {
11640         fprintf(debugFP, "feedMoves\n");
11641     }
11642     }
11643     for (i = backwardMostMove; i < upto; i++) {
11644         SendMoveToProgram(i, cps);
11645     }
11646 }
11647
11648
11649 int
11650 ResurrectChessProgram ()
11651 {
11652      /* The chess program may have exited.
11653         If so, restart it and feed it all the moves made so far. */
11654     static int doInit = 0;
11655
11656     if (appData.noChessProgram) return 1;
11657
11658     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11659         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11660         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11661         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11662     } else {
11663         if (first.pr != NoProc) return 1;
11664         StartChessProgram(&first);
11665     }
11666     InitChessProgram(&first, FALSE);
11667     FeedMovesToProgram(&first, currentMove);
11668
11669     if (!first.sendTime) {
11670         /* can't tell gnuchess what its clock should read,
11671            so we bow to its notion. */
11672         ResetClocks();
11673         timeRemaining[0][currentMove] = whiteTimeRemaining;
11674         timeRemaining[1][currentMove] = blackTimeRemaining;
11675     }
11676
11677     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11678                 appData.icsEngineAnalyze) && first.analysisSupport) {
11679       SendToProgram("analyze\n", &first);
11680       first.analyzing = TRUE;
11681     }
11682     return 1;
11683 }
11684
11685 /*
11686  * Button procedures
11687  */
11688 void
11689 Reset (int redraw, int init)
11690 {
11691     int i;
11692
11693     if (appData.debugMode) {
11694         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11695                 redraw, init, gameMode);
11696     }
11697     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11698     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11699     CleanupTail(); // [HGM] vari: delete any stored variations
11700     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11701     pausing = pauseExamInvalid = FALSE;
11702     startedFromSetupPosition = blackPlaysFirst = FALSE;
11703     firstMove = TRUE;
11704     whiteFlag = blackFlag = FALSE;
11705     userOfferedDraw = FALSE;
11706     hintRequested = bookRequested = FALSE;
11707     first.maybeThinking = FALSE;
11708     second.maybeThinking = FALSE;
11709     first.bookSuspend = FALSE; // [HGM] book
11710     second.bookSuspend = FALSE;
11711     thinkOutput[0] = NULLCHAR;
11712     lastHint[0] = NULLCHAR;
11713     ClearGameInfo(&gameInfo);
11714     gameInfo.variant = StringToVariant(appData.variant);
11715     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11716     ics_user_moved = ics_clock_paused = FALSE;
11717     ics_getting_history = H_FALSE;
11718     ics_gamenum = -1;
11719     white_holding[0] = black_holding[0] = NULLCHAR;
11720     ClearProgramStats();
11721     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11722
11723     ResetFrontEnd();
11724     ClearHighlights();
11725     flipView = appData.flipView;
11726     ClearPremoveHighlights();
11727     gotPremove = FALSE;
11728     alarmSounded = FALSE;
11729     killX = killY = -1; // [HGM] lion
11730
11731     GameEnds(EndOfFile, NULL, GE_PLAYER);
11732     if(appData.serverMovesName != NULL) {
11733         /* [HGM] prepare to make moves file for broadcasting */
11734         clock_t t = clock();
11735         if(serverMoves != NULL) fclose(serverMoves);
11736         serverMoves = fopen(appData.serverMovesName, "r");
11737         if(serverMoves != NULL) {
11738             fclose(serverMoves);
11739             /* delay 15 sec before overwriting, so all clients can see end */
11740             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11741         }
11742         serverMoves = fopen(appData.serverMovesName, "w");
11743     }
11744
11745     ExitAnalyzeMode();
11746     gameMode = BeginningOfGame;
11747     ModeHighlight();
11748     if(appData.icsActive) gameInfo.variant = VariantNormal;
11749     currentMove = forwardMostMove = backwardMostMove = 0;
11750     MarkTargetSquares(1);
11751     InitPosition(redraw);
11752     for (i = 0; i < MAX_MOVES; i++) {
11753         if (commentList[i] != NULL) {
11754             free(commentList[i]);
11755             commentList[i] = NULL;
11756         }
11757     }
11758     ResetClocks();
11759     timeRemaining[0][0] = whiteTimeRemaining;
11760     timeRemaining[1][0] = blackTimeRemaining;
11761
11762     if (first.pr == NoProc) {
11763         StartChessProgram(&first);
11764     }
11765     if (init) {
11766             InitChessProgram(&first, startedFromSetupPosition);
11767     }
11768     DisplayTitle("");
11769     DisplayMessage("", "");
11770     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11771     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11772     ClearMap();        // [HGM] exclude: invalidate map
11773 }
11774
11775 void
11776 AutoPlayGameLoop ()
11777 {
11778     for (;;) {
11779         if (!AutoPlayOneMove())
11780           return;
11781         if (matchMode || appData.timeDelay == 0)
11782           continue;
11783         if (appData.timeDelay < 0)
11784           return;
11785         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11786         break;
11787     }
11788 }
11789
11790 void
11791 AnalyzeNextGame()
11792 {
11793     ReloadGame(1); // next game
11794 }
11795
11796 int
11797 AutoPlayOneMove ()
11798 {
11799     int fromX, fromY, toX, toY;
11800
11801     if (appData.debugMode) {
11802       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11803     }
11804
11805     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11806       return FALSE;
11807
11808     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11809       pvInfoList[currentMove].depth = programStats.depth;
11810       pvInfoList[currentMove].score = programStats.score;
11811       pvInfoList[currentMove].time  = 0;
11812       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11813       else { // append analysis of final position as comment
11814         char buf[MSG_SIZ];
11815         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11816         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11817       }
11818       programStats.depth = 0;
11819     }
11820
11821     if (currentMove >= forwardMostMove) {
11822       if(gameMode == AnalyzeFile) {
11823           if(appData.loadGameIndex == -1) {
11824             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11825           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11826           } else {
11827           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11828         }
11829       }
11830 //      gameMode = EndOfGame;
11831 //      ModeHighlight();
11832
11833       /* [AS] Clear current move marker at the end of a game */
11834       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11835
11836       return FALSE;
11837     }
11838
11839     toX = moveList[currentMove][2] - AAA;
11840     toY = moveList[currentMove][3] - ONE;
11841
11842     if (moveList[currentMove][1] == '@') {
11843         if (appData.highlightLastMove) {
11844             SetHighlights(-1, -1, toX, toY);
11845         }
11846     } else {
11847         int viaX = moveList[currentMove][5] - AAA;
11848         int viaY = moveList[currentMove][6] - ONE;
11849         fromX = moveList[currentMove][0] - AAA;
11850         fromY = moveList[currentMove][1] - ONE;
11851
11852         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11853
11854         if(moveList[currentMove][4] == ';') { // multi-leg
11855             ChessSquare piece = boards[currentMove][viaY][viaX];
11856             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11857             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11858             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11859             boards[currentMove][viaY][viaX] = piece;
11860         } else
11861         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11862
11863         if (appData.highlightLastMove) {
11864             SetHighlights(fromX, fromY, toX, toY);
11865         }
11866     }
11867     DisplayMove(currentMove);
11868     SendMoveToProgram(currentMove++, &first);
11869     DisplayBothClocks();
11870     DrawPosition(FALSE, boards[currentMove]);
11871     // [HGM] PV info: always display, routine tests if empty
11872     DisplayComment(currentMove - 1, commentList[currentMove]);
11873     return TRUE;
11874 }
11875
11876
11877 int
11878 LoadGameOneMove (ChessMove readAhead)
11879 {
11880     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11881     char promoChar = NULLCHAR;
11882     ChessMove moveType;
11883     char move[MSG_SIZ];
11884     char *p, *q;
11885
11886     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11887         gameMode != AnalyzeMode && gameMode != Training) {
11888         gameFileFP = NULL;
11889         return FALSE;
11890     }
11891
11892     yyboardindex = forwardMostMove;
11893     if (readAhead != EndOfFile) {
11894       moveType = readAhead;
11895     } else {
11896       if (gameFileFP == NULL)
11897           return FALSE;
11898       moveType = (ChessMove) Myylex();
11899     }
11900
11901     done = FALSE;
11902     switch (moveType) {
11903       case Comment:
11904         if (appData.debugMode)
11905           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11906         p = yy_text;
11907
11908         /* append the comment but don't display it */
11909         AppendComment(currentMove, p, FALSE);
11910         return TRUE;
11911
11912       case WhiteCapturesEnPassant:
11913       case BlackCapturesEnPassant:
11914       case WhitePromotion:
11915       case BlackPromotion:
11916       case WhiteNonPromotion:
11917       case BlackNonPromotion:
11918       case NormalMove:
11919       case FirstLeg:
11920       case WhiteKingSideCastle:
11921       case WhiteQueenSideCastle:
11922       case BlackKingSideCastle:
11923       case BlackQueenSideCastle:
11924       case WhiteKingSideCastleWild:
11925       case WhiteQueenSideCastleWild:
11926       case BlackKingSideCastleWild:
11927       case BlackQueenSideCastleWild:
11928       /* PUSH Fabien */
11929       case WhiteHSideCastleFR:
11930       case WhiteASideCastleFR:
11931       case BlackHSideCastleFR:
11932       case BlackASideCastleFR:
11933       /* POP Fabien */
11934         if (appData.debugMode)
11935           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11936         fromX = currentMoveString[0] - AAA;
11937         fromY = currentMoveString[1] - ONE;
11938         toX = currentMoveString[2] - AAA;
11939         toY = currentMoveString[3] - ONE;
11940         promoChar = currentMoveString[4];
11941         if(promoChar == ';') promoChar = NULLCHAR;
11942         break;
11943
11944       case WhiteDrop:
11945       case BlackDrop:
11946         if (appData.debugMode)
11947           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11948         fromX = moveType == WhiteDrop ?
11949           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11950         (int) CharToPiece(ToLower(currentMoveString[0]));
11951         fromY = DROP_RANK;
11952         toX = currentMoveString[2] - AAA;
11953         toY = currentMoveString[3] - ONE;
11954         break;
11955
11956       case WhiteWins:
11957       case BlackWins:
11958       case GameIsDrawn:
11959       case GameUnfinished:
11960         if (appData.debugMode)
11961           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11962         p = strchr(yy_text, '{');
11963         if (p == NULL) p = strchr(yy_text, '(');
11964         if (p == NULL) {
11965             p = yy_text;
11966             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11967         } else {
11968             q = strchr(p, *p == '{' ? '}' : ')');
11969             if (q != NULL) *q = NULLCHAR;
11970             p++;
11971         }
11972         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11973         GameEnds(moveType, p, GE_FILE);
11974         done = TRUE;
11975         if (cmailMsgLoaded) {
11976             ClearHighlights();
11977             flipView = WhiteOnMove(currentMove);
11978             if (moveType == GameUnfinished) flipView = !flipView;
11979             if (appData.debugMode)
11980               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11981         }
11982         break;
11983
11984       case EndOfFile:
11985         if (appData.debugMode)
11986           fprintf(debugFP, "Parser hit end of file\n");
11987         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11988           case MT_NONE:
11989           case MT_CHECK:
11990             break;
11991           case MT_CHECKMATE:
11992           case MT_STAINMATE:
11993             if (WhiteOnMove(currentMove)) {
11994                 GameEnds(BlackWins, "Black mates", GE_FILE);
11995             } else {
11996                 GameEnds(WhiteWins, "White mates", GE_FILE);
11997             }
11998             break;
11999           case MT_STALEMATE:
12000             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12001             break;
12002         }
12003         done = TRUE;
12004         break;
12005
12006       case MoveNumberOne:
12007         if (lastLoadGameStart == GNUChessGame) {
12008             /* GNUChessGames have numbers, but they aren't move numbers */
12009             if (appData.debugMode)
12010               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12011                       yy_text, (int) moveType);
12012             return LoadGameOneMove(EndOfFile); /* tail recursion */
12013         }
12014         /* else fall thru */
12015
12016       case XBoardGame:
12017       case GNUChessGame:
12018       case PGNTag:
12019         /* Reached start of next game in file */
12020         if (appData.debugMode)
12021           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12022         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12023           case MT_NONE:
12024           case MT_CHECK:
12025             break;
12026           case MT_CHECKMATE:
12027           case MT_STAINMATE:
12028             if (WhiteOnMove(currentMove)) {
12029                 GameEnds(BlackWins, "Black mates", GE_FILE);
12030             } else {
12031                 GameEnds(WhiteWins, "White mates", GE_FILE);
12032             }
12033             break;
12034           case MT_STALEMATE:
12035             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12036             break;
12037         }
12038         done = TRUE;
12039         break;
12040
12041       case PositionDiagram:     /* should not happen; ignore */
12042       case ElapsedTime:         /* ignore */
12043       case NAG:                 /* ignore */
12044         if (appData.debugMode)
12045           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12046                   yy_text, (int) moveType);
12047         return LoadGameOneMove(EndOfFile); /* tail recursion */
12048
12049       case IllegalMove:
12050         if (appData.testLegality) {
12051             if (appData.debugMode)
12052               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12053             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12054                     (forwardMostMove / 2) + 1,
12055                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12056             DisplayError(move, 0);
12057             done = TRUE;
12058         } else {
12059             if (appData.debugMode)
12060               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12061                       yy_text, currentMoveString);
12062             fromX = currentMoveString[0] - AAA;
12063             fromY = currentMoveString[1] - ONE;
12064             toX = currentMoveString[2] - AAA;
12065             toY = currentMoveString[3] - ONE;
12066             promoChar = currentMoveString[4];
12067         }
12068         break;
12069
12070       case AmbiguousMove:
12071         if (appData.debugMode)
12072           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12073         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12074                 (forwardMostMove / 2) + 1,
12075                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12076         DisplayError(move, 0);
12077         done = TRUE;
12078         break;
12079
12080       default:
12081       case ImpossibleMove:
12082         if (appData.debugMode)
12083           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12084         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12085                 (forwardMostMove / 2) + 1,
12086                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12087         DisplayError(move, 0);
12088         done = TRUE;
12089         break;
12090     }
12091
12092     if (done) {
12093         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12094             DrawPosition(FALSE, boards[currentMove]);
12095             DisplayBothClocks();
12096             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12097               DisplayComment(currentMove - 1, commentList[currentMove]);
12098         }
12099         (void) StopLoadGameTimer();
12100         gameFileFP = NULL;
12101         cmailOldMove = forwardMostMove;
12102         return FALSE;
12103     } else {
12104         /* currentMoveString is set as a side-effect of yylex */
12105
12106         thinkOutput[0] = NULLCHAR;
12107         MakeMove(fromX, fromY, toX, toY, promoChar);
12108         killX = killY = -1; // [HGM] lion: used up
12109         currentMove = forwardMostMove;
12110         return TRUE;
12111     }
12112 }
12113
12114 /* Load the nth game from the given file */
12115 int
12116 LoadGameFromFile (char *filename, int n, char *title, int useList)
12117 {
12118     FILE *f;
12119     char buf[MSG_SIZ];
12120
12121     if (strcmp(filename, "-") == 0) {
12122         f = stdin;
12123         title = "stdin";
12124     } else {
12125         f = fopen(filename, "rb");
12126         if (f == NULL) {
12127           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12128             DisplayError(buf, errno);
12129             return FALSE;
12130         }
12131     }
12132     if (fseek(f, 0, 0) == -1) {
12133         /* f is not seekable; probably a pipe */
12134         useList = FALSE;
12135     }
12136     if (useList && n == 0) {
12137         int error = GameListBuild(f);
12138         if (error) {
12139             DisplayError(_("Cannot build game list"), error);
12140         } else if (!ListEmpty(&gameList) &&
12141                    ((ListGame *) gameList.tailPred)->number > 1) {
12142             GameListPopUp(f, title);
12143             return TRUE;
12144         }
12145         GameListDestroy();
12146         n = 1;
12147     }
12148     if (n == 0) n = 1;
12149     return LoadGame(f, n, title, FALSE);
12150 }
12151
12152
12153 void
12154 MakeRegisteredMove ()
12155 {
12156     int fromX, fromY, toX, toY;
12157     char promoChar;
12158     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12159         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12160           case CMAIL_MOVE:
12161           case CMAIL_DRAW:
12162             if (appData.debugMode)
12163               fprintf(debugFP, "Restoring %s for game %d\n",
12164                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12165
12166             thinkOutput[0] = NULLCHAR;
12167             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12168             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12169             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12170             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12171             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12172             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12173             MakeMove(fromX, fromY, toX, toY, promoChar);
12174             ShowMove(fromX, fromY, toX, toY);
12175
12176             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12177               case MT_NONE:
12178               case MT_CHECK:
12179                 break;
12180
12181               case MT_CHECKMATE:
12182               case MT_STAINMATE:
12183                 if (WhiteOnMove(currentMove)) {
12184                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12185                 } else {
12186                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12187                 }
12188                 break;
12189
12190               case MT_STALEMATE:
12191                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12192                 break;
12193             }
12194
12195             break;
12196
12197           case CMAIL_RESIGN:
12198             if (WhiteOnMove(currentMove)) {
12199                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12200             } else {
12201                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12202             }
12203             break;
12204
12205           case CMAIL_ACCEPT:
12206             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12207             break;
12208
12209           default:
12210             break;
12211         }
12212     }
12213
12214     return;
12215 }
12216
12217 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12218 int
12219 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12220 {
12221     int retVal;
12222
12223     if (gameNumber > nCmailGames) {
12224         DisplayError(_("No more games in this message"), 0);
12225         return FALSE;
12226     }
12227     if (f == lastLoadGameFP) {
12228         int offset = gameNumber - lastLoadGameNumber;
12229         if (offset == 0) {
12230             cmailMsg[0] = NULLCHAR;
12231             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12232                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12233                 nCmailMovesRegistered--;
12234             }
12235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12236             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12237                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12238             }
12239         } else {
12240             if (! RegisterMove()) return FALSE;
12241         }
12242     }
12243
12244     retVal = LoadGame(f, gameNumber, title, useList);
12245
12246     /* Make move registered during previous look at this game, if any */
12247     MakeRegisteredMove();
12248
12249     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12250         commentList[currentMove]
12251           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12252         DisplayComment(currentMove - 1, commentList[currentMove]);
12253     }
12254
12255     return retVal;
12256 }
12257
12258 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12259 int
12260 ReloadGame (int offset)
12261 {
12262     int gameNumber = lastLoadGameNumber + offset;
12263     if (lastLoadGameFP == NULL) {
12264         DisplayError(_("No game has been loaded yet"), 0);
12265         return FALSE;
12266     }
12267     if (gameNumber <= 0) {
12268         DisplayError(_("Can't back up any further"), 0);
12269         return FALSE;
12270     }
12271     if (cmailMsgLoaded) {
12272         return CmailLoadGame(lastLoadGameFP, gameNumber,
12273                              lastLoadGameTitle, lastLoadGameUseList);
12274     } else {
12275         return LoadGame(lastLoadGameFP, gameNumber,
12276                         lastLoadGameTitle, lastLoadGameUseList);
12277     }
12278 }
12279
12280 int keys[EmptySquare+1];
12281
12282 int
12283 PositionMatches (Board b1, Board b2)
12284 {
12285     int r, f, sum=0;
12286     switch(appData.searchMode) {
12287         case 1: return CompareWithRights(b1, b2);
12288         case 2:
12289             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12290                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12291             }
12292             return TRUE;
12293         case 3:
12294             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12295               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12296                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12297             }
12298             return sum==0;
12299         case 4:
12300             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12301                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12302             }
12303             return sum==0;
12304     }
12305     return TRUE;
12306 }
12307
12308 #define Q_PROMO  4
12309 #define Q_EP     3
12310 #define Q_BCASTL 2
12311 #define Q_WCASTL 1
12312
12313 int pieceList[256], quickBoard[256];
12314 ChessSquare pieceType[256] = { EmptySquare };
12315 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12316 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12317 int soughtTotal, turn;
12318 Boolean epOK, flipSearch;
12319
12320 typedef struct {
12321     unsigned char piece, to;
12322 } Move;
12323
12324 #define DSIZE (250000)
12325
12326 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12327 Move *moveDatabase = initialSpace;
12328 unsigned int movePtr, dataSize = DSIZE;
12329
12330 int
12331 MakePieceList (Board board, int *counts)
12332 {
12333     int r, f, n=Q_PROMO, total=0;
12334     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12335     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12336         int sq = f + (r<<4);
12337         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12338             quickBoard[sq] = ++n;
12339             pieceList[n] = sq;
12340             pieceType[n] = board[r][f];
12341             counts[board[r][f]]++;
12342             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12343             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12344             total++;
12345         }
12346     }
12347     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12348     return total;
12349 }
12350
12351 void
12352 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12353 {
12354     int sq = fromX + (fromY<<4);
12355     int piece = quickBoard[sq], rook;
12356     quickBoard[sq] = 0;
12357     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12358     if(piece == pieceList[1] && fromY == toY) {
12359       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12360         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12361         moveDatabase[movePtr++].piece = Q_WCASTL;
12362         quickBoard[sq] = piece;
12363         piece = quickBoard[from]; quickBoard[from] = 0;
12364         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12365       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12366         quickBoard[sq] = 0; // remove Rook
12367         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12368         moveDatabase[movePtr++].piece = Q_WCASTL;
12369         quickBoard[sq] = pieceList[1]; // put King
12370         piece = rook;
12371         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12372       }
12373     } else
12374     if(piece == pieceList[2] && fromY == toY) {
12375       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12376         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12377         moveDatabase[movePtr++].piece = Q_BCASTL;
12378         quickBoard[sq] = piece;
12379         piece = quickBoard[from]; quickBoard[from] = 0;
12380         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12381       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12382         quickBoard[sq] = 0; // remove Rook
12383         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12384         moveDatabase[movePtr++].piece = Q_BCASTL;
12385         quickBoard[sq] = pieceList[2]; // put King
12386         piece = rook;
12387         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12388       }
12389     } else
12390     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12391         quickBoard[(fromY<<4)+toX] = 0;
12392         moveDatabase[movePtr].piece = Q_EP;
12393         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12394         moveDatabase[movePtr].to = sq;
12395     } else
12396     if(promoPiece != pieceType[piece]) {
12397         moveDatabase[movePtr++].piece = Q_PROMO;
12398         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12399     }
12400     moveDatabase[movePtr].piece = piece;
12401     quickBoard[sq] = piece;
12402     movePtr++;
12403 }
12404
12405 int
12406 PackGame (Board board)
12407 {
12408     Move *newSpace = NULL;
12409     moveDatabase[movePtr].piece = 0; // terminate previous game
12410     if(movePtr > dataSize) {
12411         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12412         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12413         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12414         if(newSpace) {
12415             int i;
12416             Move *p = moveDatabase, *q = newSpace;
12417             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12418             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12419             moveDatabase = newSpace;
12420         } else { // calloc failed, we must be out of memory. Too bad...
12421             dataSize = 0; // prevent calloc events for all subsequent games
12422             return 0;     // and signal this one isn't cached
12423         }
12424     }
12425     movePtr++;
12426     MakePieceList(board, counts);
12427     return movePtr;
12428 }
12429
12430 int
12431 QuickCompare (Board board, int *minCounts, int *maxCounts)
12432 {   // compare according to search mode
12433     int r, f;
12434     switch(appData.searchMode)
12435     {
12436       case 1: // exact position match
12437         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12438         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12439             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12440         }
12441         break;
12442       case 2: // can have extra material on empty squares
12443         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12444             if(board[r][f] == EmptySquare) continue;
12445             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12446         }
12447         break;
12448       case 3: // material with exact Pawn structure
12449         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12450             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12451             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12452         } // fall through to material comparison
12453       case 4: // exact material
12454         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12455         break;
12456       case 6: // material range with given imbalance
12457         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12458         // fall through to range comparison
12459       case 5: // material range
12460         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12461     }
12462     return TRUE;
12463 }
12464
12465 int
12466 QuickScan (Board board, Move *move)
12467 {   // reconstruct game,and compare all positions in it
12468     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12469     do {
12470         int piece = move->piece;
12471         int to = move->to, from = pieceList[piece];
12472         if(found < 0) { // if already found just scan to game end for final piece count
12473           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12474            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12475            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12476                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12477             ) {
12478             static int lastCounts[EmptySquare+1];
12479             int i;
12480             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12481             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12482           } else stretch = 0;
12483           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12484           if(found >= 0 && !appData.minPieces) return found;
12485         }
12486         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12487           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12488           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12489             piece = (++move)->piece;
12490             from = pieceList[piece];
12491             counts[pieceType[piece]]--;
12492             pieceType[piece] = (ChessSquare) move->to;
12493             counts[move->to]++;
12494           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12495             counts[pieceType[quickBoard[to]]]--;
12496             quickBoard[to] = 0; total--;
12497             move++;
12498             continue;
12499           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12500             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12501             from  = pieceList[piece]; // so this must be King
12502             quickBoard[from] = 0;
12503             pieceList[piece] = to;
12504             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12505             quickBoard[from] = 0; // rook
12506             quickBoard[to] = piece;
12507             to = move->to; piece = move->piece;
12508             goto aftercastle;
12509           }
12510         }
12511         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12512         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12513         quickBoard[from] = 0;
12514       aftercastle:
12515         quickBoard[to] = piece;
12516         pieceList[piece] = to;
12517         cnt++; turn ^= 3;
12518         move++;
12519     } while(1);
12520 }
12521
12522 void
12523 InitSearch ()
12524 {
12525     int r, f;
12526     flipSearch = FALSE;
12527     CopyBoard(soughtBoard, boards[currentMove]);
12528     soughtTotal = MakePieceList(soughtBoard, maxSought);
12529     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12530     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12531     CopyBoard(reverseBoard, boards[currentMove]);
12532     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12533         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12534         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12535         reverseBoard[r][f] = piece;
12536     }
12537     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12538     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12539     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12540                  || (boards[currentMove][CASTLING][2] == NoRights ||
12541                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12542                  && (boards[currentMove][CASTLING][5] == NoRights ||
12543                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12544       ) {
12545         flipSearch = TRUE;
12546         CopyBoard(flipBoard, soughtBoard);
12547         CopyBoard(rotateBoard, reverseBoard);
12548         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12549             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12550             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12551         }
12552     }
12553     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12554     if(appData.searchMode >= 5) {
12555         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12556         MakePieceList(soughtBoard, minSought);
12557         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12558     }
12559     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12560         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12561 }
12562
12563 GameInfo dummyInfo;
12564 static int creatingBook;
12565
12566 int
12567 GameContainsPosition (FILE *f, ListGame *lg)
12568 {
12569     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12570     int fromX, fromY, toX, toY;
12571     char promoChar;
12572     static int initDone=FALSE;
12573
12574     // weed out games based on numerical tag comparison
12575     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12576     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12577     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12578     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12579     if(!initDone) {
12580         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12581         initDone = TRUE;
12582     }
12583     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12584     else CopyBoard(boards[scratch], initialPosition); // default start position
12585     if(lg->moves) {
12586         turn = btm + 1;
12587         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12588         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12589     }
12590     if(btm) plyNr++;
12591     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12592     fseek(f, lg->offset, 0);
12593     yynewfile(f);
12594     while(1) {
12595         yyboardindex = scratch;
12596         quickFlag = plyNr+1;
12597         next = Myylex();
12598         quickFlag = 0;
12599         switch(next) {
12600             case PGNTag:
12601                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12602             default:
12603                 continue;
12604
12605             case XBoardGame:
12606             case GNUChessGame:
12607                 if(plyNr) return -1; // after we have seen moves, this is for new game
12608               continue;
12609
12610             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12611             case ImpossibleMove:
12612             case WhiteWins: // game ends here with these four
12613             case BlackWins:
12614             case GameIsDrawn:
12615             case GameUnfinished:
12616                 return -1;
12617
12618             case IllegalMove:
12619                 if(appData.testLegality) return -1;
12620             case WhiteCapturesEnPassant:
12621             case BlackCapturesEnPassant:
12622             case WhitePromotion:
12623             case BlackPromotion:
12624             case WhiteNonPromotion:
12625             case BlackNonPromotion:
12626             case NormalMove:
12627             case FirstLeg:
12628             case WhiteKingSideCastle:
12629             case WhiteQueenSideCastle:
12630             case BlackKingSideCastle:
12631             case BlackQueenSideCastle:
12632             case WhiteKingSideCastleWild:
12633             case WhiteQueenSideCastleWild:
12634             case BlackKingSideCastleWild:
12635             case BlackQueenSideCastleWild:
12636             case WhiteHSideCastleFR:
12637             case WhiteASideCastleFR:
12638             case BlackHSideCastleFR:
12639             case BlackASideCastleFR:
12640                 fromX = currentMoveString[0] - AAA;
12641                 fromY = currentMoveString[1] - ONE;
12642                 toX = currentMoveString[2] - AAA;
12643                 toY = currentMoveString[3] - ONE;
12644                 promoChar = currentMoveString[4];
12645                 break;
12646             case WhiteDrop:
12647             case BlackDrop:
12648                 fromX = next == WhiteDrop ?
12649                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12650                   (int) CharToPiece(ToLower(currentMoveString[0]));
12651                 fromY = DROP_RANK;
12652                 toX = currentMoveString[2] - AAA;
12653                 toY = currentMoveString[3] - ONE;
12654                 promoChar = 0;
12655                 break;
12656         }
12657         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12658         plyNr++;
12659         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12660         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12661         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12662         if(appData.findMirror) {
12663             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12664             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12665         }
12666     }
12667 }
12668
12669 /* Load the nth game from open file f */
12670 int
12671 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12672 {
12673     ChessMove cm;
12674     char buf[MSG_SIZ];
12675     int gn = gameNumber;
12676     ListGame *lg = NULL;
12677     int numPGNTags = 0;
12678     int err, pos = -1;
12679     GameMode oldGameMode;
12680     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12681
12682     if (appData.debugMode)
12683         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12684
12685     if (gameMode == Training )
12686         SetTrainingModeOff();
12687
12688     oldGameMode = gameMode;
12689     if (gameMode != BeginningOfGame) {
12690       Reset(FALSE, TRUE);
12691     }
12692     killX = killY = -1; // [HGM] lion: in case we did not Reset
12693
12694     gameFileFP = f;
12695     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12696         fclose(lastLoadGameFP);
12697     }
12698
12699     if (useList) {
12700         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12701
12702         if (lg) {
12703             fseek(f, lg->offset, 0);
12704             GameListHighlight(gameNumber);
12705             pos = lg->position;
12706             gn = 1;
12707         }
12708         else {
12709             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12710               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12711             else
12712             DisplayError(_("Game number out of range"), 0);
12713             return FALSE;
12714         }
12715     } else {
12716         GameListDestroy();
12717         if (fseek(f, 0, 0) == -1) {
12718             if (f == lastLoadGameFP ?
12719                 gameNumber == lastLoadGameNumber + 1 :
12720                 gameNumber == 1) {
12721                 gn = 1;
12722             } else {
12723                 DisplayError(_("Can't seek on game file"), 0);
12724                 return FALSE;
12725             }
12726         }
12727     }
12728     lastLoadGameFP = f;
12729     lastLoadGameNumber = gameNumber;
12730     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12731     lastLoadGameUseList = useList;
12732
12733     yynewfile(f);
12734
12735     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12736       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12737                 lg->gameInfo.black);
12738             DisplayTitle(buf);
12739     } else if (*title != NULLCHAR) {
12740         if (gameNumber > 1) {
12741           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12742             DisplayTitle(buf);
12743         } else {
12744             DisplayTitle(title);
12745         }
12746     }
12747
12748     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12749         gameMode = PlayFromGameFile;
12750         ModeHighlight();
12751     }
12752
12753     currentMove = forwardMostMove = backwardMostMove = 0;
12754     CopyBoard(boards[0], initialPosition);
12755     StopClocks();
12756
12757     /*
12758      * Skip the first gn-1 games in the file.
12759      * Also skip over anything that precedes an identifiable
12760      * start of game marker, to avoid being confused by
12761      * garbage at the start of the file.  Currently
12762      * recognized start of game markers are the move number "1",
12763      * the pattern "gnuchess .* game", the pattern
12764      * "^[#;%] [^ ]* game file", and a PGN tag block.
12765      * A game that starts with one of the latter two patterns
12766      * will also have a move number 1, possibly
12767      * following a position diagram.
12768      * 5-4-02: Let's try being more lenient and allowing a game to
12769      * start with an unnumbered move.  Does that break anything?
12770      */
12771     cm = lastLoadGameStart = EndOfFile;
12772     while (gn > 0) {
12773         yyboardindex = forwardMostMove;
12774         cm = (ChessMove) Myylex();
12775         switch (cm) {
12776           case EndOfFile:
12777             if (cmailMsgLoaded) {
12778                 nCmailGames = CMAIL_MAX_GAMES - gn;
12779             } else {
12780                 Reset(TRUE, TRUE);
12781                 DisplayError(_("Game not found in file"), 0);
12782             }
12783             return FALSE;
12784
12785           case GNUChessGame:
12786           case XBoardGame:
12787             gn--;
12788             lastLoadGameStart = cm;
12789             break;
12790
12791           case MoveNumberOne:
12792             switch (lastLoadGameStart) {
12793               case GNUChessGame:
12794               case XBoardGame:
12795               case PGNTag:
12796                 break;
12797               case MoveNumberOne:
12798               case EndOfFile:
12799                 gn--;           /* count this game */
12800                 lastLoadGameStart = cm;
12801                 break;
12802               default:
12803                 /* impossible */
12804                 break;
12805             }
12806             break;
12807
12808           case PGNTag:
12809             switch (lastLoadGameStart) {
12810               case GNUChessGame:
12811               case PGNTag:
12812               case MoveNumberOne:
12813               case EndOfFile:
12814                 gn--;           /* count this game */
12815                 lastLoadGameStart = cm;
12816                 break;
12817               case XBoardGame:
12818                 lastLoadGameStart = cm; /* game counted already */
12819                 break;
12820               default:
12821                 /* impossible */
12822                 break;
12823             }
12824             if (gn > 0) {
12825                 do {
12826                     yyboardindex = forwardMostMove;
12827                     cm = (ChessMove) Myylex();
12828                 } while (cm == PGNTag || cm == Comment);
12829             }
12830             break;
12831
12832           case WhiteWins:
12833           case BlackWins:
12834           case GameIsDrawn:
12835             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12836                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12837                     != CMAIL_OLD_RESULT) {
12838                     nCmailResults ++ ;
12839                     cmailResult[  CMAIL_MAX_GAMES
12840                                 - gn - 1] = CMAIL_OLD_RESULT;
12841                 }
12842             }
12843             break;
12844
12845           case NormalMove:
12846           case FirstLeg:
12847             /* Only a NormalMove can be at the start of a game
12848              * without a position diagram. */
12849             if (lastLoadGameStart == EndOfFile ) {
12850               gn--;
12851               lastLoadGameStart = MoveNumberOne;
12852             }
12853             break;
12854
12855           default:
12856             break;
12857         }
12858     }
12859
12860     if (appData.debugMode)
12861       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12862
12863     if (cm == XBoardGame) {
12864         /* Skip any header junk before position diagram and/or move 1 */
12865         for (;;) {
12866             yyboardindex = forwardMostMove;
12867             cm = (ChessMove) Myylex();
12868
12869             if (cm == EndOfFile ||
12870                 cm == GNUChessGame || cm == XBoardGame) {
12871                 /* Empty game; pretend end-of-file and handle later */
12872                 cm = EndOfFile;
12873                 break;
12874             }
12875
12876             if (cm == MoveNumberOne || cm == PositionDiagram ||
12877                 cm == PGNTag || cm == Comment)
12878               break;
12879         }
12880     } else if (cm == GNUChessGame) {
12881         if (gameInfo.event != NULL) {
12882             free(gameInfo.event);
12883         }
12884         gameInfo.event = StrSave(yy_text);
12885     }
12886
12887     startedFromSetupPosition = FALSE;
12888     while (cm == PGNTag) {
12889         if (appData.debugMode)
12890           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12891         err = ParsePGNTag(yy_text, &gameInfo);
12892         if (!err) numPGNTags++;
12893
12894         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12895         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12896             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12897             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12898             InitPosition(TRUE);
12899             oldVariant = gameInfo.variant;
12900             if (appData.debugMode)
12901               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12902         }
12903
12904
12905         if (gameInfo.fen != NULL) {
12906           Board initial_position;
12907           startedFromSetupPosition = TRUE;
12908           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12909             Reset(TRUE, TRUE);
12910             DisplayError(_("Bad FEN position in file"), 0);
12911             return FALSE;
12912           }
12913           CopyBoard(boards[0], initial_position);
12914           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12915             CopyBoard(initialPosition, initial_position);
12916           if (blackPlaysFirst) {
12917             currentMove = forwardMostMove = backwardMostMove = 1;
12918             CopyBoard(boards[1], initial_position);
12919             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12920             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12921             timeRemaining[0][1] = whiteTimeRemaining;
12922             timeRemaining[1][1] = blackTimeRemaining;
12923             if (commentList[0] != NULL) {
12924               commentList[1] = commentList[0];
12925               commentList[0] = NULL;
12926             }
12927           } else {
12928             currentMove = forwardMostMove = backwardMostMove = 0;
12929           }
12930           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12931           {   int i;
12932               initialRulePlies = FENrulePlies;
12933               for( i=0; i< nrCastlingRights; i++ )
12934                   initialRights[i] = initial_position[CASTLING][i];
12935           }
12936           yyboardindex = forwardMostMove;
12937           free(gameInfo.fen);
12938           gameInfo.fen = NULL;
12939         }
12940
12941         yyboardindex = forwardMostMove;
12942         cm = (ChessMove) Myylex();
12943
12944         /* Handle comments interspersed among the tags */
12945         while (cm == Comment) {
12946             char *p;
12947             if (appData.debugMode)
12948               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12949             p = yy_text;
12950             AppendComment(currentMove, p, FALSE);
12951             yyboardindex = forwardMostMove;
12952             cm = (ChessMove) Myylex();
12953         }
12954     }
12955
12956     /* don't rely on existence of Event tag since if game was
12957      * pasted from clipboard the Event tag may not exist
12958      */
12959     if (numPGNTags > 0){
12960         char *tags;
12961         if (gameInfo.variant == VariantNormal) {
12962           VariantClass v = StringToVariant(gameInfo.event);
12963           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12964           if(v < VariantShogi) gameInfo.variant = v;
12965         }
12966         if (!matchMode) {
12967           if( appData.autoDisplayTags ) {
12968             tags = PGNTags(&gameInfo);
12969             TagsPopUp(tags, CmailMsg());
12970             free(tags);
12971           }
12972         }
12973     } else {
12974         /* Make something up, but don't display it now */
12975         SetGameInfo();
12976         TagsPopDown();
12977     }
12978
12979     if (cm == PositionDiagram) {
12980         int i, j;
12981         char *p;
12982         Board initial_position;
12983
12984         if (appData.debugMode)
12985           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12986
12987         if (!startedFromSetupPosition) {
12988             p = yy_text;
12989             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12990               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12991                 switch (*p) {
12992                   case '{':
12993                   case '[':
12994                   case '-':
12995                   case ' ':
12996                   case '\t':
12997                   case '\n':
12998                   case '\r':
12999                     break;
13000                   default:
13001                     initial_position[i][j++] = CharToPiece(*p);
13002                     break;
13003                 }
13004             while (*p == ' ' || *p == '\t' ||
13005                    *p == '\n' || *p == '\r') p++;
13006
13007             if (strncmp(p, "black", strlen("black"))==0)
13008               blackPlaysFirst = TRUE;
13009             else
13010               blackPlaysFirst = FALSE;
13011             startedFromSetupPosition = TRUE;
13012
13013             CopyBoard(boards[0], initial_position);
13014             if (blackPlaysFirst) {
13015                 currentMove = forwardMostMove = backwardMostMove = 1;
13016                 CopyBoard(boards[1], initial_position);
13017                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13018                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13019                 timeRemaining[0][1] = whiteTimeRemaining;
13020                 timeRemaining[1][1] = blackTimeRemaining;
13021                 if (commentList[0] != NULL) {
13022                     commentList[1] = commentList[0];
13023                     commentList[0] = NULL;
13024                 }
13025             } else {
13026                 currentMove = forwardMostMove = backwardMostMove = 0;
13027             }
13028         }
13029         yyboardindex = forwardMostMove;
13030         cm = (ChessMove) Myylex();
13031     }
13032
13033   if(!creatingBook) {
13034     if (first.pr == NoProc) {
13035         StartChessProgram(&first);
13036     }
13037     InitChessProgram(&first, FALSE);
13038     SendToProgram("force\n", &first);
13039     if (startedFromSetupPosition) {
13040         SendBoard(&first, forwardMostMove);
13041     if (appData.debugMode) {
13042         fprintf(debugFP, "Load Game\n");
13043     }
13044         DisplayBothClocks();
13045     }
13046   }
13047
13048     /* [HGM] server: flag to write setup moves in broadcast file as one */
13049     loadFlag = appData.suppressLoadMoves;
13050
13051     while (cm == Comment) {
13052         char *p;
13053         if (appData.debugMode)
13054           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13055         p = yy_text;
13056         AppendComment(currentMove, p, FALSE);
13057         yyboardindex = forwardMostMove;
13058         cm = (ChessMove) Myylex();
13059     }
13060
13061     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13062         cm == WhiteWins || cm == BlackWins ||
13063         cm == GameIsDrawn || cm == GameUnfinished) {
13064         DisplayMessage("", _("No moves in game"));
13065         if (cmailMsgLoaded) {
13066             if (appData.debugMode)
13067               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13068             ClearHighlights();
13069             flipView = FALSE;
13070         }
13071         DrawPosition(FALSE, boards[currentMove]);
13072         DisplayBothClocks();
13073         gameMode = EditGame;
13074         ModeHighlight();
13075         gameFileFP = NULL;
13076         cmailOldMove = 0;
13077         return TRUE;
13078     }
13079
13080     // [HGM] PV info: routine tests if comment empty
13081     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13082         DisplayComment(currentMove - 1, commentList[currentMove]);
13083     }
13084     if (!matchMode && appData.timeDelay != 0)
13085       DrawPosition(FALSE, boards[currentMove]);
13086
13087     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13088       programStats.ok_to_send = 1;
13089     }
13090
13091     /* if the first token after the PGN tags is a move
13092      * and not move number 1, retrieve it from the parser
13093      */
13094     if (cm != MoveNumberOne)
13095         LoadGameOneMove(cm);
13096
13097     /* load the remaining moves from the file */
13098     while (LoadGameOneMove(EndOfFile)) {
13099       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13100       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13101     }
13102
13103     /* rewind to the start of the game */
13104     currentMove = backwardMostMove;
13105
13106     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13107
13108     if (oldGameMode == AnalyzeFile) {
13109       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13110       AnalyzeFileEvent();
13111     } else
13112     if (oldGameMode == AnalyzeMode) {
13113       AnalyzeFileEvent();
13114     }
13115
13116     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13117         long int w, b; // [HGM] adjourn: restore saved clock times
13118         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13119         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13120             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13121             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13122         }
13123     }
13124
13125     if(creatingBook) return TRUE;
13126     if (!matchMode && pos > 0) {
13127         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13128     } else
13129     if (matchMode || appData.timeDelay == 0) {
13130       ToEndEvent();
13131     } else if (appData.timeDelay > 0) {
13132       AutoPlayGameLoop();
13133     }
13134
13135     if (appData.debugMode)
13136         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13137
13138     loadFlag = 0; /* [HGM] true game starts */
13139     return TRUE;
13140 }
13141
13142 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13143 int
13144 ReloadPosition (int offset)
13145 {
13146     int positionNumber = lastLoadPositionNumber + offset;
13147     if (lastLoadPositionFP == NULL) {
13148         DisplayError(_("No position has been loaded yet"), 0);
13149         return FALSE;
13150     }
13151     if (positionNumber <= 0) {
13152         DisplayError(_("Can't back up any further"), 0);
13153         return FALSE;
13154     }
13155     return LoadPosition(lastLoadPositionFP, positionNumber,
13156                         lastLoadPositionTitle);
13157 }
13158
13159 /* Load the nth position from the given file */
13160 int
13161 LoadPositionFromFile (char *filename, int n, char *title)
13162 {
13163     FILE *f;
13164     char buf[MSG_SIZ];
13165
13166     if (strcmp(filename, "-") == 0) {
13167         return LoadPosition(stdin, n, "stdin");
13168     } else {
13169         f = fopen(filename, "rb");
13170         if (f == NULL) {
13171             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13172             DisplayError(buf, errno);
13173             return FALSE;
13174         } else {
13175             return LoadPosition(f, n, title);
13176         }
13177     }
13178 }
13179
13180 /* Load the nth position from the given open file, and close it */
13181 int
13182 LoadPosition (FILE *f, int positionNumber, char *title)
13183 {
13184     char *p, line[MSG_SIZ];
13185     Board initial_position;
13186     int i, j, fenMode, pn;
13187
13188     if (gameMode == Training )
13189         SetTrainingModeOff();
13190
13191     if (gameMode != BeginningOfGame) {
13192         Reset(FALSE, TRUE);
13193     }
13194     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13195         fclose(lastLoadPositionFP);
13196     }
13197     if (positionNumber == 0) positionNumber = 1;
13198     lastLoadPositionFP = f;
13199     lastLoadPositionNumber = positionNumber;
13200     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13201     if (first.pr == NoProc && !appData.noChessProgram) {
13202       StartChessProgram(&first);
13203       InitChessProgram(&first, FALSE);
13204     }
13205     pn = positionNumber;
13206     if (positionNumber < 0) {
13207         /* Negative position number means to seek to that byte offset */
13208         if (fseek(f, -positionNumber, 0) == -1) {
13209             DisplayError(_("Can't seek on position file"), 0);
13210             return FALSE;
13211         };
13212         pn = 1;
13213     } else {
13214         if (fseek(f, 0, 0) == -1) {
13215             if (f == lastLoadPositionFP ?
13216                 positionNumber == lastLoadPositionNumber + 1 :
13217                 positionNumber == 1) {
13218                 pn = 1;
13219             } else {
13220                 DisplayError(_("Can't seek on position file"), 0);
13221                 return FALSE;
13222             }
13223         }
13224     }
13225     /* See if this file is FEN or old-style xboard */
13226     if (fgets(line, MSG_SIZ, f) == NULL) {
13227         DisplayError(_("Position not found in file"), 0);
13228         return FALSE;
13229     }
13230     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13231     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13232
13233     if (pn >= 2) {
13234         if (fenMode || line[0] == '#') pn--;
13235         while (pn > 0) {
13236             /* skip positions before number pn */
13237             if (fgets(line, MSG_SIZ, f) == NULL) {
13238                 Reset(TRUE, TRUE);
13239                 DisplayError(_("Position not found in file"), 0);
13240                 return FALSE;
13241             }
13242             if (fenMode || line[0] == '#') pn--;
13243         }
13244     }
13245
13246     if (fenMode) {
13247         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13248             DisplayError(_("Bad FEN position in file"), 0);
13249             return FALSE;
13250         }
13251     } else {
13252         (void) fgets(line, MSG_SIZ, f);
13253         (void) fgets(line, MSG_SIZ, f);
13254
13255         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13256             (void) fgets(line, MSG_SIZ, f);
13257             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13258                 if (*p == ' ')
13259                   continue;
13260                 initial_position[i][j++] = CharToPiece(*p);
13261             }
13262         }
13263
13264         blackPlaysFirst = FALSE;
13265         if (!feof(f)) {
13266             (void) fgets(line, MSG_SIZ, f);
13267             if (strncmp(line, "black", strlen("black"))==0)
13268               blackPlaysFirst = TRUE;
13269         }
13270     }
13271     startedFromSetupPosition = TRUE;
13272
13273     CopyBoard(boards[0], initial_position);
13274     if (blackPlaysFirst) {
13275         currentMove = forwardMostMove = backwardMostMove = 1;
13276         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13277         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13278         CopyBoard(boards[1], initial_position);
13279         DisplayMessage("", _("Black to play"));
13280     } else {
13281         currentMove = forwardMostMove = backwardMostMove = 0;
13282         DisplayMessage("", _("White to play"));
13283     }
13284     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13285     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13286         SendToProgram("force\n", &first);
13287         SendBoard(&first, forwardMostMove);
13288     }
13289     if (appData.debugMode) {
13290 int i, j;
13291   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13292   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13293         fprintf(debugFP, "Load Position\n");
13294     }
13295
13296     if (positionNumber > 1) {
13297       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13298         DisplayTitle(line);
13299     } else {
13300         DisplayTitle(title);
13301     }
13302     gameMode = EditGame;
13303     ModeHighlight();
13304     ResetClocks();
13305     timeRemaining[0][1] = whiteTimeRemaining;
13306     timeRemaining[1][1] = blackTimeRemaining;
13307     DrawPosition(FALSE, boards[currentMove]);
13308
13309     return TRUE;
13310 }
13311
13312
13313 void
13314 CopyPlayerNameIntoFileName (char **dest, char *src)
13315 {
13316     while (*src != NULLCHAR && *src != ',') {
13317         if (*src == ' ') {
13318             *(*dest)++ = '_';
13319             src++;
13320         } else {
13321             *(*dest)++ = *src++;
13322         }
13323     }
13324 }
13325
13326 char *
13327 DefaultFileName (char *ext)
13328 {
13329     static char def[MSG_SIZ];
13330     char *p;
13331
13332     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13333         p = def;
13334         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13335         *p++ = '-';
13336         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13337         *p++ = '.';
13338         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13339     } else {
13340         def[0] = NULLCHAR;
13341     }
13342     return def;
13343 }
13344
13345 /* Save the current game to the given file */
13346 int
13347 SaveGameToFile (char *filename, int append)
13348 {
13349     FILE *f;
13350     char buf[MSG_SIZ];
13351     int result, i, t,tot=0;
13352
13353     if (strcmp(filename, "-") == 0) {
13354         return SaveGame(stdout, 0, NULL);
13355     } else {
13356         for(i=0; i<10; i++) { // upto 10 tries
13357              f = fopen(filename, append ? "a" : "w");
13358              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13359              if(f || errno != 13) break;
13360              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13361              tot += t;
13362         }
13363         if (f == NULL) {
13364             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13365             DisplayError(buf, errno);
13366             return FALSE;
13367         } else {
13368             safeStrCpy(buf, lastMsg, MSG_SIZ);
13369             DisplayMessage(_("Waiting for access to save file"), "");
13370             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13371             DisplayMessage(_("Saving game"), "");
13372             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13373             result = SaveGame(f, 0, NULL);
13374             DisplayMessage(buf, "");
13375             return result;
13376         }
13377     }
13378 }
13379
13380 char *
13381 SavePart (char *str)
13382 {
13383     static char buf[MSG_SIZ];
13384     char *p;
13385
13386     p = strchr(str, ' ');
13387     if (p == NULL) return str;
13388     strncpy(buf, str, p - str);
13389     buf[p - str] = NULLCHAR;
13390     return buf;
13391 }
13392
13393 #define PGN_MAX_LINE 75
13394
13395 #define PGN_SIDE_WHITE  0
13396 #define PGN_SIDE_BLACK  1
13397
13398 static int
13399 FindFirstMoveOutOfBook (int side)
13400 {
13401     int result = -1;
13402
13403     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13404         int index = backwardMostMove;
13405         int has_book_hit = 0;
13406
13407         if( (index % 2) != side ) {
13408             index++;
13409         }
13410
13411         while( index < forwardMostMove ) {
13412             /* Check to see if engine is in book */
13413             int depth = pvInfoList[index].depth;
13414             int score = pvInfoList[index].score;
13415             int in_book = 0;
13416
13417             if( depth <= 2 ) {
13418                 in_book = 1;
13419             }
13420             else if( score == 0 && depth == 63 ) {
13421                 in_book = 1; /* Zappa */
13422             }
13423             else if( score == 2 && depth == 99 ) {
13424                 in_book = 1; /* Abrok */
13425             }
13426
13427             has_book_hit += in_book;
13428
13429             if( ! in_book ) {
13430                 result = index;
13431
13432                 break;
13433             }
13434
13435             index += 2;
13436         }
13437     }
13438
13439     return result;
13440 }
13441
13442 void
13443 GetOutOfBookInfo (char * buf)
13444 {
13445     int oob[2];
13446     int i;
13447     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13448
13449     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13450     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13451
13452     *buf = '\0';
13453
13454     if( oob[0] >= 0 || oob[1] >= 0 ) {
13455         for( i=0; i<2; i++ ) {
13456             int idx = oob[i];
13457
13458             if( idx >= 0 ) {
13459                 if( i > 0 && oob[0] >= 0 ) {
13460                     strcat( buf, "   " );
13461                 }
13462
13463                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13464                 sprintf( buf+strlen(buf), "%s%.2f",
13465                     pvInfoList[idx].score >= 0 ? "+" : "",
13466                     pvInfoList[idx].score / 100.0 );
13467             }
13468         }
13469     }
13470 }
13471
13472 /* Save game in PGN style */
13473 static void
13474 SaveGamePGN2 (FILE *f)
13475 {
13476     int i, offset, linelen, newblock;
13477 //    char *movetext;
13478     char numtext[32];
13479     int movelen, numlen, blank;
13480     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13481
13482     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13483
13484     PrintPGNTags(f, &gameInfo);
13485
13486     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13487
13488     if (backwardMostMove > 0 || startedFromSetupPosition) {
13489         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13490         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13491         fprintf(f, "\n{--------------\n");
13492         PrintPosition(f, backwardMostMove);
13493         fprintf(f, "--------------}\n");
13494         free(fen);
13495     }
13496     else {
13497         /* [AS] Out of book annotation */
13498         if( appData.saveOutOfBookInfo ) {
13499             char buf[64];
13500
13501             GetOutOfBookInfo( buf );
13502
13503             if( buf[0] != '\0' ) {
13504                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13505             }
13506         }
13507
13508         fprintf(f, "\n");
13509     }
13510
13511     i = backwardMostMove;
13512     linelen = 0;
13513     newblock = TRUE;
13514
13515     while (i < forwardMostMove) {
13516         /* Print comments preceding this move */
13517         if (commentList[i] != NULL) {
13518             if (linelen > 0) fprintf(f, "\n");
13519             fprintf(f, "%s", commentList[i]);
13520             linelen = 0;
13521             newblock = TRUE;
13522         }
13523
13524         /* Format move number */
13525         if ((i % 2) == 0)
13526           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13527         else
13528           if (newblock)
13529             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13530           else
13531             numtext[0] = NULLCHAR;
13532
13533         numlen = strlen(numtext);
13534         newblock = FALSE;
13535
13536         /* Print move number */
13537         blank = linelen > 0 && numlen > 0;
13538         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13539             fprintf(f, "\n");
13540             linelen = 0;
13541             blank = 0;
13542         }
13543         if (blank) {
13544             fprintf(f, " ");
13545             linelen++;
13546         }
13547         fprintf(f, "%s", numtext);
13548         linelen += numlen;
13549
13550         /* Get move */
13551         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13552         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13553
13554         /* Print move */
13555         blank = linelen > 0 && movelen > 0;
13556         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13557             fprintf(f, "\n");
13558             linelen = 0;
13559             blank = 0;
13560         }
13561         if (blank) {
13562             fprintf(f, " ");
13563             linelen++;
13564         }
13565         fprintf(f, "%s", move_buffer);
13566         linelen += movelen;
13567
13568         /* [AS] Add PV info if present */
13569         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13570             /* [HGM] add time */
13571             char buf[MSG_SIZ]; int seconds;
13572
13573             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13574
13575             if( seconds <= 0)
13576               buf[0] = 0;
13577             else
13578               if( seconds < 30 )
13579                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13580               else
13581                 {
13582                   seconds = (seconds + 4)/10; // round to full seconds
13583                   if( seconds < 60 )
13584                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13585                   else
13586                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13587                 }
13588
13589             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13590                       pvInfoList[i].score >= 0 ? "+" : "",
13591                       pvInfoList[i].score / 100.0,
13592                       pvInfoList[i].depth,
13593                       buf );
13594
13595             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13596
13597             /* Print score/depth */
13598             blank = linelen > 0 && movelen > 0;
13599             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13600                 fprintf(f, "\n");
13601                 linelen = 0;
13602                 blank = 0;
13603             }
13604             if (blank) {
13605                 fprintf(f, " ");
13606                 linelen++;
13607             }
13608             fprintf(f, "%s", move_buffer);
13609             linelen += movelen;
13610         }
13611
13612         i++;
13613     }
13614
13615     /* Start a new line */
13616     if (linelen > 0) fprintf(f, "\n");
13617
13618     /* Print comments after last move */
13619     if (commentList[i] != NULL) {
13620         fprintf(f, "%s\n", commentList[i]);
13621     }
13622
13623     /* Print result */
13624     if (gameInfo.resultDetails != NULL &&
13625         gameInfo.resultDetails[0] != NULLCHAR) {
13626         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13627         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13628            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13629             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13630         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13631     } else {
13632         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13633     }
13634 }
13635
13636 /* Save game in PGN style and close the file */
13637 int
13638 SaveGamePGN (FILE *f)
13639 {
13640     SaveGamePGN2(f);
13641     fclose(f);
13642     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13643     return TRUE;
13644 }
13645
13646 /* Save game in old style and close the file */
13647 int
13648 SaveGameOldStyle (FILE *f)
13649 {
13650     int i, offset;
13651     time_t tm;
13652
13653     tm = time((time_t *) NULL);
13654
13655     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13656     PrintOpponents(f);
13657
13658     if (backwardMostMove > 0 || startedFromSetupPosition) {
13659         fprintf(f, "\n[--------------\n");
13660         PrintPosition(f, backwardMostMove);
13661         fprintf(f, "--------------]\n");
13662     } else {
13663         fprintf(f, "\n");
13664     }
13665
13666     i = backwardMostMove;
13667     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13668
13669     while (i < forwardMostMove) {
13670         if (commentList[i] != NULL) {
13671             fprintf(f, "[%s]\n", commentList[i]);
13672         }
13673
13674         if ((i % 2) == 1) {
13675             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13676             i++;
13677         } else {
13678             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13679             i++;
13680             if (commentList[i] != NULL) {
13681                 fprintf(f, "\n");
13682                 continue;
13683             }
13684             if (i >= forwardMostMove) {
13685                 fprintf(f, "\n");
13686                 break;
13687             }
13688             fprintf(f, "%s\n", parseList[i]);
13689             i++;
13690         }
13691     }
13692
13693     if (commentList[i] != NULL) {
13694         fprintf(f, "[%s]\n", commentList[i]);
13695     }
13696
13697     /* This isn't really the old style, but it's close enough */
13698     if (gameInfo.resultDetails != NULL &&
13699         gameInfo.resultDetails[0] != NULLCHAR) {
13700         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13701                 gameInfo.resultDetails);
13702     } else {
13703         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13704     }
13705
13706     fclose(f);
13707     return TRUE;
13708 }
13709
13710 /* Save the current game to open file f and close the file */
13711 int
13712 SaveGame (FILE *f, int dummy, char *dummy2)
13713 {
13714     if (gameMode == EditPosition) EditPositionDone(TRUE);
13715     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13716     if (appData.oldSaveStyle)
13717       return SaveGameOldStyle(f);
13718     else
13719       return SaveGamePGN(f);
13720 }
13721
13722 /* Save the current position to the given file */
13723 int
13724 SavePositionToFile (char *filename)
13725 {
13726     FILE *f;
13727     char buf[MSG_SIZ];
13728
13729     if (strcmp(filename, "-") == 0) {
13730         return SavePosition(stdout, 0, NULL);
13731     } else {
13732         f = fopen(filename, "a");
13733         if (f == NULL) {
13734             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13735             DisplayError(buf, errno);
13736             return FALSE;
13737         } else {
13738             safeStrCpy(buf, lastMsg, MSG_SIZ);
13739             DisplayMessage(_("Waiting for access to save file"), "");
13740             flock(fileno(f), LOCK_EX); // [HGM] lock
13741             DisplayMessage(_("Saving position"), "");
13742             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13743             SavePosition(f, 0, NULL);
13744             DisplayMessage(buf, "");
13745             return TRUE;
13746         }
13747     }
13748 }
13749
13750 /* Save the current position to the given open file and close the file */
13751 int
13752 SavePosition (FILE *f, int dummy, char *dummy2)
13753 {
13754     time_t tm;
13755     char *fen;
13756
13757     if (gameMode == EditPosition) EditPositionDone(TRUE);
13758     if (appData.oldSaveStyle) {
13759         tm = time((time_t *) NULL);
13760
13761         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13762         PrintOpponents(f);
13763         fprintf(f, "[--------------\n");
13764         PrintPosition(f, currentMove);
13765         fprintf(f, "--------------]\n");
13766     } else {
13767         fen = PositionToFEN(currentMove, NULL, 1);
13768         fprintf(f, "%s\n", fen);
13769         free(fen);
13770     }
13771     fclose(f);
13772     return TRUE;
13773 }
13774
13775 void
13776 ReloadCmailMsgEvent (int unregister)
13777 {
13778 #if !WIN32
13779     static char *inFilename = NULL;
13780     static char *outFilename;
13781     int i;
13782     struct stat inbuf, outbuf;
13783     int status;
13784
13785     /* Any registered moves are unregistered if unregister is set, */
13786     /* i.e. invoked by the signal handler */
13787     if (unregister) {
13788         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13789             cmailMoveRegistered[i] = FALSE;
13790             if (cmailCommentList[i] != NULL) {
13791                 free(cmailCommentList[i]);
13792                 cmailCommentList[i] = NULL;
13793             }
13794         }
13795         nCmailMovesRegistered = 0;
13796     }
13797
13798     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13799         cmailResult[i] = CMAIL_NOT_RESULT;
13800     }
13801     nCmailResults = 0;
13802
13803     if (inFilename == NULL) {
13804         /* Because the filenames are static they only get malloced once  */
13805         /* and they never get freed                                      */
13806         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13807         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13808
13809         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13810         sprintf(outFilename, "%s.out", appData.cmailGameName);
13811     }
13812
13813     status = stat(outFilename, &outbuf);
13814     if (status < 0) {
13815         cmailMailedMove = FALSE;
13816     } else {
13817         status = stat(inFilename, &inbuf);
13818         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13819     }
13820
13821     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13822        counts the games, notes how each one terminated, etc.
13823
13824        It would be nice to remove this kludge and instead gather all
13825        the information while building the game list.  (And to keep it
13826        in the game list nodes instead of having a bunch of fixed-size
13827        parallel arrays.)  Note this will require getting each game's
13828        termination from the PGN tags, as the game list builder does
13829        not process the game moves.  --mann
13830        */
13831     cmailMsgLoaded = TRUE;
13832     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13833
13834     /* Load first game in the file or popup game menu */
13835     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13836
13837 #endif /* !WIN32 */
13838     return;
13839 }
13840
13841 int
13842 RegisterMove ()
13843 {
13844     FILE *f;
13845     char string[MSG_SIZ];
13846
13847     if (   cmailMailedMove
13848         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13849         return TRUE;            /* Allow free viewing  */
13850     }
13851
13852     /* Unregister move to ensure that we don't leave RegisterMove        */
13853     /* with the move registered when the conditions for registering no   */
13854     /* longer hold                                                       */
13855     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13856         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13857         nCmailMovesRegistered --;
13858
13859         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13860           {
13861               free(cmailCommentList[lastLoadGameNumber - 1]);
13862               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13863           }
13864     }
13865
13866     if (cmailOldMove == -1) {
13867         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13868         return FALSE;
13869     }
13870
13871     if (currentMove > cmailOldMove + 1) {
13872         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13873         return FALSE;
13874     }
13875
13876     if (currentMove < cmailOldMove) {
13877         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13878         return FALSE;
13879     }
13880
13881     if (forwardMostMove > currentMove) {
13882         /* Silently truncate extra moves */
13883         TruncateGame();
13884     }
13885
13886     if (   (currentMove == cmailOldMove + 1)
13887         || (   (currentMove == cmailOldMove)
13888             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13889                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13890         if (gameInfo.result != GameUnfinished) {
13891             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13892         }
13893
13894         if (commentList[currentMove] != NULL) {
13895             cmailCommentList[lastLoadGameNumber - 1]
13896               = StrSave(commentList[currentMove]);
13897         }
13898         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13899
13900         if (appData.debugMode)
13901           fprintf(debugFP, "Saving %s for game %d\n",
13902                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13903
13904         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13905
13906         f = fopen(string, "w");
13907         if (appData.oldSaveStyle) {
13908             SaveGameOldStyle(f); /* also closes the file */
13909
13910             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13911             f = fopen(string, "w");
13912             SavePosition(f, 0, NULL); /* also closes the file */
13913         } else {
13914             fprintf(f, "{--------------\n");
13915             PrintPosition(f, currentMove);
13916             fprintf(f, "--------------}\n\n");
13917
13918             SaveGame(f, 0, NULL); /* also closes the file*/
13919         }
13920
13921         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13922         nCmailMovesRegistered ++;
13923     } else if (nCmailGames == 1) {
13924         DisplayError(_("You have not made a move yet"), 0);
13925         return FALSE;
13926     }
13927
13928     return TRUE;
13929 }
13930
13931 void
13932 MailMoveEvent ()
13933 {
13934 #if !WIN32
13935     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13936     FILE *commandOutput;
13937     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13938     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13939     int nBuffers;
13940     int i;
13941     int archived;
13942     char *arcDir;
13943
13944     if (! cmailMsgLoaded) {
13945         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13946         return;
13947     }
13948
13949     if (nCmailGames == nCmailResults) {
13950         DisplayError(_("No unfinished games"), 0);
13951         return;
13952     }
13953
13954 #if CMAIL_PROHIBIT_REMAIL
13955     if (cmailMailedMove) {
13956       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);
13957         DisplayError(msg, 0);
13958         return;
13959     }
13960 #endif
13961
13962     if (! (cmailMailedMove || RegisterMove())) return;
13963
13964     if (   cmailMailedMove
13965         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13966       snprintf(string, MSG_SIZ, partCommandString,
13967                appData.debugMode ? " -v" : "", appData.cmailGameName);
13968         commandOutput = popen(string, "r");
13969
13970         if (commandOutput == NULL) {
13971             DisplayError(_("Failed to invoke cmail"), 0);
13972         } else {
13973             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13974                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13975             }
13976             if (nBuffers > 1) {
13977                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13978                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13979                 nBytes = MSG_SIZ - 1;
13980             } else {
13981                 (void) memcpy(msg, buffer, nBytes);
13982             }
13983             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13984
13985             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13986                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13987
13988                 archived = TRUE;
13989                 for (i = 0; i < nCmailGames; i ++) {
13990                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13991                         archived = FALSE;
13992                     }
13993                 }
13994                 if (   archived
13995                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13996                         != NULL)) {
13997                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13998                            arcDir,
13999                            appData.cmailGameName,
14000                            gameInfo.date);
14001                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14002                     cmailMsgLoaded = FALSE;
14003                 }
14004             }
14005
14006             DisplayInformation(msg);
14007             pclose(commandOutput);
14008         }
14009     } else {
14010         if ((*cmailMsg) != '\0') {
14011             DisplayInformation(cmailMsg);
14012         }
14013     }
14014
14015     return;
14016 #endif /* !WIN32 */
14017 }
14018
14019 char *
14020 CmailMsg ()
14021 {
14022 #if WIN32
14023     return NULL;
14024 #else
14025     int  prependComma = 0;
14026     char number[5];
14027     char string[MSG_SIZ];       /* Space for game-list */
14028     int  i;
14029
14030     if (!cmailMsgLoaded) return "";
14031
14032     if (cmailMailedMove) {
14033       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14034     } else {
14035         /* Create a list of games left */
14036       snprintf(string, MSG_SIZ, "[");
14037         for (i = 0; i < nCmailGames; i ++) {
14038             if (! (   cmailMoveRegistered[i]
14039                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14040                 if (prependComma) {
14041                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14042                 } else {
14043                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14044                     prependComma = 1;
14045                 }
14046
14047                 strcat(string, number);
14048             }
14049         }
14050         strcat(string, "]");
14051
14052         if (nCmailMovesRegistered + nCmailResults == 0) {
14053             switch (nCmailGames) {
14054               case 1:
14055                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14056                 break;
14057
14058               case 2:
14059                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14060                 break;
14061
14062               default:
14063                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14064                          nCmailGames);
14065                 break;
14066             }
14067         } else {
14068             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14069               case 1:
14070                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14071                          string);
14072                 break;
14073
14074               case 0:
14075                 if (nCmailResults == nCmailGames) {
14076                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14077                 } else {
14078                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14079                 }
14080                 break;
14081
14082               default:
14083                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14084                          string);
14085             }
14086         }
14087     }
14088     return cmailMsg;
14089 #endif /* WIN32 */
14090 }
14091
14092 void
14093 ResetGameEvent ()
14094 {
14095     if (gameMode == Training)
14096       SetTrainingModeOff();
14097
14098     Reset(TRUE, TRUE);
14099     cmailMsgLoaded = FALSE;
14100     if (appData.icsActive) {
14101       SendToICS(ics_prefix);
14102       SendToICS("refresh\n");
14103     }
14104 }
14105
14106 void
14107 ExitEvent (int status)
14108 {
14109     exiting++;
14110     if (exiting > 2) {
14111       /* Give up on clean exit */
14112       exit(status);
14113     }
14114     if (exiting > 1) {
14115       /* Keep trying for clean exit */
14116       return;
14117     }
14118
14119     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14120     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14121
14122     if (telnetISR != NULL) {
14123       RemoveInputSource(telnetISR);
14124     }
14125     if (icsPR != NoProc) {
14126       DestroyChildProcess(icsPR, TRUE);
14127     }
14128
14129     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14130     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14131
14132     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14133     /* make sure this other one finishes before killing it!                  */
14134     if(endingGame) { int count = 0;
14135         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14136         while(endingGame && count++ < 10) DoSleep(1);
14137         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14138     }
14139
14140     /* Kill off chess programs */
14141     if (first.pr != NoProc) {
14142         ExitAnalyzeMode();
14143
14144         DoSleep( appData.delayBeforeQuit );
14145         SendToProgram("quit\n", &first);
14146         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14147     }
14148     if (second.pr != NoProc) {
14149         DoSleep( appData.delayBeforeQuit );
14150         SendToProgram("quit\n", &second);
14151         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14152     }
14153     if (first.isr != NULL) {
14154         RemoveInputSource(first.isr);
14155     }
14156     if (second.isr != NULL) {
14157         RemoveInputSource(second.isr);
14158     }
14159
14160     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14161     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14162
14163     ShutDownFrontEnd();
14164     exit(status);
14165 }
14166
14167 void
14168 PauseEngine (ChessProgramState *cps)
14169 {
14170     SendToProgram("pause\n", cps);
14171     cps->pause = 2;
14172 }
14173
14174 void
14175 UnPauseEngine (ChessProgramState *cps)
14176 {
14177     SendToProgram("resume\n", cps);
14178     cps->pause = 1;
14179 }
14180
14181 void
14182 PauseEvent ()
14183 {
14184     if (appData.debugMode)
14185         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14186     if (pausing) {
14187         pausing = FALSE;
14188         ModeHighlight();
14189         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14190             StartClocks();
14191             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14192                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14193                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14194             }
14195             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14196             HandleMachineMove(stashedInputMove, stalledEngine);
14197             stalledEngine = NULL;
14198             return;
14199         }
14200         if (gameMode == MachinePlaysWhite ||
14201             gameMode == TwoMachinesPlay   ||
14202             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14203             if(first.pause)  UnPauseEngine(&first);
14204             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14205             if(second.pause) UnPauseEngine(&second);
14206             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14207             StartClocks();
14208         } else {
14209             DisplayBothClocks();
14210         }
14211         if (gameMode == PlayFromGameFile) {
14212             if (appData.timeDelay >= 0)
14213                 AutoPlayGameLoop();
14214         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14215             Reset(FALSE, TRUE);
14216             SendToICS(ics_prefix);
14217             SendToICS("refresh\n");
14218         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14219             ForwardInner(forwardMostMove);
14220         }
14221         pauseExamInvalid = FALSE;
14222     } else {
14223         switch (gameMode) {
14224           default:
14225             return;
14226           case IcsExamining:
14227             pauseExamForwardMostMove = forwardMostMove;
14228             pauseExamInvalid = FALSE;
14229             /* fall through */
14230           case IcsObserving:
14231           case IcsPlayingWhite:
14232           case IcsPlayingBlack:
14233             pausing = TRUE;
14234             ModeHighlight();
14235             return;
14236           case PlayFromGameFile:
14237             (void) StopLoadGameTimer();
14238             pausing = TRUE;
14239             ModeHighlight();
14240             break;
14241           case BeginningOfGame:
14242             if (appData.icsActive) return;
14243             /* else fall through */
14244           case MachinePlaysWhite:
14245           case MachinePlaysBlack:
14246           case TwoMachinesPlay:
14247             if (forwardMostMove == 0)
14248               return;           /* don't pause if no one has moved */
14249             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14250                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14251                 if(onMove->pause) {           // thinking engine can be paused
14252                     PauseEngine(onMove);      // do it
14253                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14254                         PauseEngine(onMove->other);
14255                     else
14256                         SendToProgram("easy\n", onMove->other);
14257                     StopClocks();
14258                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14259             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14260                 if(first.pause) {
14261                     PauseEngine(&first);
14262                     StopClocks();
14263                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14264             } else { // human on move, pause pondering by either method
14265                 if(first.pause)
14266                     PauseEngine(&first);
14267                 else if(appData.ponderNextMove)
14268                     SendToProgram("easy\n", &first);
14269                 StopClocks();
14270             }
14271             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14272           case AnalyzeMode:
14273             pausing = TRUE;
14274             ModeHighlight();
14275             break;
14276         }
14277     }
14278 }
14279
14280 void
14281 EditCommentEvent ()
14282 {
14283     char title[MSG_SIZ];
14284
14285     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14286       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14287     } else {
14288       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14289                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14290                parseList[currentMove - 1]);
14291     }
14292
14293     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14294 }
14295
14296
14297 void
14298 EditTagsEvent ()
14299 {
14300     char *tags = PGNTags(&gameInfo);
14301     bookUp = FALSE;
14302     EditTagsPopUp(tags, NULL);
14303     free(tags);
14304 }
14305
14306 void
14307 ToggleSecond ()
14308 {
14309   if(second.analyzing) {
14310     SendToProgram("exit\n", &second);
14311     second.analyzing = FALSE;
14312   } else {
14313     if (second.pr == NoProc) StartChessProgram(&second);
14314     InitChessProgram(&second, FALSE);
14315     FeedMovesToProgram(&second, currentMove);
14316
14317     SendToProgram("analyze\n", &second);
14318     second.analyzing = TRUE;
14319   }
14320 }
14321
14322 /* Toggle ShowThinking */
14323 void
14324 ToggleShowThinking()
14325 {
14326   appData.showThinking = !appData.showThinking;
14327   ShowThinkingEvent();
14328 }
14329
14330 int
14331 AnalyzeModeEvent ()
14332 {
14333     char buf[MSG_SIZ];
14334
14335     if (!first.analysisSupport) {
14336       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14337       DisplayError(buf, 0);
14338       return 0;
14339     }
14340     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14341     if (appData.icsActive) {
14342         if (gameMode != IcsObserving) {
14343           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14344             DisplayError(buf, 0);
14345             /* secure check */
14346             if (appData.icsEngineAnalyze) {
14347                 if (appData.debugMode)
14348                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14349                 ExitAnalyzeMode();
14350                 ModeHighlight();
14351             }
14352             return 0;
14353         }
14354         /* if enable, user wants to disable icsEngineAnalyze */
14355         if (appData.icsEngineAnalyze) {
14356                 ExitAnalyzeMode();
14357                 ModeHighlight();
14358                 return 0;
14359         }
14360         appData.icsEngineAnalyze = TRUE;
14361         if (appData.debugMode)
14362             fprintf(debugFP, "ICS engine analyze starting... \n");
14363     }
14364
14365     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14366     if (appData.noChessProgram || gameMode == AnalyzeMode)
14367       return 0;
14368
14369     if (gameMode != AnalyzeFile) {
14370         if (!appData.icsEngineAnalyze) {
14371                EditGameEvent();
14372                if (gameMode != EditGame) return 0;
14373         }
14374         if (!appData.showThinking) ToggleShowThinking();
14375         ResurrectChessProgram();
14376         SendToProgram("analyze\n", &first);
14377         first.analyzing = TRUE;
14378         /*first.maybeThinking = TRUE;*/
14379         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14380         EngineOutputPopUp();
14381     }
14382     if (!appData.icsEngineAnalyze) {
14383         gameMode = AnalyzeMode;
14384         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14385     }
14386     pausing = FALSE;
14387     ModeHighlight();
14388     SetGameInfo();
14389
14390     StartAnalysisClock();
14391     GetTimeMark(&lastNodeCountTime);
14392     lastNodeCount = 0;
14393     return 1;
14394 }
14395
14396 void
14397 AnalyzeFileEvent ()
14398 {
14399     if (appData.noChessProgram || gameMode == AnalyzeFile)
14400       return;
14401
14402     if (!first.analysisSupport) {
14403       char buf[MSG_SIZ];
14404       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14405       DisplayError(buf, 0);
14406       return;
14407     }
14408
14409     if (gameMode != AnalyzeMode) {
14410         keepInfo = 1; // mere annotating should not alter PGN tags
14411         EditGameEvent();
14412         keepInfo = 0;
14413         if (gameMode != EditGame) return;
14414         if (!appData.showThinking) ToggleShowThinking();
14415         ResurrectChessProgram();
14416         SendToProgram("analyze\n", &first);
14417         first.analyzing = TRUE;
14418         /*first.maybeThinking = TRUE;*/
14419         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14420         EngineOutputPopUp();
14421     }
14422     gameMode = AnalyzeFile;
14423     pausing = FALSE;
14424     ModeHighlight();
14425
14426     StartAnalysisClock();
14427     GetTimeMark(&lastNodeCountTime);
14428     lastNodeCount = 0;
14429     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14430     AnalysisPeriodicEvent(1);
14431 }
14432
14433 void
14434 MachineWhiteEvent ()
14435 {
14436     char buf[MSG_SIZ];
14437     char *bookHit = NULL;
14438
14439     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14440       return;
14441
14442
14443     if (gameMode == PlayFromGameFile ||
14444         gameMode == TwoMachinesPlay  ||
14445         gameMode == Training         ||
14446         gameMode == AnalyzeMode      ||
14447         gameMode == EndOfGame)
14448         EditGameEvent();
14449
14450     if (gameMode == EditPosition)
14451         EditPositionDone(TRUE);
14452
14453     if (!WhiteOnMove(currentMove)) {
14454         DisplayError(_("It is not White's turn"), 0);
14455         return;
14456     }
14457
14458     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14459       ExitAnalyzeMode();
14460
14461     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14462         gameMode == AnalyzeFile)
14463         TruncateGame();
14464
14465     ResurrectChessProgram();    /* in case it isn't running */
14466     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14467         gameMode = MachinePlaysWhite;
14468         ResetClocks();
14469     } else
14470     gameMode = MachinePlaysWhite;
14471     pausing = FALSE;
14472     ModeHighlight();
14473     SetGameInfo();
14474     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14475     DisplayTitle(buf);
14476     if (first.sendName) {
14477       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14478       SendToProgram(buf, &first);
14479     }
14480     if (first.sendTime) {
14481       if (first.useColors) {
14482         SendToProgram("black\n", &first); /*gnu kludge*/
14483       }
14484       SendTimeRemaining(&first, TRUE);
14485     }
14486     if (first.useColors) {
14487       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14488     }
14489     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14490     SetMachineThinkingEnables();
14491     first.maybeThinking = TRUE;
14492     StartClocks();
14493     firstMove = FALSE;
14494
14495     if (appData.autoFlipView && !flipView) {
14496       flipView = !flipView;
14497       DrawPosition(FALSE, NULL);
14498       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14499     }
14500
14501     if(bookHit) { // [HGM] book: simulate book reply
14502         static char bookMove[MSG_SIZ]; // a bit generous?
14503
14504         programStats.nodes = programStats.depth = programStats.time =
14505         programStats.score = programStats.got_only_move = 0;
14506         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14507
14508         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14509         strcat(bookMove, bookHit);
14510         HandleMachineMove(bookMove, &first);
14511     }
14512 }
14513
14514 void
14515 MachineBlackEvent ()
14516 {
14517   char buf[MSG_SIZ];
14518   char *bookHit = NULL;
14519
14520     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14521         return;
14522
14523
14524     if (gameMode == PlayFromGameFile ||
14525         gameMode == TwoMachinesPlay  ||
14526         gameMode == Training         ||
14527         gameMode == AnalyzeMode      ||
14528         gameMode == EndOfGame)
14529         EditGameEvent();
14530
14531     if (gameMode == EditPosition)
14532         EditPositionDone(TRUE);
14533
14534     if (WhiteOnMove(currentMove)) {
14535         DisplayError(_("It is not Black's turn"), 0);
14536         return;
14537     }
14538
14539     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14540       ExitAnalyzeMode();
14541
14542     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14543         gameMode == AnalyzeFile)
14544         TruncateGame();
14545
14546     ResurrectChessProgram();    /* in case it isn't running */
14547     gameMode = MachinePlaysBlack;
14548     pausing = FALSE;
14549     ModeHighlight();
14550     SetGameInfo();
14551     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14552     DisplayTitle(buf);
14553     if (first.sendName) {
14554       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14555       SendToProgram(buf, &first);
14556     }
14557     if (first.sendTime) {
14558       if (first.useColors) {
14559         SendToProgram("white\n", &first); /*gnu kludge*/
14560       }
14561       SendTimeRemaining(&first, FALSE);
14562     }
14563     if (first.useColors) {
14564       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14565     }
14566     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14567     SetMachineThinkingEnables();
14568     first.maybeThinking = TRUE;
14569     StartClocks();
14570
14571     if (appData.autoFlipView && flipView) {
14572       flipView = !flipView;
14573       DrawPosition(FALSE, NULL);
14574       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14575     }
14576     if(bookHit) { // [HGM] book: simulate book reply
14577         static char bookMove[MSG_SIZ]; // a bit generous?
14578
14579         programStats.nodes = programStats.depth = programStats.time =
14580         programStats.score = programStats.got_only_move = 0;
14581         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14582
14583         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14584         strcat(bookMove, bookHit);
14585         HandleMachineMove(bookMove, &first);
14586     }
14587 }
14588
14589
14590 void
14591 DisplayTwoMachinesTitle ()
14592 {
14593     char buf[MSG_SIZ];
14594     if (appData.matchGames > 0) {
14595         if(appData.tourneyFile[0]) {
14596           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14597                    gameInfo.white, _("vs."), gameInfo.black,
14598                    nextGame+1, appData.matchGames+1,
14599                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14600         } else
14601         if (first.twoMachinesColor[0] == 'w') {
14602           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14603                    gameInfo.white, _("vs."),  gameInfo.black,
14604                    first.matchWins, second.matchWins,
14605                    matchGame - 1 - (first.matchWins + second.matchWins));
14606         } else {
14607           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14608                    gameInfo.white, _("vs."), gameInfo.black,
14609                    second.matchWins, first.matchWins,
14610                    matchGame - 1 - (first.matchWins + second.matchWins));
14611         }
14612     } else {
14613       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14614     }
14615     DisplayTitle(buf);
14616 }
14617
14618 void
14619 SettingsMenuIfReady ()
14620 {
14621   if (second.lastPing != second.lastPong) {
14622     DisplayMessage("", _("Waiting for second chess program"));
14623     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14624     return;
14625   }
14626   ThawUI();
14627   DisplayMessage("", "");
14628   SettingsPopUp(&second);
14629 }
14630
14631 int
14632 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14633 {
14634     char buf[MSG_SIZ];
14635     if (cps->pr == NoProc) {
14636         StartChessProgram(cps);
14637         if (cps->protocolVersion == 1) {
14638           retry();
14639           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14640         } else {
14641           /* kludge: allow timeout for initial "feature" command */
14642           if(retry != TwoMachinesEventIfReady) FreezeUI();
14643           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14644           DisplayMessage("", buf);
14645           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14646         }
14647         return 1;
14648     }
14649     return 0;
14650 }
14651
14652 void
14653 TwoMachinesEvent P((void))
14654 {
14655     int i;
14656     char buf[MSG_SIZ];
14657     ChessProgramState *onmove;
14658     char *bookHit = NULL;
14659     static int stalling = 0;
14660     TimeMark now;
14661     long wait;
14662
14663     if (appData.noChessProgram) return;
14664
14665     switch (gameMode) {
14666       case TwoMachinesPlay:
14667         return;
14668       case MachinePlaysWhite:
14669       case MachinePlaysBlack:
14670         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14671             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14672             return;
14673         }
14674         /* fall through */
14675       case BeginningOfGame:
14676       case PlayFromGameFile:
14677       case EndOfGame:
14678         EditGameEvent();
14679         if (gameMode != EditGame) return;
14680         break;
14681       case EditPosition:
14682         EditPositionDone(TRUE);
14683         break;
14684       case AnalyzeMode:
14685       case AnalyzeFile:
14686         ExitAnalyzeMode();
14687         break;
14688       case EditGame:
14689       default:
14690         break;
14691     }
14692
14693 //    forwardMostMove = currentMove;
14694     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14695     startingEngine = TRUE;
14696
14697     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14698
14699     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14700     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14701       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14702       return;
14703     }
14704     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14705
14706     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14707                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14708         startingEngine = matchMode = FALSE;
14709         DisplayError("second engine does not play this", 0);
14710         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14711         EditGameEvent(); // switch back to EditGame mode
14712         return;
14713     }
14714
14715     if(!stalling) {
14716       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14717       SendToProgram("force\n", &second);
14718       stalling = 1;
14719       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14720       return;
14721     }
14722     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14723     if(appData.matchPause>10000 || appData.matchPause<10)
14724                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14725     wait = SubtractTimeMarks(&now, &pauseStart);
14726     if(wait < appData.matchPause) {
14727         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14728         return;
14729     }
14730     // we are now committed to starting the game
14731     stalling = 0;
14732     DisplayMessage("", "");
14733     if (startedFromSetupPosition) {
14734         SendBoard(&second, backwardMostMove);
14735     if (appData.debugMode) {
14736         fprintf(debugFP, "Two Machines\n");
14737     }
14738     }
14739     for (i = backwardMostMove; i < forwardMostMove; i++) {
14740         SendMoveToProgram(i, &second);
14741     }
14742
14743     gameMode = TwoMachinesPlay;
14744     pausing = startingEngine = FALSE;
14745     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14746     SetGameInfo();
14747     DisplayTwoMachinesTitle();
14748     firstMove = TRUE;
14749     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14750         onmove = &first;
14751     } else {
14752         onmove = &second;
14753     }
14754     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14755     SendToProgram(first.computerString, &first);
14756     if (first.sendName) {
14757       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14758       SendToProgram(buf, &first);
14759     }
14760     SendToProgram(second.computerString, &second);
14761     if (second.sendName) {
14762       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14763       SendToProgram(buf, &second);
14764     }
14765
14766     ResetClocks();
14767     if (!first.sendTime || !second.sendTime) {
14768         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14769         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14770     }
14771     if (onmove->sendTime) {
14772       if (onmove->useColors) {
14773         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14774       }
14775       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14776     }
14777     if (onmove->useColors) {
14778       SendToProgram(onmove->twoMachinesColor, onmove);
14779     }
14780     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14781 //    SendToProgram("go\n", onmove);
14782     onmove->maybeThinking = TRUE;
14783     SetMachineThinkingEnables();
14784
14785     StartClocks();
14786
14787     if(bookHit) { // [HGM] book: simulate book reply
14788         static char bookMove[MSG_SIZ]; // a bit generous?
14789
14790         programStats.nodes = programStats.depth = programStats.time =
14791         programStats.score = programStats.got_only_move = 0;
14792         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14793
14794         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14795         strcat(bookMove, bookHit);
14796         savedMessage = bookMove; // args for deferred call
14797         savedState = onmove;
14798         ScheduleDelayedEvent(DeferredBookMove, 1);
14799     }
14800 }
14801
14802 void
14803 TrainingEvent ()
14804 {
14805     if (gameMode == Training) {
14806       SetTrainingModeOff();
14807       gameMode = PlayFromGameFile;
14808       DisplayMessage("", _("Training mode off"));
14809     } else {
14810       gameMode = Training;
14811       animateTraining = appData.animate;
14812
14813       /* make sure we are not already at the end of the game */
14814       if (currentMove < forwardMostMove) {
14815         SetTrainingModeOn();
14816         DisplayMessage("", _("Training mode on"));
14817       } else {
14818         gameMode = PlayFromGameFile;
14819         DisplayError(_("Already at end of game"), 0);
14820       }
14821     }
14822     ModeHighlight();
14823 }
14824
14825 void
14826 IcsClientEvent ()
14827 {
14828     if (!appData.icsActive) return;
14829     switch (gameMode) {
14830       case IcsPlayingWhite:
14831       case IcsPlayingBlack:
14832       case IcsObserving:
14833       case IcsIdle:
14834       case BeginningOfGame:
14835       case IcsExamining:
14836         return;
14837
14838       case EditGame:
14839         break;
14840
14841       case EditPosition:
14842         EditPositionDone(TRUE);
14843         break;
14844
14845       case AnalyzeMode:
14846       case AnalyzeFile:
14847         ExitAnalyzeMode();
14848         break;
14849
14850       default:
14851         EditGameEvent();
14852         break;
14853     }
14854
14855     gameMode = IcsIdle;
14856     ModeHighlight();
14857     return;
14858 }
14859
14860 void
14861 EditGameEvent ()
14862 {
14863     int i;
14864
14865     switch (gameMode) {
14866       case Training:
14867         SetTrainingModeOff();
14868         break;
14869       case MachinePlaysWhite:
14870       case MachinePlaysBlack:
14871       case BeginningOfGame:
14872         SendToProgram("force\n", &first);
14873         SetUserThinkingEnables();
14874         break;
14875       case PlayFromGameFile:
14876         (void) StopLoadGameTimer();
14877         if (gameFileFP != NULL) {
14878             gameFileFP = NULL;
14879         }
14880         break;
14881       case EditPosition:
14882         EditPositionDone(TRUE);
14883         break;
14884       case AnalyzeMode:
14885       case AnalyzeFile:
14886         ExitAnalyzeMode();
14887         SendToProgram("force\n", &first);
14888         break;
14889       case TwoMachinesPlay:
14890         GameEnds(EndOfFile, NULL, GE_PLAYER);
14891         ResurrectChessProgram();
14892         SetUserThinkingEnables();
14893         break;
14894       case EndOfGame:
14895         ResurrectChessProgram();
14896         break;
14897       case IcsPlayingBlack:
14898       case IcsPlayingWhite:
14899         DisplayError(_("Warning: You are still playing a game"), 0);
14900         break;
14901       case IcsObserving:
14902         DisplayError(_("Warning: You are still observing a game"), 0);
14903         break;
14904       case IcsExamining:
14905         DisplayError(_("Warning: You are still examining a game"), 0);
14906         break;
14907       case IcsIdle:
14908         break;
14909       case EditGame:
14910       default:
14911         return;
14912     }
14913
14914     pausing = FALSE;
14915     StopClocks();
14916     first.offeredDraw = second.offeredDraw = 0;
14917
14918     if (gameMode == PlayFromGameFile) {
14919         whiteTimeRemaining = timeRemaining[0][currentMove];
14920         blackTimeRemaining = timeRemaining[1][currentMove];
14921         DisplayTitle("");
14922     }
14923
14924     if (gameMode == MachinePlaysWhite ||
14925         gameMode == MachinePlaysBlack ||
14926         gameMode == TwoMachinesPlay ||
14927         gameMode == EndOfGame) {
14928         i = forwardMostMove;
14929         while (i > currentMove) {
14930             SendToProgram("undo\n", &first);
14931             i--;
14932         }
14933         if(!adjustedClock) {
14934         whiteTimeRemaining = timeRemaining[0][currentMove];
14935         blackTimeRemaining = timeRemaining[1][currentMove];
14936         DisplayBothClocks();
14937         }
14938         if (whiteFlag || blackFlag) {
14939             whiteFlag = blackFlag = 0;
14940         }
14941         DisplayTitle("");
14942     }
14943
14944     gameMode = EditGame;
14945     ModeHighlight();
14946     SetGameInfo();
14947 }
14948
14949
14950 void
14951 EditPositionEvent ()
14952 {
14953     if (gameMode == EditPosition) {
14954         EditGameEvent();
14955         return;
14956     }
14957
14958     EditGameEvent();
14959     if (gameMode != EditGame) return;
14960
14961     gameMode = EditPosition;
14962     ModeHighlight();
14963     SetGameInfo();
14964     if (currentMove > 0)
14965       CopyBoard(boards[0], boards[currentMove]);
14966
14967     blackPlaysFirst = !WhiteOnMove(currentMove);
14968     ResetClocks();
14969     currentMove = forwardMostMove = backwardMostMove = 0;
14970     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14971     DisplayMove(-1);
14972     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14973 }
14974
14975 void
14976 ExitAnalyzeMode ()
14977 {
14978     /* [DM] icsEngineAnalyze - possible call from other functions */
14979     if (appData.icsEngineAnalyze) {
14980         appData.icsEngineAnalyze = FALSE;
14981
14982         DisplayMessage("",_("Close ICS engine analyze..."));
14983     }
14984     if (first.analysisSupport && first.analyzing) {
14985       SendToBoth("exit\n");
14986       first.analyzing = second.analyzing = FALSE;
14987     }
14988     thinkOutput[0] = NULLCHAR;
14989 }
14990
14991 void
14992 EditPositionDone (Boolean fakeRights)
14993 {
14994     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14995
14996     startedFromSetupPosition = TRUE;
14997     InitChessProgram(&first, FALSE);
14998     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14999       boards[0][EP_STATUS] = EP_NONE;
15000       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15001       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15002         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15003         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15004       } else boards[0][CASTLING][2] = NoRights;
15005       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15006         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15007         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15008       } else boards[0][CASTLING][5] = NoRights;
15009       if(gameInfo.variant == VariantSChess) {
15010         int i;
15011         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15012           boards[0][VIRGIN][i] = 0;
15013           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15014           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15015         }
15016       }
15017     }
15018     SendToProgram("force\n", &first);
15019     if (blackPlaysFirst) {
15020         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15021         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15022         currentMove = forwardMostMove = backwardMostMove = 1;
15023         CopyBoard(boards[1], boards[0]);
15024     } else {
15025         currentMove = forwardMostMove = backwardMostMove = 0;
15026     }
15027     SendBoard(&first, forwardMostMove);
15028     if (appData.debugMode) {
15029         fprintf(debugFP, "EditPosDone\n");
15030     }
15031     DisplayTitle("");
15032     DisplayMessage("", "");
15033     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15034     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15035     gameMode = EditGame;
15036     ModeHighlight();
15037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15038     ClearHighlights(); /* [AS] */
15039 }
15040
15041 /* Pause for `ms' milliseconds */
15042 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15043 void
15044 TimeDelay (long ms)
15045 {
15046     TimeMark m1, m2;
15047
15048     GetTimeMark(&m1);
15049     do {
15050         GetTimeMark(&m2);
15051     } while (SubtractTimeMarks(&m2, &m1) < ms);
15052 }
15053
15054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15055 void
15056 SendMultiLineToICS (char *buf)
15057 {
15058     char temp[MSG_SIZ+1], *p;
15059     int len;
15060
15061     len = strlen(buf);
15062     if (len > MSG_SIZ)
15063       len = MSG_SIZ;
15064
15065     strncpy(temp, buf, len);
15066     temp[len] = 0;
15067
15068     p = temp;
15069     while (*p) {
15070         if (*p == '\n' || *p == '\r')
15071           *p = ' ';
15072         ++p;
15073     }
15074
15075     strcat(temp, "\n");
15076     SendToICS(temp);
15077     SendToPlayer(temp, strlen(temp));
15078 }
15079
15080 void
15081 SetWhiteToPlayEvent ()
15082 {
15083     if (gameMode == EditPosition) {
15084         blackPlaysFirst = FALSE;
15085         DisplayBothClocks();    /* works because currentMove is 0 */
15086     } else if (gameMode == IcsExamining) {
15087         SendToICS(ics_prefix);
15088         SendToICS("tomove white\n");
15089     }
15090 }
15091
15092 void
15093 SetBlackToPlayEvent ()
15094 {
15095     if (gameMode == EditPosition) {
15096         blackPlaysFirst = TRUE;
15097         currentMove = 1;        /* kludge */
15098         DisplayBothClocks();
15099         currentMove = 0;
15100     } else if (gameMode == IcsExamining) {
15101         SendToICS(ics_prefix);
15102         SendToICS("tomove black\n");
15103     }
15104 }
15105
15106 void
15107 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15108 {
15109     char buf[MSG_SIZ];
15110     ChessSquare piece = boards[0][y][x];
15111     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15112     static int lastVariant;
15113
15114     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15115
15116     switch (selection) {
15117       case ClearBoard:
15118         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15119         MarkTargetSquares(1);
15120         CopyBoard(currentBoard, boards[0]);
15121         CopyBoard(menuBoard, initialPosition);
15122         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15123             SendToICS(ics_prefix);
15124             SendToICS("bsetup clear\n");
15125         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15126             SendToICS(ics_prefix);
15127             SendToICS("clearboard\n");
15128         } else {
15129             int nonEmpty = 0;
15130             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15131                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15132                 for (y = 0; y < BOARD_HEIGHT; y++) {
15133                     if (gameMode == IcsExamining) {
15134                         if (boards[currentMove][y][x] != EmptySquare) {
15135                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15136                                     AAA + x, ONE + y);
15137                             SendToICS(buf);
15138                         }
15139                     } else if(boards[0][y][x] != DarkSquare) {
15140                         if(boards[0][y][x] != p) nonEmpty++;
15141                         boards[0][y][x] = p;
15142                     }
15143                 }
15144             }
15145             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15146                 int r;
15147                 for(r = 0; r < BOARD_HEIGHT; r++) {
15148                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15149                     ChessSquare p = menuBoard[r][x];
15150                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15151                   }
15152                 }
15153                 DisplayMessage("Clicking clock again restores position", "");
15154                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15155                 if(!nonEmpty) { // asked to clear an empty board
15156                     CopyBoard(boards[0], menuBoard);
15157                 } else
15158                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15159                     CopyBoard(boards[0], initialPosition);
15160                 } else
15161                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15162                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15163                     CopyBoard(boards[0], erasedBoard);
15164                 } else
15165                     CopyBoard(erasedBoard, currentBoard);
15166
15167             }
15168         }
15169         if (gameMode == EditPosition) {
15170             DrawPosition(FALSE, boards[0]);
15171         }
15172         break;
15173
15174       case WhitePlay:
15175         SetWhiteToPlayEvent();
15176         break;
15177
15178       case BlackPlay:
15179         SetBlackToPlayEvent();
15180         break;
15181
15182       case EmptySquare:
15183         if (gameMode == IcsExamining) {
15184             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15185             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15186             SendToICS(buf);
15187         } else {
15188             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15189                 if(x == BOARD_LEFT-2) {
15190                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15191                     boards[0][y][1] = 0;
15192                 } else
15193                 if(x == BOARD_RGHT+1) {
15194                     if(y >= gameInfo.holdingsSize) break;
15195                     boards[0][y][BOARD_WIDTH-2] = 0;
15196                 } else break;
15197             }
15198             boards[0][y][x] = EmptySquare;
15199             DrawPosition(FALSE, boards[0]);
15200         }
15201         break;
15202
15203       case PromotePiece:
15204         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15205            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15206             selection = (ChessSquare) (PROMOTED piece);
15207         } else if(piece == EmptySquare) selection = WhiteSilver;
15208         else selection = (ChessSquare)((int)piece - 1);
15209         goto defaultlabel;
15210
15211       case DemotePiece:
15212         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15213            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15214             selection = (ChessSquare) (DEMOTED piece);
15215         } else if(piece == EmptySquare) selection = BlackSilver;
15216         else selection = (ChessSquare)((int)piece + 1);
15217         goto defaultlabel;
15218
15219       case WhiteQueen:
15220       case BlackQueen:
15221         if(gameInfo.variant == VariantShatranj ||
15222            gameInfo.variant == VariantXiangqi  ||
15223            gameInfo.variant == VariantCourier  ||
15224            gameInfo.variant == VariantASEAN    ||
15225            gameInfo.variant == VariantMakruk     )
15226             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15227         goto defaultlabel;
15228
15229       case WhiteKing:
15230       case BlackKing:
15231         if(gameInfo.variant == VariantXiangqi)
15232             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15233         if(gameInfo.variant == VariantKnightmate)
15234             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15235       default:
15236         defaultlabel:
15237         if (gameMode == IcsExamining) {
15238             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15239             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15240                      PieceToChar(selection), AAA + x, ONE + y);
15241             SendToICS(buf);
15242         } else {
15243             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15244                 int n;
15245                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15246                     n = PieceToNumber(selection - BlackPawn);
15247                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15248                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15249                     boards[0][BOARD_HEIGHT-1-n][1]++;
15250                 } else
15251                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15252                     n = PieceToNumber(selection);
15253                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15254                     boards[0][n][BOARD_WIDTH-1] = selection;
15255                     boards[0][n][BOARD_WIDTH-2]++;
15256                 }
15257             } else
15258             boards[0][y][x] = selection;
15259             DrawPosition(TRUE, boards[0]);
15260             ClearHighlights();
15261             fromX = fromY = -1;
15262         }
15263         break;
15264     }
15265 }
15266
15267
15268 void
15269 DropMenuEvent (ChessSquare selection, int x, int y)
15270 {
15271     ChessMove moveType;
15272
15273     switch (gameMode) {
15274       case IcsPlayingWhite:
15275       case MachinePlaysBlack:
15276         if (!WhiteOnMove(currentMove)) {
15277             DisplayMoveError(_("It is Black's turn"));
15278             return;
15279         }
15280         moveType = WhiteDrop;
15281         break;
15282       case IcsPlayingBlack:
15283       case MachinePlaysWhite:
15284         if (WhiteOnMove(currentMove)) {
15285             DisplayMoveError(_("It is White's turn"));
15286             return;
15287         }
15288         moveType = BlackDrop;
15289         break;
15290       case EditGame:
15291         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15292         break;
15293       default:
15294         return;
15295     }
15296
15297     if (moveType == BlackDrop && selection < BlackPawn) {
15298       selection = (ChessSquare) ((int) selection
15299                                  + (int) BlackPawn - (int) WhitePawn);
15300     }
15301     if (boards[currentMove][y][x] != EmptySquare) {
15302         DisplayMoveError(_("That square is occupied"));
15303         return;
15304     }
15305
15306     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15307 }
15308
15309 void
15310 AcceptEvent ()
15311 {
15312     /* Accept a pending offer of any kind from opponent */
15313
15314     if (appData.icsActive) {
15315         SendToICS(ics_prefix);
15316         SendToICS("accept\n");
15317     } else if (cmailMsgLoaded) {
15318         if (currentMove == cmailOldMove &&
15319             commentList[cmailOldMove] != NULL &&
15320             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15321                    "Black offers a draw" : "White offers a draw")) {
15322             TruncateGame();
15323             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15324             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15325         } else {
15326             DisplayError(_("There is no pending offer on this move"), 0);
15327             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15328         }
15329     } else {
15330         /* Not used for offers from chess program */
15331     }
15332 }
15333
15334 void
15335 DeclineEvent ()
15336 {
15337     /* Decline a pending offer of any kind from opponent */
15338
15339     if (appData.icsActive) {
15340         SendToICS(ics_prefix);
15341         SendToICS("decline\n");
15342     } else if (cmailMsgLoaded) {
15343         if (currentMove == cmailOldMove &&
15344             commentList[cmailOldMove] != NULL &&
15345             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15346                    "Black offers a draw" : "White offers a draw")) {
15347 #ifdef NOTDEF
15348             AppendComment(cmailOldMove, "Draw declined", TRUE);
15349             DisplayComment(cmailOldMove - 1, "Draw declined");
15350 #endif /*NOTDEF*/
15351         } else {
15352             DisplayError(_("There is no pending offer on this move"), 0);
15353         }
15354     } else {
15355         /* Not used for offers from chess program */
15356     }
15357 }
15358
15359 void
15360 RematchEvent ()
15361 {
15362     /* Issue ICS rematch command */
15363     if (appData.icsActive) {
15364         SendToICS(ics_prefix);
15365         SendToICS("rematch\n");
15366     }
15367 }
15368
15369 void
15370 CallFlagEvent ()
15371 {
15372     /* Call your opponent's flag (claim a win on time) */
15373     if (appData.icsActive) {
15374         SendToICS(ics_prefix);
15375         SendToICS("flag\n");
15376     } else {
15377         switch (gameMode) {
15378           default:
15379             return;
15380           case MachinePlaysWhite:
15381             if (whiteFlag) {
15382                 if (blackFlag)
15383                   GameEnds(GameIsDrawn, "Both players ran out of time",
15384                            GE_PLAYER);
15385                 else
15386                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15387             } else {
15388                 DisplayError(_("Your opponent is not out of time"), 0);
15389             }
15390             break;
15391           case MachinePlaysBlack:
15392             if (blackFlag) {
15393                 if (whiteFlag)
15394                   GameEnds(GameIsDrawn, "Both players ran out of time",
15395                            GE_PLAYER);
15396                 else
15397                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15398             } else {
15399                 DisplayError(_("Your opponent is not out of time"), 0);
15400             }
15401             break;
15402         }
15403     }
15404 }
15405
15406 void
15407 ClockClick (int which)
15408 {       // [HGM] code moved to back-end from winboard.c
15409         if(which) { // black clock
15410           if (gameMode == EditPosition || gameMode == IcsExamining) {
15411             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15412             SetBlackToPlayEvent();
15413           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15414                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15415           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15416           } else if (shiftKey) {
15417             AdjustClock(which, -1);
15418           } else if (gameMode == IcsPlayingWhite ||
15419                      gameMode == MachinePlaysBlack) {
15420             CallFlagEvent();
15421           }
15422         } else { // white clock
15423           if (gameMode == EditPosition || gameMode == IcsExamining) {
15424             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15425             SetWhiteToPlayEvent();
15426           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15427                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15428           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15429           } else if (shiftKey) {
15430             AdjustClock(which, -1);
15431           } else if (gameMode == IcsPlayingBlack ||
15432                    gameMode == MachinePlaysWhite) {
15433             CallFlagEvent();
15434           }
15435         }
15436 }
15437
15438 void
15439 DrawEvent ()
15440 {
15441     /* Offer draw or accept pending draw offer from opponent */
15442
15443     if (appData.icsActive) {
15444         /* Note: tournament rules require draw offers to be
15445            made after you make your move but before you punch
15446            your clock.  Currently ICS doesn't let you do that;
15447            instead, you immediately punch your clock after making
15448            a move, but you can offer a draw at any time. */
15449
15450         SendToICS(ics_prefix);
15451         SendToICS("draw\n");
15452         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15453     } else if (cmailMsgLoaded) {
15454         if (currentMove == cmailOldMove &&
15455             commentList[cmailOldMove] != NULL &&
15456             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15457                    "Black offers a draw" : "White offers a draw")) {
15458             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15460         } else if (currentMove == cmailOldMove + 1) {
15461             char *offer = WhiteOnMove(cmailOldMove) ?
15462               "White offers a draw" : "Black offers a draw";
15463             AppendComment(currentMove, offer, TRUE);
15464             DisplayComment(currentMove - 1, offer);
15465             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15466         } else {
15467             DisplayError(_("You must make your move before offering a draw"), 0);
15468             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15469         }
15470     } else if (first.offeredDraw) {
15471         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15472     } else {
15473         if (first.sendDrawOffers) {
15474             SendToProgram("draw\n", &first);
15475             userOfferedDraw = TRUE;
15476         }
15477     }
15478 }
15479
15480 void
15481 AdjournEvent ()
15482 {
15483     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15484
15485     if (appData.icsActive) {
15486         SendToICS(ics_prefix);
15487         SendToICS("adjourn\n");
15488     } else {
15489         /* Currently GNU Chess doesn't offer or accept Adjourns */
15490     }
15491 }
15492
15493
15494 void
15495 AbortEvent ()
15496 {
15497     /* Offer Abort or accept pending Abort offer from opponent */
15498
15499     if (appData.icsActive) {
15500         SendToICS(ics_prefix);
15501         SendToICS("abort\n");
15502     } else {
15503         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15504     }
15505 }
15506
15507 void
15508 ResignEvent ()
15509 {
15510     /* Resign.  You can do this even if it's not your turn. */
15511
15512     if (appData.icsActive) {
15513         SendToICS(ics_prefix);
15514         SendToICS("resign\n");
15515     } else {
15516         switch (gameMode) {
15517           case MachinePlaysWhite:
15518             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15519             break;
15520           case MachinePlaysBlack:
15521             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15522             break;
15523           case EditGame:
15524             if (cmailMsgLoaded) {
15525                 TruncateGame();
15526                 if (WhiteOnMove(cmailOldMove)) {
15527                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15528                 } else {
15529                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15530                 }
15531                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15532             }
15533             break;
15534           default:
15535             break;
15536         }
15537     }
15538 }
15539
15540
15541 void
15542 StopObservingEvent ()
15543 {
15544     /* Stop observing current games */
15545     SendToICS(ics_prefix);
15546     SendToICS("unobserve\n");
15547 }
15548
15549 void
15550 StopExaminingEvent ()
15551 {
15552     /* Stop observing current game */
15553     SendToICS(ics_prefix);
15554     SendToICS("unexamine\n");
15555 }
15556
15557 void
15558 ForwardInner (int target)
15559 {
15560     int limit; int oldSeekGraphUp = seekGraphUp;
15561
15562     if (appData.debugMode)
15563         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15564                 target, currentMove, forwardMostMove);
15565
15566     if (gameMode == EditPosition)
15567       return;
15568
15569     seekGraphUp = FALSE;
15570     MarkTargetSquares(1);
15571     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15572
15573     if (gameMode == PlayFromGameFile && !pausing)
15574       PauseEvent();
15575
15576     if (gameMode == IcsExamining && pausing)
15577       limit = pauseExamForwardMostMove;
15578     else
15579       limit = forwardMostMove;
15580
15581     if (target > limit) target = limit;
15582
15583     if (target > 0 && moveList[target - 1][0]) {
15584         int fromX, fromY, toX, toY;
15585         toX = moveList[target - 1][2] - AAA;
15586         toY = moveList[target - 1][3] - ONE;
15587         if (moveList[target - 1][1] == '@') {
15588             if (appData.highlightLastMove) {
15589                 SetHighlights(-1, -1, toX, toY);
15590             }
15591         } else {
15592             int viaX = moveList[target - 1][5] - AAA;
15593             int viaY = moveList[target - 1][6] - ONE;
15594             fromX = moveList[target - 1][0] - AAA;
15595             fromY = moveList[target - 1][1] - ONE;
15596             if (target == currentMove + 1) {
15597                 if(moveList[target - 1][4] == ';') { // multi-leg
15598                     ChessSquare piece = boards[currentMove][viaY][viaX];
15599                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15600                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15601                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15602                     boards[currentMove][viaY][viaX] = piece;
15603                 } else
15604                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15605             }
15606             if (appData.highlightLastMove) {
15607                 SetHighlights(fromX, fromY, toX, toY);
15608             }
15609         }
15610     }
15611     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15612         gameMode == Training || gameMode == PlayFromGameFile ||
15613         gameMode == AnalyzeFile) {
15614         while (currentMove < target) {
15615             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15616             SendMoveToProgram(currentMove++, &first);
15617         }
15618     } else {
15619         currentMove = target;
15620     }
15621
15622     if (gameMode == EditGame || gameMode == EndOfGame) {
15623         whiteTimeRemaining = timeRemaining[0][currentMove];
15624         blackTimeRemaining = timeRemaining[1][currentMove];
15625     }
15626     DisplayBothClocks();
15627     DisplayMove(currentMove - 1);
15628     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15629     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15630     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15631         DisplayComment(currentMove - 1, commentList[currentMove]);
15632     }
15633     ClearMap(); // [HGM] exclude: invalidate map
15634 }
15635
15636
15637 void
15638 ForwardEvent ()
15639 {
15640     if (gameMode == IcsExamining && !pausing) {
15641         SendToICS(ics_prefix);
15642         SendToICS("forward\n");
15643     } else {
15644         ForwardInner(currentMove + 1);
15645     }
15646 }
15647
15648 void
15649 ToEndEvent ()
15650 {
15651     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15652         /* to optimze, we temporarily turn off analysis mode while we feed
15653          * the remaining moves to the engine. Otherwise we get analysis output
15654          * after each move.
15655          */
15656         if (first.analysisSupport) {
15657           SendToProgram("exit\nforce\n", &first);
15658           first.analyzing = FALSE;
15659         }
15660     }
15661
15662     if (gameMode == IcsExamining && !pausing) {
15663         SendToICS(ics_prefix);
15664         SendToICS("forward 999999\n");
15665     } else {
15666         ForwardInner(forwardMostMove);
15667     }
15668
15669     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15670         /* we have fed all the moves, so reactivate analysis mode */
15671         SendToProgram("analyze\n", &first);
15672         first.analyzing = TRUE;
15673         /*first.maybeThinking = TRUE;*/
15674         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15675     }
15676 }
15677
15678 void
15679 BackwardInner (int target)
15680 {
15681     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15682
15683     if (appData.debugMode)
15684         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15685                 target, currentMove, forwardMostMove);
15686
15687     if (gameMode == EditPosition) return;
15688     seekGraphUp = FALSE;
15689     MarkTargetSquares(1);
15690     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15691     if (currentMove <= backwardMostMove) {
15692         ClearHighlights();
15693         DrawPosition(full_redraw, boards[currentMove]);
15694         return;
15695     }
15696     if (gameMode == PlayFromGameFile && !pausing)
15697       PauseEvent();
15698
15699     if (moveList[target][0]) {
15700         int fromX, fromY, toX, toY;
15701         toX = moveList[target][2] - AAA;
15702         toY = moveList[target][3] - ONE;
15703         if (moveList[target][1] == '@') {
15704             if (appData.highlightLastMove) {
15705                 SetHighlights(-1, -1, toX, toY);
15706             }
15707         } else {
15708             fromX = moveList[target][0] - AAA;
15709             fromY = moveList[target][1] - ONE;
15710             if (target == currentMove - 1) {
15711                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15712             }
15713             if (appData.highlightLastMove) {
15714                 SetHighlights(fromX, fromY, toX, toY);
15715             }
15716         }
15717     }
15718     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15719         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15720         while (currentMove > target) {
15721             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15722                 // null move cannot be undone. Reload program with move history before it.
15723                 int i;
15724                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15725                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15726                 }
15727                 SendBoard(&first, i);
15728               if(second.analyzing) SendBoard(&second, i);
15729                 for(currentMove=i; currentMove<target; currentMove++) {
15730                     SendMoveToProgram(currentMove, &first);
15731                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15732                 }
15733                 break;
15734             }
15735             SendToBoth("undo\n");
15736             currentMove--;
15737         }
15738     } else {
15739         currentMove = target;
15740     }
15741
15742     if (gameMode == EditGame || gameMode == EndOfGame) {
15743         whiteTimeRemaining = timeRemaining[0][currentMove];
15744         blackTimeRemaining = timeRemaining[1][currentMove];
15745     }
15746     DisplayBothClocks();
15747     DisplayMove(currentMove - 1);
15748     DrawPosition(full_redraw, boards[currentMove]);
15749     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15750     // [HGM] PV info: routine tests if comment empty
15751     DisplayComment(currentMove - 1, commentList[currentMove]);
15752     ClearMap(); // [HGM] exclude: invalidate map
15753 }
15754
15755 void
15756 BackwardEvent ()
15757 {
15758     if (gameMode == IcsExamining && !pausing) {
15759         SendToICS(ics_prefix);
15760         SendToICS("backward\n");
15761     } else {
15762         BackwardInner(currentMove - 1);
15763     }
15764 }
15765
15766 void
15767 ToStartEvent ()
15768 {
15769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15770         /* to optimize, we temporarily turn off analysis mode while we undo
15771          * all the moves. Otherwise we get analysis output after each undo.
15772          */
15773         if (first.analysisSupport) {
15774           SendToProgram("exit\nforce\n", &first);
15775           first.analyzing = FALSE;
15776         }
15777     }
15778
15779     if (gameMode == IcsExamining && !pausing) {
15780         SendToICS(ics_prefix);
15781         SendToICS("backward 999999\n");
15782     } else {
15783         BackwardInner(backwardMostMove);
15784     }
15785
15786     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15787         /* we have fed all the moves, so reactivate analysis mode */
15788         SendToProgram("analyze\n", &first);
15789         first.analyzing = TRUE;
15790         /*first.maybeThinking = TRUE;*/
15791         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15792     }
15793 }
15794
15795 void
15796 ToNrEvent (int to)
15797 {
15798   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15799   if (to >= forwardMostMove) to = forwardMostMove;
15800   if (to <= backwardMostMove) to = backwardMostMove;
15801   if (to < currentMove) {
15802     BackwardInner(to);
15803   } else {
15804     ForwardInner(to);
15805   }
15806 }
15807
15808 void
15809 RevertEvent (Boolean annotate)
15810 {
15811     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15812         return;
15813     }
15814     if (gameMode != IcsExamining) {
15815         DisplayError(_("You are not examining a game"), 0);
15816         return;
15817     }
15818     if (pausing) {
15819         DisplayError(_("You can't revert while pausing"), 0);
15820         return;
15821     }
15822     SendToICS(ics_prefix);
15823     SendToICS("revert\n");
15824 }
15825
15826 void
15827 RetractMoveEvent ()
15828 {
15829     switch (gameMode) {
15830       case MachinePlaysWhite:
15831       case MachinePlaysBlack:
15832         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15833             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15834             return;
15835         }
15836         if (forwardMostMove < 2) return;
15837         currentMove = forwardMostMove = forwardMostMove - 2;
15838         whiteTimeRemaining = timeRemaining[0][currentMove];
15839         blackTimeRemaining = timeRemaining[1][currentMove];
15840         DisplayBothClocks();
15841         DisplayMove(currentMove - 1);
15842         ClearHighlights();/*!! could figure this out*/
15843         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15844         SendToProgram("remove\n", &first);
15845         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15846         break;
15847
15848       case BeginningOfGame:
15849       default:
15850         break;
15851
15852       case IcsPlayingWhite:
15853       case IcsPlayingBlack:
15854         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15855             SendToICS(ics_prefix);
15856             SendToICS("takeback 2\n");
15857         } else {
15858             SendToICS(ics_prefix);
15859             SendToICS("takeback 1\n");
15860         }
15861         break;
15862     }
15863 }
15864
15865 void
15866 MoveNowEvent ()
15867 {
15868     ChessProgramState *cps;
15869
15870     switch (gameMode) {
15871       case MachinePlaysWhite:
15872         if (!WhiteOnMove(forwardMostMove)) {
15873             DisplayError(_("It is your turn"), 0);
15874             return;
15875         }
15876         cps = &first;
15877         break;
15878       case MachinePlaysBlack:
15879         if (WhiteOnMove(forwardMostMove)) {
15880             DisplayError(_("It is your turn"), 0);
15881             return;
15882         }
15883         cps = &first;
15884         break;
15885       case TwoMachinesPlay:
15886         if (WhiteOnMove(forwardMostMove) ==
15887             (first.twoMachinesColor[0] == 'w')) {
15888             cps = &first;
15889         } else {
15890             cps = &second;
15891         }
15892         break;
15893       case BeginningOfGame:
15894       default:
15895         return;
15896     }
15897     SendToProgram("?\n", cps);
15898 }
15899
15900 void
15901 TruncateGameEvent ()
15902 {
15903     EditGameEvent();
15904     if (gameMode != EditGame) return;
15905     TruncateGame();
15906 }
15907
15908 void
15909 TruncateGame ()
15910 {
15911     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15912     if (forwardMostMove > currentMove) {
15913         if (gameInfo.resultDetails != NULL) {
15914             free(gameInfo.resultDetails);
15915             gameInfo.resultDetails = NULL;
15916             gameInfo.result = GameUnfinished;
15917         }
15918         forwardMostMove = currentMove;
15919         HistorySet(parseList, backwardMostMove, forwardMostMove,
15920                    currentMove-1);
15921     }
15922 }
15923
15924 void
15925 HintEvent ()
15926 {
15927     if (appData.noChessProgram) return;
15928     switch (gameMode) {
15929       case MachinePlaysWhite:
15930         if (WhiteOnMove(forwardMostMove)) {
15931             DisplayError(_("Wait until your turn."), 0);
15932             return;
15933         }
15934         break;
15935       case BeginningOfGame:
15936       case MachinePlaysBlack:
15937         if (!WhiteOnMove(forwardMostMove)) {
15938             DisplayError(_("Wait until your turn."), 0);
15939             return;
15940         }
15941         break;
15942       default:
15943         DisplayError(_("No hint available"), 0);
15944         return;
15945     }
15946     SendToProgram("hint\n", &first);
15947     hintRequested = TRUE;
15948 }
15949
15950 int
15951 SaveSelected (FILE *g, int dummy, char *dummy2)
15952 {
15953     ListGame * lg = (ListGame *) gameList.head;
15954     int nItem, cnt=0;
15955     FILE *f;
15956
15957     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15958         DisplayError(_("Game list not loaded or empty"), 0);
15959         return 0;
15960     }
15961
15962     creatingBook = TRUE; // suppresses stuff during load game
15963
15964     /* Get list size */
15965     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15966         if(lg->position >= 0) { // selected?
15967             LoadGame(f, nItem, "", TRUE);
15968             SaveGamePGN2(g); // leaves g open
15969             cnt++; DoEvents();
15970         }
15971         lg = (ListGame *) lg->node.succ;
15972     }
15973
15974     fclose(g);
15975     creatingBook = FALSE;
15976
15977     return cnt;
15978 }
15979
15980 void
15981 CreateBookEvent ()
15982 {
15983     ListGame * lg = (ListGame *) gameList.head;
15984     FILE *f, *g;
15985     int nItem;
15986     static int secondTime = FALSE;
15987
15988     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15989         DisplayError(_("Game list not loaded or empty"), 0);
15990         return;
15991     }
15992
15993     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15994         fclose(g);
15995         secondTime++;
15996         DisplayNote(_("Book file exists! Try again for overwrite."));
15997         return;
15998     }
15999
16000     creatingBook = TRUE;
16001     secondTime = FALSE;
16002
16003     /* Get list size */
16004     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16005         if(lg->position >= 0) {
16006             LoadGame(f, nItem, "", TRUE);
16007             AddGameToBook(TRUE);
16008             DoEvents();
16009         }
16010         lg = (ListGame *) lg->node.succ;
16011     }
16012
16013     creatingBook = FALSE;
16014     FlushBook();
16015 }
16016
16017 void
16018 BookEvent ()
16019 {
16020     if (appData.noChessProgram) return;
16021     switch (gameMode) {
16022       case MachinePlaysWhite:
16023         if (WhiteOnMove(forwardMostMove)) {
16024             DisplayError(_("Wait until your turn."), 0);
16025             return;
16026         }
16027         break;
16028       case BeginningOfGame:
16029       case MachinePlaysBlack:
16030         if (!WhiteOnMove(forwardMostMove)) {
16031             DisplayError(_("Wait until your turn."), 0);
16032             return;
16033         }
16034         break;
16035       case EditPosition:
16036         EditPositionDone(TRUE);
16037         break;
16038       case TwoMachinesPlay:
16039         return;
16040       default:
16041         break;
16042     }
16043     SendToProgram("bk\n", &first);
16044     bookOutput[0] = NULLCHAR;
16045     bookRequested = TRUE;
16046 }
16047
16048 void
16049 AboutGameEvent ()
16050 {
16051     char *tags = PGNTags(&gameInfo);
16052     TagsPopUp(tags, CmailMsg());
16053     free(tags);
16054 }
16055
16056 /* end button procedures */
16057
16058 void
16059 PrintPosition (FILE *fp, int move)
16060 {
16061     int i, j;
16062
16063     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16064         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16065             char c = PieceToChar(boards[move][i][j]);
16066             fputc(c == 'x' ? '.' : c, fp);
16067             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16068         }
16069     }
16070     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16071       fprintf(fp, "white to play\n");
16072     else
16073       fprintf(fp, "black to play\n");
16074 }
16075
16076 void
16077 PrintOpponents (FILE *fp)
16078 {
16079     if (gameInfo.white != NULL) {
16080         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16081     } else {
16082         fprintf(fp, "\n");
16083     }
16084 }
16085
16086 /* Find last component of program's own name, using some heuristics */
16087 void
16088 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16089 {
16090     char *p, *q, c;
16091     int local = (strcmp(host, "localhost") == 0);
16092     while (!local && (p = strchr(prog, ';')) != NULL) {
16093         p++;
16094         while (*p == ' ') p++;
16095         prog = p;
16096     }
16097     if (*prog == '"' || *prog == '\'') {
16098         q = strchr(prog + 1, *prog);
16099     } else {
16100         q = strchr(prog, ' ');
16101     }
16102     if (q == NULL) q = prog + strlen(prog);
16103     p = q;
16104     while (p >= prog && *p != '/' && *p != '\\') p--;
16105     p++;
16106     if(p == prog && *p == '"') p++;
16107     c = *q; *q = 0;
16108     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16109     memcpy(buf, p, q - p);
16110     buf[q - p] = NULLCHAR;
16111     if (!local) {
16112         strcat(buf, "@");
16113         strcat(buf, host);
16114     }
16115 }
16116
16117 char *
16118 TimeControlTagValue ()
16119 {
16120     char buf[MSG_SIZ];
16121     if (!appData.clockMode) {
16122       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16123     } else if (movesPerSession > 0) {
16124       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16125     } else if (timeIncrement == 0) {
16126       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16127     } else {
16128       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16129     }
16130     return StrSave(buf);
16131 }
16132
16133 void
16134 SetGameInfo ()
16135 {
16136     /* This routine is used only for certain modes */
16137     VariantClass v = gameInfo.variant;
16138     ChessMove r = GameUnfinished;
16139     char *p = NULL;
16140
16141     if(keepInfo) return;
16142
16143     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16144         r = gameInfo.result;
16145         p = gameInfo.resultDetails;
16146         gameInfo.resultDetails = NULL;
16147     }
16148     ClearGameInfo(&gameInfo);
16149     gameInfo.variant = v;
16150
16151     switch (gameMode) {
16152       case MachinePlaysWhite:
16153         gameInfo.event = StrSave( appData.pgnEventHeader );
16154         gameInfo.site = StrSave(HostName());
16155         gameInfo.date = PGNDate();
16156         gameInfo.round = StrSave("-");
16157         gameInfo.white = StrSave(first.tidy);
16158         gameInfo.black = StrSave(UserName());
16159         gameInfo.timeControl = TimeControlTagValue();
16160         break;
16161
16162       case MachinePlaysBlack:
16163         gameInfo.event = StrSave( appData.pgnEventHeader );
16164         gameInfo.site = StrSave(HostName());
16165         gameInfo.date = PGNDate();
16166         gameInfo.round = StrSave("-");
16167         gameInfo.white = StrSave(UserName());
16168         gameInfo.black = StrSave(first.tidy);
16169         gameInfo.timeControl = TimeControlTagValue();
16170         break;
16171
16172       case TwoMachinesPlay:
16173         gameInfo.event = StrSave( appData.pgnEventHeader );
16174         gameInfo.site = StrSave(HostName());
16175         gameInfo.date = PGNDate();
16176         if (roundNr > 0) {
16177             char buf[MSG_SIZ];
16178             snprintf(buf, MSG_SIZ, "%d", roundNr);
16179             gameInfo.round = StrSave(buf);
16180         } else {
16181             gameInfo.round = StrSave("-");
16182         }
16183         if (first.twoMachinesColor[0] == 'w') {
16184             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16185             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16186         } else {
16187             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16188             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16189         }
16190         gameInfo.timeControl = TimeControlTagValue();
16191         break;
16192
16193       case EditGame:
16194         gameInfo.event = StrSave("Edited game");
16195         gameInfo.site = StrSave(HostName());
16196         gameInfo.date = PGNDate();
16197         gameInfo.round = StrSave("-");
16198         gameInfo.white = StrSave("-");
16199         gameInfo.black = StrSave("-");
16200         gameInfo.result = r;
16201         gameInfo.resultDetails = p;
16202         break;
16203
16204       case EditPosition:
16205         gameInfo.event = StrSave("Edited position");
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       case IcsPlayingWhite:
16214       case IcsPlayingBlack:
16215       case IcsObserving:
16216       case IcsExamining:
16217         break;
16218
16219       case PlayFromGameFile:
16220         gameInfo.event = StrSave("Game from non-PGN file");
16221         gameInfo.site = StrSave(HostName());
16222         gameInfo.date = PGNDate();
16223         gameInfo.round = StrSave("-");
16224         gameInfo.white = StrSave("?");
16225         gameInfo.black = StrSave("?");
16226         break;
16227
16228       default:
16229         break;
16230     }
16231 }
16232
16233 void
16234 ReplaceComment (int index, char *text)
16235 {
16236     int len;
16237     char *p;
16238     float score;
16239
16240     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16241        pvInfoList[index-1].depth == len &&
16242        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16243        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16244     while (*text == '\n') text++;
16245     len = strlen(text);
16246     while (len > 0 && text[len - 1] == '\n') len--;
16247
16248     if (commentList[index] != NULL)
16249       free(commentList[index]);
16250
16251     if (len == 0) {
16252         commentList[index] = NULL;
16253         return;
16254     }
16255   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16256       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16257       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16258     commentList[index] = (char *) malloc(len + 2);
16259     strncpy(commentList[index], text, len);
16260     commentList[index][len] = '\n';
16261     commentList[index][len + 1] = NULLCHAR;
16262   } else {
16263     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16264     char *p;
16265     commentList[index] = (char *) malloc(len + 7);
16266     safeStrCpy(commentList[index], "{\n", 3);
16267     safeStrCpy(commentList[index]+2, text, len+1);
16268     commentList[index][len+2] = NULLCHAR;
16269     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16270     strcat(commentList[index], "\n}\n");
16271   }
16272 }
16273
16274 void
16275 CrushCRs (char *text)
16276 {
16277   char *p = text;
16278   char *q = text;
16279   char ch;
16280
16281   do {
16282     ch = *p++;
16283     if (ch == '\r') continue;
16284     *q++ = ch;
16285   } while (ch != '\0');
16286 }
16287
16288 void
16289 AppendComment (int index, char *text, Boolean addBraces)
16290 /* addBraces  tells if we should add {} */
16291 {
16292     int oldlen, len;
16293     char *old;
16294
16295 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16296     if(addBraces == 3) addBraces = 0; else // force appending literally
16297     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16298
16299     CrushCRs(text);
16300     while (*text == '\n') text++;
16301     len = strlen(text);
16302     while (len > 0 && text[len - 1] == '\n') len--;
16303     text[len] = NULLCHAR;
16304
16305     if (len == 0) return;
16306
16307     if (commentList[index] != NULL) {
16308       Boolean addClosingBrace = addBraces;
16309         old = commentList[index];
16310         oldlen = strlen(old);
16311         while(commentList[index][oldlen-1] ==  '\n')
16312           commentList[index][--oldlen] = NULLCHAR;
16313         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16314         safeStrCpy(commentList[index], old, oldlen + len + 6);
16315         free(old);
16316         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16317         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16318           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16319           while (*text == '\n') { text++; len--; }
16320           commentList[index][--oldlen] = NULLCHAR;
16321       }
16322         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16323         else          strcat(commentList[index], "\n");
16324         strcat(commentList[index], text);
16325         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16326         else          strcat(commentList[index], "\n");
16327     } else {
16328         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16329         if(addBraces)
16330           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16331         else commentList[index][0] = NULLCHAR;
16332         strcat(commentList[index], text);
16333         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16334         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16335     }
16336 }
16337
16338 static char *
16339 FindStr (char * text, char * sub_text)
16340 {
16341     char * result = strstr( text, sub_text );
16342
16343     if( result != NULL ) {
16344         result += strlen( sub_text );
16345     }
16346
16347     return result;
16348 }
16349
16350 /* [AS] Try to extract PV info from PGN comment */
16351 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16352 char *
16353 GetInfoFromComment (int index, char * text)
16354 {
16355     char * sep = text, *p;
16356
16357     if( text != NULL && index > 0 ) {
16358         int score = 0;
16359         int depth = 0;
16360         int time = -1, sec = 0, deci;
16361         char * s_eval = FindStr( text, "[%eval " );
16362         char * s_emt = FindStr( text, "[%emt " );
16363 #if 0
16364         if( s_eval != NULL || s_emt != NULL ) {
16365 #else
16366         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16367 #endif
16368             /* New style */
16369             char delim;
16370
16371             if( s_eval != NULL ) {
16372                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16373                     return text;
16374                 }
16375
16376                 if( delim != ']' ) {
16377                     return text;
16378                 }
16379             }
16380
16381             if( s_emt != NULL ) {
16382             }
16383                 return text;
16384         }
16385         else {
16386             /* We expect something like: [+|-]nnn.nn/dd */
16387             int score_lo = 0;
16388
16389             if(*text != '{') return text; // [HGM] braces: must be normal comment
16390
16391             sep = strchr( text, '/' );
16392             if( sep == NULL || sep < (text+4) ) {
16393                 return text;
16394             }
16395
16396             p = text;
16397             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16398             if(p[1] == '(') { // comment starts with PV
16399                p = strchr(p, ')'); // locate end of PV
16400                if(p == NULL || sep < p+5) return text;
16401                // at this point we have something like "{(.*) +0.23/6 ..."
16402                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16403                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16404                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16405             }
16406             time = -1; sec = -1; deci = -1;
16407             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16408                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16409                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16410                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16411                 return text;
16412             }
16413
16414             if( score_lo < 0 || score_lo >= 100 ) {
16415                 return text;
16416             }
16417
16418             if(sec >= 0) time = 600*time + 10*sec; else
16419             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16420
16421             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16422
16423             /* [HGM] PV time: now locate end of PV info */
16424             while( *++sep >= '0' && *sep <= '9'); // strip depth
16425             if(time >= 0)
16426             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16427             if(sec >= 0)
16428             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16429             if(deci >= 0)
16430             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16431             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16432         }
16433
16434         if( depth <= 0 ) {
16435             return text;
16436         }
16437
16438         if( time < 0 ) {
16439             time = -1;
16440         }
16441
16442         pvInfoList[index-1].depth = depth;
16443         pvInfoList[index-1].score = score;
16444         pvInfoList[index-1].time  = 10*time; // centi-sec
16445         if(*sep == '}') *sep = 0; else *--sep = '{';
16446         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16447     }
16448     return sep;
16449 }
16450
16451 void
16452 SendToProgram (char *message, ChessProgramState *cps)
16453 {
16454     int count, outCount, error;
16455     char buf[MSG_SIZ];
16456
16457     if (cps->pr == NoProc) return;
16458     Attention(cps);
16459
16460     if (appData.debugMode) {
16461         TimeMark now;
16462         GetTimeMark(&now);
16463         fprintf(debugFP, "%ld >%-6s: %s",
16464                 SubtractTimeMarks(&now, &programStartTime),
16465                 cps->which, message);
16466         if(serverFP)
16467             fprintf(serverFP, "%ld >%-6s: %s",
16468                 SubtractTimeMarks(&now, &programStartTime),
16469                 cps->which, message), fflush(serverFP);
16470     }
16471
16472     count = strlen(message);
16473     outCount = OutputToProcess(cps->pr, message, count, &error);
16474     if (outCount < count && !exiting
16475                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16476       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16477       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16478         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16479             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16480                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16481                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16482                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16483             } else {
16484                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16485                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16486                 gameInfo.result = res;
16487             }
16488             gameInfo.resultDetails = StrSave(buf);
16489         }
16490         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16491         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16492     }
16493 }
16494
16495 void
16496 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16497 {
16498     char *end_str;
16499     char buf[MSG_SIZ];
16500     ChessProgramState *cps = (ChessProgramState *)closure;
16501
16502     if (isr != cps->isr) return; /* Killed intentionally */
16503     if (count <= 0) {
16504         if (count == 0) {
16505             RemoveInputSource(cps->isr);
16506             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16507                     _(cps->which), cps->program);
16508             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16509             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16510                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16511                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16512                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16513                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16514                 } else {
16515                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16516                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16517                     gameInfo.result = res;
16518                 }
16519                 gameInfo.resultDetails = StrSave(buf);
16520             }
16521             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16522             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16523         } else {
16524             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16525                     _(cps->which), cps->program);
16526             RemoveInputSource(cps->isr);
16527
16528             /* [AS] Program is misbehaving badly... kill it */
16529             if( count == -2 ) {
16530                 DestroyChildProcess( cps->pr, 9 );
16531                 cps->pr = NoProc;
16532             }
16533
16534             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16535         }
16536         return;
16537     }
16538
16539     if ((end_str = strchr(message, '\r')) != NULL)
16540       *end_str = NULLCHAR;
16541     if ((end_str = strchr(message, '\n')) != NULL)
16542       *end_str = NULLCHAR;
16543
16544     if (appData.debugMode) {
16545         TimeMark now; int print = 1;
16546         char *quote = ""; char c; int i;
16547
16548         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16549                 char start = message[0];
16550                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16551                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16552                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16553                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16554                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16555                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16556                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16557                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16558                    sscanf(message, "hint: %c", &c)!=1 &&
16559                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16560                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16561                     print = (appData.engineComments >= 2);
16562                 }
16563                 message[0] = start; // restore original message
16564         }
16565         if(print) {
16566                 GetTimeMark(&now);
16567                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16568                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16569                         quote,
16570                         message);
16571                 if(serverFP)
16572                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16573                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16574                         quote,
16575                         message), fflush(serverFP);
16576         }
16577     }
16578
16579     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16580     if (appData.icsEngineAnalyze) {
16581         if (strstr(message, "whisper") != NULL ||
16582              strstr(message, "kibitz") != NULL ||
16583             strstr(message, "tellics") != NULL) return;
16584     }
16585
16586     HandleMachineMove(message, cps);
16587 }
16588
16589
16590 void
16591 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16592 {
16593     char buf[MSG_SIZ];
16594     int seconds;
16595
16596     if( timeControl_2 > 0 ) {
16597         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16598             tc = timeControl_2;
16599         }
16600     }
16601     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16602     inc /= cps->timeOdds;
16603     st  /= cps->timeOdds;
16604
16605     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16606
16607     if (st > 0) {
16608       /* Set exact time per move, normally using st command */
16609       if (cps->stKludge) {
16610         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16611         seconds = st % 60;
16612         if (seconds == 0) {
16613           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16614         } else {
16615           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16616         }
16617       } else {
16618         snprintf(buf, MSG_SIZ, "st %d\n", st);
16619       }
16620     } else {
16621       /* Set conventional or incremental time control, using level command */
16622       if (seconds == 0) {
16623         /* Note old gnuchess bug -- minutes:seconds used to not work.
16624            Fixed in later versions, but still avoid :seconds
16625            when seconds is 0. */
16626         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16627       } else {
16628         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16629                  seconds, inc/1000.);
16630       }
16631     }
16632     SendToProgram(buf, cps);
16633
16634     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16635     /* Orthogonally, limit search to given depth */
16636     if (sd > 0) {
16637       if (cps->sdKludge) {
16638         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16639       } else {
16640         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16641       }
16642       SendToProgram(buf, cps);
16643     }
16644
16645     if(cps->nps >= 0) { /* [HGM] nps */
16646         if(cps->supportsNPS == FALSE)
16647           cps->nps = -1; // don't use if engine explicitly says not supported!
16648         else {
16649           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16650           SendToProgram(buf, cps);
16651         }
16652     }
16653 }
16654
16655 ChessProgramState *
16656 WhitePlayer ()
16657 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16658 {
16659     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16660        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16661         return &second;
16662     return &first;
16663 }
16664
16665 void
16666 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16667 {
16668     char message[MSG_SIZ];
16669     long time, otime;
16670
16671     /* Note: this routine must be called when the clocks are stopped
16672        or when they have *just* been set or switched; otherwise
16673        it will be off by the time since the current tick started.
16674     */
16675     if (machineWhite) {
16676         time = whiteTimeRemaining / 10;
16677         otime = blackTimeRemaining / 10;
16678     } else {
16679         time = blackTimeRemaining / 10;
16680         otime = whiteTimeRemaining / 10;
16681     }
16682     /* [HGM] translate opponent's time by time-odds factor */
16683     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16684
16685     if (time <= 0) time = 1;
16686     if (otime <= 0) otime = 1;
16687
16688     snprintf(message, MSG_SIZ, "time %ld\n", time);
16689     SendToProgram(message, cps);
16690
16691     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16692     SendToProgram(message, cps);
16693 }
16694
16695 char *
16696 EngineDefinedVariant (ChessProgramState *cps, int n)
16697 {   // return name of n-th unknown variant that engine supports
16698     static char buf[MSG_SIZ];
16699     char *p, *s = cps->variants;
16700     if(!s) return NULL;
16701     do { // parse string from variants feature
16702       VariantClass v;
16703         p = strchr(s, ',');
16704         if(p) *p = NULLCHAR;
16705       v = StringToVariant(s);
16706       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16707         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16708             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16709                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16710                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16711                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16712             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16713         }
16714         if(p) *p++ = ',';
16715         if(n < 0) return buf;
16716     } while(s = p);
16717     return NULL;
16718 }
16719
16720 int
16721 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16722 {
16723   char buf[MSG_SIZ];
16724   int len = strlen(name);
16725   int val;
16726
16727   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16728     (*p) += len + 1;
16729     sscanf(*p, "%d", &val);
16730     *loc = (val != 0);
16731     while (**p && **p != ' ')
16732       (*p)++;
16733     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16734     SendToProgram(buf, cps);
16735     return TRUE;
16736   }
16737   return FALSE;
16738 }
16739
16740 int
16741 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16742 {
16743   char buf[MSG_SIZ];
16744   int len = strlen(name);
16745   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16746     (*p) += len + 1;
16747     sscanf(*p, "%d", loc);
16748     while (**p && **p != ' ') (*p)++;
16749     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16750     SendToProgram(buf, cps);
16751     return TRUE;
16752   }
16753   return FALSE;
16754 }
16755
16756 int
16757 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16758 {
16759   char buf[MSG_SIZ];
16760   int len = strlen(name);
16761   if (strncmp((*p), name, len) == 0
16762       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16763     (*p) += len + 2;
16764     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16765     sscanf(*p, "%[^\"]", *loc);
16766     while (**p && **p != '\"') (*p)++;
16767     if (**p == '\"') (*p)++;
16768     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16769     SendToProgram(buf, cps);
16770     return TRUE;
16771   }
16772   return FALSE;
16773 }
16774
16775 int
16776 ParseOption (Option *opt, ChessProgramState *cps)
16777 // [HGM] options: process the string that defines an engine option, and determine
16778 // name, type, default value, and allowed value range
16779 {
16780         char *p, *q, buf[MSG_SIZ];
16781         int n, min = (-1)<<31, max = 1<<31, def;
16782
16783         if(p = strstr(opt->name, " -spin ")) {
16784             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16785             if(max < min) max = min; // enforce consistency
16786             if(def < min) def = min;
16787             if(def > max) def = max;
16788             opt->value = def;
16789             opt->min = min;
16790             opt->max = max;
16791             opt->type = Spin;
16792         } else if((p = strstr(opt->name, " -slider "))) {
16793             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16794             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16795             if(max < min) max = min; // enforce consistency
16796             if(def < min) def = min;
16797             if(def > max) def = max;
16798             opt->value = def;
16799             opt->min = min;
16800             opt->max = max;
16801             opt->type = Spin; // Slider;
16802         } else if((p = strstr(opt->name, " -string "))) {
16803             opt->textValue = p+9;
16804             opt->type = TextBox;
16805         } else if((p = strstr(opt->name, " -file "))) {
16806             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16807             opt->textValue = p+7;
16808             opt->type = FileName; // FileName;
16809         } else if((p = strstr(opt->name, " -path "))) {
16810             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16811             opt->textValue = p+7;
16812             opt->type = PathName; // PathName;
16813         } else if(p = strstr(opt->name, " -check ")) {
16814             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16815             opt->value = (def != 0);
16816             opt->type = CheckBox;
16817         } else if(p = strstr(opt->name, " -combo ")) {
16818             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16819             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16820             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16821             opt->value = n = 0;
16822             while(q = StrStr(q, " /// ")) {
16823                 n++; *q = 0;    // count choices, and null-terminate each of them
16824                 q += 5;
16825                 if(*q == '*') { // remember default, which is marked with * prefix
16826                     q++;
16827                     opt->value = n;
16828                 }
16829                 cps->comboList[cps->comboCnt++] = q;
16830             }
16831             cps->comboList[cps->comboCnt++] = NULL;
16832             opt->max = n + 1;
16833             opt->type = ComboBox;
16834         } else if(p = strstr(opt->name, " -button")) {
16835             opt->type = Button;
16836         } else if(p = strstr(opt->name, " -save")) {
16837             opt->type = SaveButton;
16838         } else return FALSE;
16839         *p = 0; // terminate option name
16840         // now look if the command-line options define a setting for this engine option.
16841         if(cps->optionSettings && cps->optionSettings[0])
16842             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16843         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16844           snprintf(buf, MSG_SIZ, "option %s", p);
16845                 if(p = strstr(buf, ",")) *p = 0;
16846                 if(q = strchr(buf, '=')) switch(opt->type) {
16847                     case ComboBox:
16848                         for(n=0; n<opt->max; n++)
16849                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16850                         break;
16851                     case TextBox:
16852                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16853                         break;
16854                     case Spin:
16855                     case CheckBox:
16856                         opt->value = atoi(q+1);
16857                     default:
16858                         break;
16859                 }
16860                 strcat(buf, "\n");
16861                 SendToProgram(buf, cps);
16862         }
16863         return TRUE;
16864 }
16865
16866 void
16867 FeatureDone (ChessProgramState *cps, int val)
16868 {
16869   DelayedEventCallback cb = GetDelayedEvent();
16870   if ((cb == InitBackEnd3 && cps == &first) ||
16871       (cb == SettingsMenuIfReady && cps == &second) ||
16872       (cb == LoadEngine) ||
16873       (cb == TwoMachinesEventIfReady)) {
16874     CancelDelayedEvent();
16875     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16876   }
16877   cps->initDone = val;
16878   if(val) cps->reload = FALSE;
16879 }
16880
16881 /* Parse feature command from engine */
16882 void
16883 ParseFeatures (char *args, ChessProgramState *cps)
16884 {
16885   char *p = args;
16886   char *q = NULL;
16887   int val;
16888   char buf[MSG_SIZ];
16889
16890   for (;;) {
16891     while (*p == ' ') p++;
16892     if (*p == NULLCHAR) return;
16893
16894     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16895     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16896     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16897     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16898     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16899     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16900     if (BoolFeature(&p, "reuse", &val, cps)) {
16901       /* Engine can disable reuse, but can't enable it if user said no */
16902       if (!val) cps->reuse = FALSE;
16903       continue;
16904     }
16905     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16906     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16907       if (gameMode == TwoMachinesPlay) {
16908         DisplayTwoMachinesTitle();
16909       } else {
16910         DisplayTitle("");
16911       }
16912       continue;
16913     }
16914     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16915     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16916     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16917     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16918     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16919     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16920     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16921     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16922     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16923     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16924     if (IntFeature(&p, "done", &val, cps)) {
16925       FeatureDone(cps, val);
16926       continue;
16927     }
16928     /* Added by Tord: */
16929     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16930     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16931     /* End of additions by Tord */
16932
16933     /* [HGM] added features: */
16934     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16935     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16936     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16937     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16938     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16939     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16940     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16941     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16942         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16943         FREE(cps->option[cps->nrOptions].name);
16944         cps->option[cps->nrOptions].name = q; q = NULL;
16945         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16946           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16947             SendToProgram(buf, cps);
16948             continue;
16949         }
16950         if(cps->nrOptions >= MAX_OPTIONS) {
16951             cps->nrOptions--;
16952             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16953             DisplayError(buf, 0);
16954         }
16955         continue;
16956     }
16957     /* End of additions by HGM */
16958
16959     /* unknown feature: complain and skip */
16960     q = p;
16961     while (*q && *q != '=') q++;
16962     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16963     SendToProgram(buf, cps);
16964     p = q;
16965     if (*p == '=') {
16966       p++;
16967       if (*p == '\"') {
16968         p++;
16969         while (*p && *p != '\"') p++;
16970         if (*p == '\"') p++;
16971       } else {
16972         while (*p && *p != ' ') p++;
16973       }
16974     }
16975   }
16976
16977 }
16978
16979 void
16980 PeriodicUpdatesEvent (int newState)
16981 {
16982     if (newState == appData.periodicUpdates)
16983       return;
16984
16985     appData.periodicUpdates=newState;
16986
16987     /* Display type changes, so update it now */
16988 //    DisplayAnalysis();
16989
16990     /* Get the ball rolling again... */
16991     if (newState) {
16992         AnalysisPeriodicEvent(1);
16993         StartAnalysisClock();
16994     }
16995 }
16996
16997 void
16998 PonderNextMoveEvent (int newState)
16999 {
17000     if (newState == appData.ponderNextMove) return;
17001     if (gameMode == EditPosition) EditPositionDone(TRUE);
17002     if (newState) {
17003         SendToProgram("hard\n", &first);
17004         if (gameMode == TwoMachinesPlay) {
17005             SendToProgram("hard\n", &second);
17006         }
17007     } else {
17008         SendToProgram("easy\n", &first);
17009         thinkOutput[0] = NULLCHAR;
17010         if (gameMode == TwoMachinesPlay) {
17011             SendToProgram("easy\n", &second);
17012         }
17013     }
17014     appData.ponderNextMove = newState;
17015 }
17016
17017 void
17018 NewSettingEvent (int option, int *feature, char *command, int value)
17019 {
17020     char buf[MSG_SIZ];
17021
17022     if (gameMode == EditPosition) EditPositionDone(TRUE);
17023     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17024     if(feature == NULL || *feature) SendToProgram(buf, &first);
17025     if (gameMode == TwoMachinesPlay) {
17026         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17027     }
17028 }
17029
17030 void
17031 ShowThinkingEvent ()
17032 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17033 {
17034     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17035     int newState = appData.showThinking
17036         // [HGM] thinking: other features now need thinking output as well
17037         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17038
17039     if (oldState == newState) return;
17040     oldState = newState;
17041     if (gameMode == EditPosition) EditPositionDone(TRUE);
17042     if (oldState) {
17043         SendToProgram("post\n", &first);
17044         if (gameMode == TwoMachinesPlay) {
17045             SendToProgram("post\n", &second);
17046         }
17047     } else {
17048         SendToProgram("nopost\n", &first);
17049         thinkOutput[0] = NULLCHAR;
17050         if (gameMode == TwoMachinesPlay) {
17051             SendToProgram("nopost\n", &second);
17052         }
17053     }
17054 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17055 }
17056
17057 void
17058 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17059 {
17060   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17061   if (pr == NoProc) return;
17062   AskQuestion(title, question, replyPrefix, pr);
17063 }
17064
17065 void
17066 TypeInEvent (char firstChar)
17067 {
17068     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17069         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17070         gameMode == AnalyzeMode || gameMode == EditGame ||
17071         gameMode == EditPosition || gameMode == IcsExamining ||
17072         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17073         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17074                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17075                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17076         gameMode == Training) PopUpMoveDialog(firstChar);
17077 }
17078
17079 void
17080 TypeInDoneEvent (char *move)
17081 {
17082         Board board;
17083         int n, fromX, fromY, toX, toY;
17084         char promoChar;
17085         ChessMove moveType;
17086
17087         // [HGM] FENedit
17088         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17089                 EditPositionPasteFEN(move);
17090                 return;
17091         }
17092         // [HGM] movenum: allow move number to be typed in any mode
17093         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17094           ToNrEvent(2*n-1);
17095           return;
17096         }
17097         // undocumented kludge: allow command-line option to be typed in!
17098         // (potentially fatal, and does not implement the effect of the option.)
17099         // should only be used for options that are values on which future decisions will be made,
17100         // and definitely not on options that would be used during initialization.
17101         if(strstr(move, "!!! -") == move) {
17102             ParseArgsFromString(move+4);
17103             return;
17104         }
17105
17106       if (gameMode != EditGame && currentMove != forwardMostMove &&
17107         gameMode != Training) {
17108         DisplayMoveError(_("Displayed move is not current"));
17109       } else {
17110         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17111           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17112         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17113         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17114           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17115           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17116         } else {
17117           DisplayMoveError(_("Could not parse move"));
17118         }
17119       }
17120 }
17121
17122 void
17123 DisplayMove (int moveNumber)
17124 {
17125     char message[MSG_SIZ];
17126     char res[MSG_SIZ];
17127     char cpThinkOutput[MSG_SIZ];
17128
17129     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17130
17131     if (moveNumber == forwardMostMove - 1 ||
17132         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17133
17134         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17135
17136         if (strchr(cpThinkOutput, '\n')) {
17137             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17138         }
17139     } else {
17140         *cpThinkOutput = NULLCHAR;
17141     }
17142
17143     /* [AS] Hide thinking from human user */
17144     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17145         *cpThinkOutput = NULLCHAR;
17146         if( thinkOutput[0] != NULLCHAR ) {
17147             int i;
17148
17149             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17150                 cpThinkOutput[i] = '.';
17151             }
17152             cpThinkOutput[i] = NULLCHAR;
17153             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17154         }
17155     }
17156
17157     if (moveNumber == forwardMostMove - 1 &&
17158         gameInfo.resultDetails != NULL) {
17159         if (gameInfo.resultDetails[0] == NULLCHAR) {
17160           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17161         } else {
17162           snprintf(res, MSG_SIZ, " {%s} %s",
17163                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17164         }
17165     } else {
17166         res[0] = NULLCHAR;
17167     }
17168
17169     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17170         DisplayMessage(res, cpThinkOutput);
17171     } else {
17172       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17173                 WhiteOnMove(moveNumber) ? " " : ".. ",
17174                 parseList[moveNumber], res);
17175         DisplayMessage(message, cpThinkOutput);
17176     }
17177 }
17178
17179 void
17180 DisplayComment (int moveNumber, char *text)
17181 {
17182     char title[MSG_SIZ];
17183
17184     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17185       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17186     } else {
17187       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17188               WhiteOnMove(moveNumber) ? " " : ".. ",
17189               parseList[moveNumber]);
17190     }
17191     if (text != NULL && (appData.autoDisplayComment || commentUp))
17192         CommentPopUp(title, text);
17193 }
17194
17195 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17196  * might be busy thinking or pondering.  It can be omitted if your
17197  * gnuchess is configured to stop thinking immediately on any user
17198  * input.  However, that gnuchess feature depends on the FIONREAD
17199  * ioctl, which does not work properly on some flavors of Unix.
17200  */
17201 void
17202 Attention (ChessProgramState *cps)
17203 {
17204 #if ATTENTION
17205     if (!cps->useSigint) return;
17206     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17207     switch (gameMode) {
17208       case MachinePlaysWhite:
17209       case MachinePlaysBlack:
17210       case TwoMachinesPlay:
17211       case IcsPlayingWhite:
17212       case IcsPlayingBlack:
17213       case AnalyzeMode:
17214       case AnalyzeFile:
17215         /* Skip if we know it isn't thinking */
17216         if (!cps->maybeThinking) return;
17217         if (appData.debugMode)
17218           fprintf(debugFP, "Interrupting %s\n", cps->which);
17219         InterruptChildProcess(cps->pr);
17220         cps->maybeThinking = FALSE;
17221         break;
17222       default:
17223         break;
17224     }
17225 #endif /*ATTENTION*/
17226 }
17227
17228 int
17229 CheckFlags ()
17230 {
17231     if (whiteTimeRemaining <= 0) {
17232         if (!whiteFlag) {
17233             whiteFlag = TRUE;
17234             if (appData.icsActive) {
17235                 if (appData.autoCallFlag &&
17236                     gameMode == IcsPlayingBlack && !blackFlag) {
17237                   SendToICS(ics_prefix);
17238                   SendToICS("flag\n");
17239                 }
17240             } else {
17241                 if (blackFlag) {
17242                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17243                 } else {
17244                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17245                     if (appData.autoCallFlag) {
17246                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17247                         return TRUE;
17248                     }
17249                 }
17250             }
17251         }
17252     }
17253     if (blackTimeRemaining <= 0) {
17254         if (!blackFlag) {
17255             blackFlag = TRUE;
17256             if (appData.icsActive) {
17257                 if (appData.autoCallFlag &&
17258                     gameMode == IcsPlayingWhite && !whiteFlag) {
17259                   SendToICS(ics_prefix);
17260                   SendToICS("flag\n");
17261                 }
17262             } else {
17263                 if (whiteFlag) {
17264                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17265                 } else {
17266                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17267                     if (appData.autoCallFlag) {
17268                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17269                         return TRUE;
17270                     }
17271                 }
17272             }
17273         }
17274     }
17275     return FALSE;
17276 }
17277
17278 void
17279 CheckTimeControl ()
17280 {
17281     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17282         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17283
17284     /*
17285      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17286      */
17287     if ( !WhiteOnMove(forwardMostMove) ) {
17288         /* White made time control */
17289         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17290         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17291         /* [HGM] time odds: correct new time quota for time odds! */
17292                                             / WhitePlayer()->timeOdds;
17293         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17294     } else {
17295         lastBlack -= blackTimeRemaining;
17296         /* Black made time control */
17297         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17298                                             / WhitePlayer()->other->timeOdds;
17299         lastWhite = whiteTimeRemaining;
17300     }
17301 }
17302
17303 void
17304 DisplayBothClocks ()
17305 {
17306     int wom = gameMode == EditPosition ?
17307       !blackPlaysFirst : WhiteOnMove(currentMove);
17308     DisplayWhiteClock(whiteTimeRemaining, wom);
17309     DisplayBlackClock(blackTimeRemaining, !wom);
17310 }
17311
17312
17313 /* Timekeeping seems to be a portability nightmare.  I think everyone
17314    has ftime(), but I'm really not sure, so I'm including some ifdefs
17315    to use other calls if you don't.  Clocks will be less accurate if
17316    you have neither ftime nor gettimeofday.
17317 */
17318
17319 /* VS 2008 requires the #include outside of the function */
17320 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17321 #include <sys/timeb.h>
17322 #endif
17323
17324 /* Get the current time as a TimeMark */
17325 void
17326 GetTimeMark (TimeMark *tm)
17327 {
17328 #if HAVE_GETTIMEOFDAY
17329
17330     struct timeval timeVal;
17331     struct timezone timeZone;
17332
17333     gettimeofday(&timeVal, &timeZone);
17334     tm->sec = (long) timeVal.tv_sec;
17335     tm->ms = (int) (timeVal.tv_usec / 1000L);
17336
17337 #else /*!HAVE_GETTIMEOFDAY*/
17338 #if HAVE_FTIME
17339
17340 // include <sys/timeb.h> / moved to just above start of function
17341     struct timeb timeB;
17342
17343     ftime(&timeB);
17344     tm->sec = (long) timeB.time;
17345     tm->ms = (int) timeB.millitm;
17346
17347 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17348     tm->sec = (long) time(NULL);
17349     tm->ms = 0;
17350 #endif
17351 #endif
17352 }
17353
17354 /* Return the difference in milliseconds between two
17355    time marks.  We assume the difference will fit in a long!
17356 */
17357 long
17358 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17359 {
17360     return 1000L*(tm2->sec - tm1->sec) +
17361            (long) (tm2->ms - tm1->ms);
17362 }
17363
17364
17365 /*
17366  * Code to manage the game clocks.
17367  *
17368  * In tournament play, black starts the clock and then white makes a move.
17369  * We give the human user a slight advantage if he is playing white---the
17370  * clocks don't run until he makes his first move, so it takes zero time.
17371  * Also, we don't account for network lag, so we could get out of sync
17372  * with GNU Chess's clock -- but then, referees are always right.
17373  */
17374
17375 static TimeMark tickStartTM;
17376 static long intendedTickLength;
17377
17378 long
17379 NextTickLength (long timeRemaining)
17380 {
17381     long nominalTickLength, nextTickLength;
17382
17383     if (timeRemaining > 0L && timeRemaining <= 10000L)
17384       nominalTickLength = 100L;
17385     else
17386       nominalTickLength = 1000L;
17387     nextTickLength = timeRemaining % nominalTickLength;
17388     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17389
17390     return nextTickLength;
17391 }
17392
17393 /* Adjust clock one minute up or down */
17394 void
17395 AdjustClock (Boolean which, int dir)
17396 {
17397     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17398     if(which) blackTimeRemaining += 60000*dir;
17399     else      whiteTimeRemaining += 60000*dir;
17400     DisplayBothClocks();
17401     adjustedClock = TRUE;
17402 }
17403
17404 /* Stop clocks and reset to a fresh time control */
17405 void
17406 ResetClocks ()
17407 {
17408     (void) StopClockTimer();
17409     if (appData.icsActive) {
17410         whiteTimeRemaining = blackTimeRemaining = 0;
17411     } else if (searchTime) {
17412         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17413         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17414     } else { /* [HGM] correct new time quote for time odds */
17415         whiteTC = blackTC = fullTimeControlString;
17416         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17417         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17418     }
17419     if (whiteFlag || blackFlag) {
17420         DisplayTitle("");
17421         whiteFlag = blackFlag = FALSE;
17422     }
17423     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17424     DisplayBothClocks();
17425     adjustedClock = FALSE;
17426 }
17427
17428 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17429
17430 /* Decrement running clock by amount of time that has passed */
17431 void
17432 DecrementClocks ()
17433 {
17434     long timeRemaining;
17435     long lastTickLength, fudge;
17436     TimeMark now;
17437
17438     if (!appData.clockMode) return;
17439     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17440
17441     GetTimeMark(&now);
17442
17443     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17444
17445     /* Fudge if we woke up a little too soon */
17446     fudge = intendedTickLength - lastTickLength;
17447     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17448
17449     if (WhiteOnMove(forwardMostMove)) {
17450         if(whiteNPS >= 0) lastTickLength = 0;
17451         timeRemaining = whiteTimeRemaining -= lastTickLength;
17452         if(timeRemaining < 0 && !appData.icsActive) {
17453             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17454             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17455                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17456                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17457             }
17458         }
17459         DisplayWhiteClock(whiteTimeRemaining - fudge,
17460                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17461     } else {
17462         if(blackNPS >= 0) lastTickLength = 0;
17463         timeRemaining = blackTimeRemaining -= lastTickLength;
17464         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17465             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17466             if(suddenDeath) {
17467                 blackStartMove = forwardMostMove;
17468                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17469             }
17470         }
17471         DisplayBlackClock(blackTimeRemaining - fudge,
17472                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17473     }
17474     if (CheckFlags()) return;
17475
17476     if(twoBoards) { // count down secondary board's clocks as well
17477         activePartnerTime -= lastTickLength;
17478         partnerUp = 1;
17479         if(activePartner == 'W')
17480             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17481         else
17482             DisplayBlackClock(activePartnerTime, TRUE);
17483         partnerUp = 0;
17484     }
17485
17486     tickStartTM = now;
17487     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17488     StartClockTimer(intendedTickLength);
17489
17490     /* if the time remaining has fallen below the alarm threshold, sound the
17491      * alarm. if the alarm has sounded and (due to a takeback or time control
17492      * with increment) the time remaining has increased to a level above the
17493      * threshold, reset the alarm so it can sound again.
17494      */
17495
17496     if (appData.icsActive && appData.icsAlarm) {
17497
17498         /* make sure we are dealing with the user's clock */
17499         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17500                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17501            )) return;
17502
17503         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17504             alarmSounded = FALSE;
17505         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17506             PlayAlarmSound();
17507             alarmSounded = TRUE;
17508         }
17509     }
17510 }
17511
17512
17513 /* A player has just moved, so stop the previously running
17514    clock and (if in clock mode) start the other one.
17515    We redisplay both clocks in case we're in ICS mode, because
17516    ICS gives us an update to both clocks after every move.
17517    Note that this routine is called *after* forwardMostMove
17518    is updated, so the last fractional tick must be subtracted
17519    from the color that is *not* on move now.
17520 */
17521 void
17522 SwitchClocks (int newMoveNr)
17523 {
17524     long lastTickLength;
17525     TimeMark now;
17526     int flagged = FALSE;
17527
17528     GetTimeMark(&now);
17529
17530     if (StopClockTimer() && appData.clockMode) {
17531         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17532         if (!WhiteOnMove(forwardMostMove)) {
17533             if(blackNPS >= 0) lastTickLength = 0;
17534             blackTimeRemaining -= lastTickLength;
17535            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17536 //         if(pvInfoList[forwardMostMove].time == -1)
17537                  pvInfoList[forwardMostMove].time =               // use GUI time
17538                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17539         } else {
17540            if(whiteNPS >= 0) lastTickLength = 0;
17541            whiteTimeRemaining -= lastTickLength;
17542            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17543 //         if(pvInfoList[forwardMostMove].time == -1)
17544                  pvInfoList[forwardMostMove].time =
17545                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17546         }
17547         flagged = CheckFlags();
17548     }
17549     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17550     CheckTimeControl();
17551
17552     if (flagged || !appData.clockMode) return;
17553
17554     switch (gameMode) {
17555       case MachinePlaysBlack:
17556       case MachinePlaysWhite:
17557       case BeginningOfGame:
17558         if (pausing) return;
17559         break;
17560
17561       case EditGame:
17562       case PlayFromGameFile:
17563       case IcsExamining:
17564         return;
17565
17566       default:
17567         break;
17568     }
17569
17570     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17571         if(WhiteOnMove(forwardMostMove))
17572              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17573         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17574     }
17575
17576     tickStartTM = now;
17577     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17578       whiteTimeRemaining : blackTimeRemaining);
17579     StartClockTimer(intendedTickLength);
17580 }
17581
17582
17583 /* Stop both clocks */
17584 void
17585 StopClocks ()
17586 {
17587     long lastTickLength;
17588     TimeMark now;
17589
17590     if (!StopClockTimer()) return;
17591     if (!appData.clockMode) return;
17592
17593     GetTimeMark(&now);
17594
17595     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17596     if (WhiteOnMove(forwardMostMove)) {
17597         if(whiteNPS >= 0) lastTickLength = 0;
17598         whiteTimeRemaining -= lastTickLength;
17599         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17600     } else {
17601         if(blackNPS >= 0) lastTickLength = 0;
17602         blackTimeRemaining -= lastTickLength;
17603         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17604     }
17605     CheckFlags();
17606 }
17607
17608 /* Start clock of player on move.  Time may have been reset, so
17609    if clock is already running, stop and restart it. */
17610 void
17611 StartClocks ()
17612 {
17613     (void) StopClockTimer(); /* in case it was running already */
17614     DisplayBothClocks();
17615     if (CheckFlags()) return;
17616
17617     if (!appData.clockMode) return;
17618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17619
17620     GetTimeMark(&tickStartTM);
17621     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17622       whiteTimeRemaining : blackTimeRemaining);
17623
17624    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17625     whiteNPS = blackNPS = -1;
17626     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17627        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17628         whiteNPS = first.nps;
17629     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17630        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17631         blackNPS = first.nps;
17632     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17633         whiteNPS = second.nps;
17634     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17635         blackNPS = second.nps;
17636     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17637
17638     StartClockTimer(intendedTickLength);
17639 }
17640
17641 char *
17642 TimeString (long ms)
17643 {
17644     long second, minute, hour, day;
17645     char *sign = "";
17646     static char buf[32];
17647
17648     if (ms > 0 && ms <= 9900) {
17649       /* convert milliseconds to tenths, rounding up */
17650       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17651
17652       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17653       return buf;
17654     }
17655
17656     /* convert milliseconds to seconds, rounding up */
17657     /* use floating point to avoid strangeness of integer division
17658        with negative dividends on many machines */
17659     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17660
17661     if (second < 0) {
17662         sign = "-";
17663         second = -second;
17664     }
17665
17666     day = second / (60 * 60 * 24);
17667     second = second % (60 * 60 * 24);
17668     hour = second / (60 * 60);
17669     second = second % (60 * 60);
17670     minute = second / 60;
17671     second = second % 60;
17672
17673     if (day > 0)
17674       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17675               sign, day, hour, minute, second);
17676     else if (hour > 0)
17677       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17678     else
17679       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17680
17681     return buf;
17682 }
17683
17684
17685 /*
17686  * This is necessary because some C libraries aren't ANSI C compliant yet.
17687  */
17688 char *
17689 StrStr (char *string, char *match)
17690 {
17691     int i, length;
17692
17693     length = strlen(match);
17694
17695     for (i = strlen(string) - length; i >= 0; i--, string++)
17696       if (!strncmp(match, string, length))
17697         return string;
17698
17699     return NULL;
17700 }
17701
17702 char *
17703 StrCaseStr (char *string, char *match)
17704 {
17705     int i, j, length;
17706
17707     length = strlen(match);
17708
17709     for (i = strlen(string) - length; i >= 0; i--, string++) {
17710         for (j = 0; j < length; j++) {
17711             if (ToLower(match[j]) != ToLower(string[j]))
17712               break;
17713         }
17714         if (j == length) return string;
17715     }
17716
17717     return NULL;
17718 }
17719
17720 #ifndef _amigados
17721 int
17722 StrCaseCmp (char *s1, char *s2)
17723 {
17724     char c1, c2;
17725
17726     for (;;) {
17727         c1 = ToLower(*s1++);
17728         c2 = ToLower(*s2++);
17729         if (c1 > c2) return 1;
17730         if (c1 < c2) return -1;
17731         if (c1 == NULLCHAR) return 0;
17732     }
17733 }
17734
17735
17736 int
17737 ToLower (int c)
17738 {
17739     return isupper(c) ? tolower(c) : c;
17740 }
17741
17742
17743 int
17744 ToUpper (int c)
17745 {
17746     return islower(c) ? toupper(c) : c;
17747 }
17748 #endif /* !_amigados    */
17749
17750 char *
17751 StrSave (char *s)
17752 {
17753   char *ret;
17754
17755   if ((ret = (char *) malloc(strlen(s) + 1)))
17756     {
17757       safeStrCpy(ret, s, strlen(s)+1);
17758     }
17759   return ret;
17760 }
17761
17762 char *
17763 StrSavePtr (char *s, char **savePtr)
17764 {
17765     if (*savePtr) {
17766         free(*savePtr);
17767     }
17768     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17769       safeStrCpy(*savePtr, s, strlen(s)+1);
17770     }
17771     return(*savePtr);
17772 }
17773
17774 char *
17775 PGNDate ()
17776 {
17777     time_t clock;
17778     struct tm *tm;
17779     char buf[MSG_SIZ];
17780
17781     clock = time((time_t *)NULL);
17782     tm = localtime(&clock);
17783     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17784             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17785     return StrSave(buf);
17786 }
17787
17788
17789 char *
17790 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17791 {
17792     int i, j, fromX, fromY, toX, toY;
17793     int whiteToPlay;
17794     char buf[MSG_SIZ];
17795     char *p, *q;
17796     int emptycount;
17797     ChessSquare piece;
17798
17799     whiteToPlay = (gameMode == EditPosition) ?
17800       !blackPlaysFirst : (move % 2 == 0);
17801     p = buf;
17802
17803     /* Piece placement data */
17804     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17805         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17806         emptycount = 0;
17807         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17808             if (boards[move][i][j] == EmptySquare) {
17809                 emptycount++;
17810             } else { ChessSquare piece = boards[move][i][j];
17811                 if (emptycount > 0) {
17812                     if(emptycount<10) /* [HGM] can be >= 10 */
17813                         *p++ = '0' + emptycount;
17814                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17815                     emptycount = 0;
17816                 }
17817                 if(PieceToChar(piece) == '+') {
17818                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17819                     *p++ = '+';
17820                     piece = (ChessSquare)(CHUDEMOTED piece);
17821                 }
17822                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17823                 if(p[-1] == '~') {
17824                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17825                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17826                     *p++ = '~';
17827                 }
17828             }
17829         }
17830         if (emptycount > 0) {
17831             if(emptycount<10) /* [HGM] can be >= 10 */
17832                 *p++ = '0' + emptycount;
17833             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17834             emptycount = 0;
17835         }
17836         *p++ = '/';
17837     }
17838     *(p - 1) = ' ';
17839
17840     /* [HGM] print Crazyhouse or Shogi holdings */
17841     if( gameInfo.holdingsWidth ) {
17842         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17843         q = p;
17844         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17845             piece = boards[move][i][BOARD_WIDTH-1];
17846             if( piece != EmptySquare )
17847               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17848                   *p++ = PieceToChar(piece);
17849         }
17850         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17851             piece = boards[move][BOARD_HEIGHT-i-1][0];
17852             if( piece != EmptySquare )
17853               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17854                   *p++ = PieceToChar(piece);
17855         }
17856
17857         if( q == p ) *p++ = '-';
17858         *p++ = ']';
17859         *p++ = ' ';
17860     }
17861
17862     /* Active color */
17863     *p++ = whiteToPlay ? 'w' : 'b';
17864     *p++ = ' ';
17865
17866   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17867     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17868   } else {
17869   if(nrCastlingRights) {
17870      int handW=0, handB=0;
17871      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17872         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17873         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17874      }
17875      q = p;
17876      if(appData.fischerCastling) {
17877         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17878            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17879                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17880         } else {
17881        /* [HGM] write directly from rights */
17882            if(boards[move][CASTLING][2] != NoRights &&
17883               boards[move][CASTLING][0] != NoRights   )
17884                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17885            if(boards[move][CASTLING][2] != NoRights &&
17886               boards[move][CASTLING][1] != NoRights   )
17887                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17888         }
17889         if(handB) {
17890            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17891                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17892         } else {
17893            if(boards[move][CASTLING][5] != NoRights &&
17894               boards[move][CASTLING][3] != NoRights   )
17895                 *p++ = boards[move][CASTLING][3] + AAA;
17896            if(boards[move][CASTLING][5] != NoRights &&
17897               boards[move][CASTLING][4] != NoRights   )
17898                 *p++ = boards[move][CASTLING][4] + AAA;
17899         }
17900      } else {
17901
17902         /* [HGM] write true castling rights */
17903         if( nrCastlingRights == 6 ) {
17904             int q, k=0;
17905             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17906                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17907             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17908                  boards[move][CASTLING][2] != NoRights  );
17909             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17910                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17911                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17912                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17913             }
17914             if(q) *p++ = 'Q';
17915             k = 0;
17916             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17917                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17918             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17919                  boards[move][CASTLING][5] != NoRights  );
17920             if(handB) {
17921                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17922                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17923                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17924             }
17925             if(q) *p++ = 'q';
17926         }
17927      }
17928      if (q == p) *p++ = '-'; /* No castling rights */
17929      *p++ = ' ';
17930   }
17931
17932   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17933      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17934      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17935     /* En passant target square */
17936     if (move > backwardMostMove) {
17937         fromX = moveList[move - 1][0] - AAA;
17938         fromY = moveList[move - 1][1] - ONE;
17939         toX = moveList[move - 1][2] - AAA;
17940         toY = moveList[move - 1][3] - ONE;
17941         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17942             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17943             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17944             fromX == toX) {
17945             /* 2-square pawn move just happened */
17946             *p++ = toX + AAA;
17947             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17948         } else {
17949             *p++ = '-';
17950         }
17951     } else if(move == backwardMostMove) {
17952         // [HGM] perhaps we should always do it like this, and forget the above?
17953         if((signed char)boards[move][EP_STATUS] >= 0) {
17954             *p++ = boards[move][EP_STATUS] + AAA;
17955             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17956         } else {
17957             *p++ = '-';
17958         }
17959     } else {
17960         *p++ = '-';
17961     }
17962     *p++ = ' ';
17963   }
17964   }
17965
17966     if(moveCounts)
17967     {   int i = 0, j=move;
17968
17969         /* [HGM] find reversible plies */
17970         if (appData.debugMode) { int k;
17971             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17972             for(k=backwardMostMove; k<=forwardMostMove; k++)
17973                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17974
17975         }
17976
17977         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17978         if( j == backwardMostMove ) i += initialRulePlies;
17979         sprintf(p, "%d ", i);
17980         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17981
17982         /* Fullmove number */
17983         sprintf(p, "%d", (move / 2) + 1);
17984     } else *--p = NULLCHAR;
17985
17986     return StrSave(buf);
17987 }
17988
17989 Boolean
17990 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17991 {
17992     int i, j, k, w=0, subst=0, shuffle=0;
17993     char *p, c;
17994     int emptycount, virgin[BOARD_FILES];
17995     ChessSquare piece;
17996
17997     p = fen;
17998
17999     /* Piece placement data */
18000     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18001         j = 0;
18002         for (;;) {
18003             if (*p == '/' || *p == ' ' || *p == '[' ) {
18004                 if(j > w) w = j;
18005                 emptycount = gameInfo.boardWidth - j;
18006                 while (emptycount--)
18007                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18008                 if (*p == '/') p++;
18009                 else if(autoSize) { // we stumbled unexpectedly into end of board
18010                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18011                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18012                     }
18013                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18014                 }
18015                 break;
18016 #if(BOARD_FILES >= 10)*0
18017             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18018                 p++; emptycount=10;
18019                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18020                 while (emptycount--)
18021                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18022 #endif
18023             } else if (*p == '*') {
18024                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18025             } else if (isdigit(*p)) {
18026                 emptycount = *p++ - '0';
18027                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18028                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18029                 while (emptycount--)
18030                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18031             } else if (*p == '<') {
18032                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18033                 else if (i != 0 || !shuffle) return FALSE;
18034                 p++;
18035             } else if (shuffle && *p == '>') {
18036                 p++; // for now ignore closing shuffle range, and assume rank-end
18037             } else if (*p == '?') {
18038                 if (j >= gameInfo.boardWidth) return FALSE;
18039                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18040                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18041             } else if (*p == '+' || isalpha(*p)) {
18042                 if (j >= gameInfo.boardWidth) return FALSE;
18043                 if(*p=='+') {
18044                     piece = CharToPiece(*++p);
18045                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18046                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18047                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18048                 } else piece = CharToPiece(*p++);
18049
18050                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18051                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18052                     piece = (ChessSquare) (PROMOTED piece);
18053                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18054                     p++;
18055                 }
18056                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18057             } else {
18058                 return FALSE;
18059             }
18060         }
18061     }
18062     while (*p == '/' || *p == ' ') p++;
18063
18064     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18065
18066     /* [HGM] by default clear Crazyhouse holdings, if present */
18067     if(gameInfo.holdingsWidth) {
18068        for(i=0; i<BOARD_HEIGHT; i++) {
18069            board[i][0]             = EmptySquare; /* black holdings */
18070            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18071            board[i][1]             = (ChessSquare) 0; /* black counts */
18072            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18073        }
18074     }
18075
18076     /* [HGM] look for Crazyhouse holdings here */
18077     while(*p==' ') p++;
18078     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18079         int swap=0, wcnt=0, bcnt=0;
18080         if(*p == '[') p++;
18081         if(*p == '<') swap++, p++;
18082         if(*p == '-' ) p++; /* empty holdings */ else {
18083             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18084             /* if we would allow FEN reading to set board size, we would   */
18085             /* have to add holdings and shift the board read so far here   */
18086             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18087                 p++;
18088                 if((int) piece >= (int) BlackPawn ) {
18089                     i = (int)piece - (int)BlackPawn;
18090                     i = PieceToNumber((ChessSquare)i);
18091                     if( i >= gameInfo.holdingsSize ) return FALSE;
18092                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18093                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18094                     bcnt++;
18095                 } else {
18096                     i = (int)piece - (int)WhitePawn;
18097                     i = PieceToNumber((ChessSquare)i);
18098                     if( i >= gameInfo.holdingsSize ) return FALSE;
18099                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18100                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18101                     wcnt++;
18102                 }
18103             }
18104             if(subst) { // substitute back-rank question marks by holdings pieces
18105                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18106                     int k, m, n = bcnt + 1;
18107                     if(board[0][j] == ClearBoard) {
18108                         if(!wcnt) return FALSE;
18109                         n = rand() % wcnt;
18110                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18111                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18112                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18113                             break;
18114                         }
18115                     }
18116                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18117                         if(!bcnt) return FALSE;
18118                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18119                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18120                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18121                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18122                             break;
18123                         }
18124                     }
18125                 }
18126                 subst = 0;
18127             }
18128         }
18129         if(*p == ']') p++;
18130     }
18131
18132     if(subst) return FALSE; // substitution requested, but no holdings
18133
18134     while(*p == ' ') p++;
18135
18136     /* Active color */
18137     c = *p++;
18138     if(appData.colorNickNames) {
18139       if( c == appData.colorNickNames[0] ) c = 'w'; else
18140       if( c == appData.colorNickNames[1] ) c = 'b';
18141     }
18142     switch (c) {
18143       case 'w':
18144         *blackPlaysFirst = FALSE;
18145         break;
18146       case 'b':
18147         *blackPlaysFirst = TRUE;
18148         break;
18149       default:
18150         return FALSE;
18151     }
18152
18153     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18154     /* return the extra info in global variiables             */
18155
18156     /* set defaults in case FEN is incomplete */
18157     board[EP_STATUS] = EP_UNKNOWN;
18158     for(i=0; i<nrCastlingRights; i++ ) {
18159         board[CASTLING][i] =
18160             appData.fischerCastling ? NoRights : initialRights[i];
18161     }   /* assume possible unless obviously impossible */
18162     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18163     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18164     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18165                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18166     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18167     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18168     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18169                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18170     FENrulePlies = 0;
18171
18172     while(*p==' ') p++;
18173     if(nrCastlingRights) {
18174       int fischer = 0;
18175       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18176       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18177           /* castling indicator present, so default becomes no castlings */
18178           for(i=0; i<nrCastlingRights; i++ ) {
18179                  board[CASTLING][i] = NoRights;
18180           }
18181       }
18182       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18183              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18184              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18185              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18186         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18187
18188         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18189             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18190             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18191         }
18192         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18193             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18194         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18195                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18196         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18197                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18198         switch(c) {
18199           case'K':
18200               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18201               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18202               board[CASTLING][2] = whiteKingFile;
18203               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18204               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18205               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18206               break;
18207           case'Q':
18208               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18209               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18210               board[CASTLING][2] = whiteKingFile;
18211               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18212               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18213               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18214               break;
18215           case'k':
18216               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18217               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18218               board[CASTLING][5] = blackKingFile;
18219               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18220               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18221               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18222               break;
18223           case'q':
18224               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18225               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18226               board[CASTLING][5] = blackKingFile;
18227               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18228               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18229               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18230           case '-':
18231               break;
18232           default: /* FRC castlings */
18233               if(c >= 'a') { /* black rights */
18234                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18235                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18236                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18237                   if(i == BOARD_RGHT) break;
18238                   board[CASTLING][5] = i;
18239                   c -= AAA;
18240                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18241                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18242                   if(c > i)
18243                       board[CASTLING][3] = c;
18244                   else
18245                       board[CASTLING][4] = c;
18246               } else { /* white rights */
18247                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18248                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18249                     if(board[0][i] == WhiteKing) break;
18250                   if(i == BOARD_RGHT) break;
18251                   board[CASTLING][2] = i;
18252                   c -= AAA - 'a' + 'A';
18253                   if(board[0][c] >= WhiteKing) break;
18254                   if(c > i)
18255                       board[CASTLING][0] = c;
18256                   else
18257                       board[CASTLING][1] = c;
18258               }
18259         }
18260       }
18261       for(i=0; i<nrCastlingRights; i++)
18262         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18263       if(gameInfo.variant == VariantSChess)
18264         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18265       if(fischer && shuffle) appData.fischerCastling = TRUE;
18266     if (appData.debugMode) {
18267         fprintf(debugFP, "FEN castling rights:");
18268         for(i=0; i<nrCastlingRights; i++)
18269         fprintf(debugFP, " %d", board[CASTLING][i]);
18270         fprintf(debugFP, "\n");
18271     }
18272
18273       while(*p==' ') p++;
18274     }
18275
18276     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18277
18278     /* read e.p. field in games that know e.p. capture */
18279     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18280        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18281        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18282       if(*p=='-') {
18283         p++; board[EP_STATUS] = EP_NONE;
18284       } else {
18285          char c = *p++ - AAA;
18286
18287          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18288          if(*p >= '0' && *p <='9') p++;
18289          board[EP_STATUS] = c;
18290       }
18291     }
18292
18293
18294     if(sscanf(p, "%d", &i) == 1) {
18295         FENrulePlies = i; /* 50-move ply counter */
18296         /* (The move number is still ignored)    */
18297     }
18298
18299     return TRUE;
18300 }
18301
18302 void
18303 EditPositionPasteFEN (char *fen)
18304 {
18305   if (fen != NULL) {
18306     Board initial_position;
18307
18308     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18309       DisplayError(_("Bad FEN position in clipboard"), 0);
18310       return ;
18311     } else {
18312       int savedBlackPlaysFirst = blackPlaysFirst;
18313       EditPositionEvent();
18314       blackPlaysFirst = savedBlackPlaysFirst;
18315       CopyBoard(boards[0], initial_position);
18316       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18317       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18318       DisplayBothClocks();
18319       DrawPosition(FALSE, boards[currentMove]);
18320     }
18321   }
18322 }
18323
18324 static char cseq[12] = "\\   ";
18325
18326 Boolean
18327 set_cont_sequence (char *new_seq)
18328 {
18329     int len;
18330     Boolean ret;
18331
18332     // handle bad attempts to set the sequence
18333         if (!new_seq)
18334                 return 0; // acceptable error - no debug
18335
18336     len = strlen(new_seq);
18337     ret = (len > 0) && (len < sizeof(cseq));
18338     if (ret)
18339       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18340     else if (appData.debugMode)
18341       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18342     return ret;
18343 }
18344
18345 /*
18346     reformat a source message so words don't cross the width boundary.  internal
18347     newlines are not removed.  returns the wrapped size (no null character unless
18348     included in source message).  If dest is NULL, only calculate the size required
18349     for the dest buffer.  lp argument indicats line position upon entry, and it's
18350     passed back upon exit.
18351 */
18352 int
18353 wrap (char *dest, char *src, int count, int width, int *lp)
18354 {
18355     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18356
18357     cseq_len = strlen(cseq);
18358     old_line = line = *lp;
18359     ansi = len = clen = 0;
18360
18361     for (i=0; i < count; i++)
18362     {
18363         if (src[i] == '\033')
18364             ansi = 1;
18365
18366         // if we hit the width, back up
18367         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18368         {
18369             // store i & len in case the word is too long
18370             old_i = i, old_len = len;
18371
18372             // find the end of the last word
18373             while (i && src[i] != ' ' && src[i] != '\n')
18374             {
18375                 i--;
18376                 len--;
18377             }
18378
18379             // word too long?  restore i & len before splitting it
18380             if ((old_i-i+clen) >= width)
18381             {
18382                 i = old_i;
18383                 len = old_len;
18384             }
18385
18386             // extra space?
18387             if (i && src[i-1] == ' ')
18388                 len--;
18389
18390             if (src[i] != ' ' && src[i] != '\n')
18391             {
18392                 i--;
18393                 if (len)
18394                     len--;
18395             }
18396
18397             // now append the newline and continuation sequence
18398             if (dest)
18399                 dest[len] = '\n';
18400             len++;
18401             if (dest)
18402                 strncpy(dest+len, cseq, cseq_len);
18403             len += cseq_len;
18404             line = cseq_len;
18405             clen = cseq_len;
18406             continue;
18407         }
18408
18409         if (dest)
18410             dest[len] = src[i];
18411         len++;
18412         if (!ansi)
18413             line++;
18414         if (src[i] == '\n')
18415             line = 0;
18416         if (src[i] == 'm')
18417             ansi = 0;
18418     }
18419     if (dest && appData.debugMode)
18420     {
18421         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18422             count, width, line, len, *lp);
18423         show_bytes(debugFP, src, count);
18424         fprintf(debugFP, "\ndest: ");
18425         show_bytes(debugFP, dest, len);
18426         fprintf(debugFP, "\n");
18427     }
18428     *lp = dest ? line : old_line;
18429
18430     return len;
18431 }
18432
18433 // [HGM] vari: routines for shelving variations
18434 Boolean modeRestore = FALSE;
18435
18436 void
18437 PushInner (int firstMove, int lastMove)
18438 {
18439         int i, j, nrMoves = lastMove - firstMove;
18440
18441         // push current tail of game on stack
18442         savedResult[storedGames] = gameInfo.result;
18443         savedDetails[storedGames] = gameInfo.resultDetails;
18444         gameInfo.resultDetails = NULL;
18445         savedFirst[storedGames] = firstMove;
18446         savedLast [storedGames] = lastMove;
18447         savedFramePtr[storedGames] = framePtr;
18448         framePtr -= nrMoves; // reserve space for the boards
18449         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18450             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18451             for(j=0; j<MOVE_LEN; j++)
18452                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18453             for(j=0; j<2*MOVE_LEN; j++)
18454                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18455             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18456             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18457             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18458             pvInfoList[firstMove+i-1].depth = 0;
18459             commentList[framePtr+i] = commentList[firstMove+i];
18460             commentList[firstMove+i] = NULL;
18461         }
18462
18463         storedGames++;
18464         forwardMostMove = firstMove; // truncate game so we can start variation
18465 }
18466
18467 void
18468 PushTail (int firstMove, int lastMove)
18469 {
18470         if(appData.icsActive) { // only in local mode
18471                 forwardMostMove = currentMove; // mimic old ICS behavior
18472                 return;
18473         }
18474         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18475
18476         PushInner(firstMove, lastMove);
18477         if(storedGames == 1) GreyRevert(FALSE);
18478         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18479 }
18480
18481 void
18482 PopInner (Boolean annotate)
18483 {
18484         int i, j, nrMoves;
18485         char buf[8000], moveBuf[20];
18486
18487         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18488         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18489         nrMoves = savedLast[storedGames] - currentMove;
18490         if(annotate) {
18491                 int cnt = 10;
18492                 if(!WhiteOnMove(currentMove))
18493                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18494                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18495                 for(i=currentMove; i<forwardMostMove; i++) {
18496                         if(WhiteOnMove(i))
18497                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18498                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18499                         strcat(buf, moveBuf);
18500                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18501                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18502                 }
18503                 strcat(buf, ")");
18504         }
18505         for(i=1; i<=nrMoves; i++) { // copy last variation back
18506             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18507             for(j=0; j<MOVE_LEN; j++)
18508                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18509             for(j=0; j<2*MOVE_LEN; j++)
18510                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18511             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18512             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18513             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18514             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18515             commentList[currentMove+i] = commentList[framePtr+i];
18516             commentList[framePtr+i] = NULL;
18517         }
18518         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18519         framePtr = savedFramePtr[storedGames];
18520         gameInfo.result = savedResult[storedGames];
18521         if(gameInfo.resultDetails != NULL) {
18522             free(gameInfo.resultDetails);
18523       }
18524         gameInfo.resultDetails = savedDetails[storedGames];
18525         forwardMostMove = currentMove + nrMoves;
18526 }
18527
18528 Boolean
18529 PopTail (Boolean annotate)
18530 {
18531         if(appData.icsActive) return FALSE; // only in local mode
18532         if(!storedGames) return FALSE; // sanity
18533         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18534
18535         PopInner(annotate);
18536         if(currentMove < forwardMostMove) ForwardEvent(); else
18537         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18538
18539         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18540         return TRUE;
18541 }
18542
18543 void
18544 CleanupTail ()
18545 {       // remove all shelved variations
18546         int i;
18547         for(i=0; i<storedGames; i++) {
18548             if(savedDetails[i])
18549                 free(savedDetails[i]);
18550             savedDetails[i] = NULL;
18551         }
18552         for(i=framePtr; i<MAX_MOVES; i++) {
18553                 if(commentList[i]) free(commentList[i]);
18554                 commentList[i] = NULL;
18555         }
18556         framePtr = MAX_MOVES-1;
18557         storedGames = 0;
18558 }
18559
18560 void
18561 LoadVariation (int index, char *text)
18562 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18563         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18564         int level = 0, move;
18565
18566         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18567         // first find outermost bracketing variation
18568         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18569             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18570                 if(*p == '{') wait = '}'; else
18571                 if(*p == '[') wait = ']'; else
18572                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18573                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18574             }
18575             if(*p == wait) wait = NULLCHAR; // closing ]} found
18576             p++;
18577         }
18578         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18579         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18580         end[1] = NULLCHAR; // clip off comment beyond variation
18581         ToNrEvent(currentMove-1);
18582         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18583         // kludge: use ParsePV() to append variation to game
18584         move = currentMove;
18585         ParsePV(start, TRUE, TRUE);
18586         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18587         ClearPremoveHighlights();
18588         CommentPopDown();
18589         ToNrEvent(currentMove+1);
18590 }
18591
18592 void
18593 LoadTheme ()
18594 {
18595     char *p, *q, buf[MSG_SIZ];
18596     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18597         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18598         ParseArgsFromString(buf);
18599         ActivateTheme(TRUE); // also redo colors
18600         return;
18601     }
18602     p = nickName;
18603     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18604     {
18605         int len;
18606         q = appData.themeNames;
18607         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18608       if(appData.useBitmaps) {
18609         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18610                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18611                 appData.liteBackTextureMode,
18612                 appData.darkBackTextureMode );
18613       } else {
18614         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18615                 Col2Text(2),   // lightSquareColor
18616                 Col2Text(3) ); // darkSquareColor
18617       }
18618       if(appData.useBorder) {
18619         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18620                 appData.border);
18621       } else {
18622         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18623       }
18624       if(appData.useFont) {
18625         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18626                 appData.renderPiecesWithFont,
18627                 appData.fontToPieceTable,
18628                 Col2Text(9),    // appData.fontBackColorWhite
18629                 Col2Text(10) ); // appData.fontForeColorBlack
18630       } else {
18631         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18632                 appData.pieceDirectory);
18633         if(!appData.pieceDirectory[0])
18634           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18635                 Col2Text(0),   // whitePieceColor
18636                 Col2Text(1) ); // blackPieceColor
18637       }
18638       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18639                 Col2Text(4),   // highlightSquareColor
18640                 Col2Text(5) ); // premoveHighlightColor
18641         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18642         if(insert != q) insert[-1] = NULLCHAR;
18643         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18644         if(q)   free(q);
18645     }
18646     ActivateTheme(FALSE);
18647 }