Improve board drawing
[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, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
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 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first 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_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         int oldFMM = forwardMostMove;
4167                         gotPremove = 0;
4168                         ClearPremoveHighlights();
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                           UserMoveEvent(premoveFromX, premoveFromY,
4172                                         premoveToX, premoveToY,
4173                                         premovePromoChar);
4174                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4175                           if(moveList[oldFMM-1][1] != '@')
4176                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4177                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4178                           else // (drop)
4179                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4180                         }
4181                       }
4182                     }
4183
4184                     /* Usually suppress following prompt */
4185                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4186                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4187                         if (looking_at(buf, &i, "*% ")) {
4188                             savingComment = FALSE;
4189                             suppressKibitz = 0;
4190                         }
4191                     }
4192                     next_out = i;
4193                 } else if (started == STARTED_HOLDINGS) {
4194                     int gamenum;
4195                     char new_piece[MSG_SIZ];
4196                     started = STARTED_NONE;
4197                     parse[parse_pos] = NULLCHAR;
4198                     if (appData.debugMode)
4199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4200                                                         parse, currentMove);
4201                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4202                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4203                         if (gameInfo.variant == VariantNormal) {
4204                           /* [HGM] We seem to switch variant during a game!
4205                            * Presumably no holdings were displayed, so we have
4206                            * to move the position two files to the right to
4207                            * create room for them!
4208                            */
4209                           VariantClass newVariant;
4210                           switch(gameInfo.boardWidth) { // base guess on board width
4211                                 case 9:  newVariant = VariantShogi; break;
4212                                 case 10: newVariant = VariantGreat; break;
4213                                 default: newVariant = VariantCrazyhouse; break;
4214                           }
4215                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4216                           /* Get a move list just to see the header, which
4217                              will tell us whether this is really bug or zh */
4218                           if (ics_getting_history == H_FALSE) {
4219                             ics_getting_history = H_REQUESTED;
4220                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221                             SendToICS(str);
4222                           }
4223                         }
4224                         new_piece[0] = NULLCHAR;
4225                         sscanf(parse, "game %d white [%s black [%s <- %s",
4226                                &gamenum, white_holding, black_holding,
4227                                new_piece);
4228                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4229                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4230                         /* [HGM] copy holdings to board holdings area */
4231                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4232                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4233                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4234 #if ZIPPY
4235                         if (appData.zippyPlay && first.initDone) {
4236                             ZippyHoldings(white_holding, black_holding,
4237                                           new_piece);
4238                         }
4239 #endif /*ZIPPY*/
4240                         if (tinyLayout || smallLayout) {
4241                             char wh[16], bh[16];
4242                             PackHolding(wh, white_holding);
4243                             PackHolding(bh, black_holding);
4244                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4245                                     gameInfo.white, gameInfo.black);
4246                         } else {
4247                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4248                                     gameInfo.white, white_holding, _("vs."),
4249                                     gameInfo.black, black_holding);
4250                         }
4251                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4252                         DrawPosition(FALSE, boards[currentMove]);
4253                         DisplayTitle(str);
4254                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4255                         sscanf(parse, "game %d white [%s black [%s <- %s",
4256                                &gamenum, white_holding, black_holding,
4257                                new_piece);
4258                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4259                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4260                         /* [HGM] copy holdings to partner-board holdings area */
4261                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4262                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4263                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4264                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4265                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4266                       }
4267                     }
4268                     /* Suppress following prompt */
4269                     if (looking_at(buf, &i, "*% ")) {
4270                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4271                         savingComment = FALSE;
4272                         suppressKibitz = 0;
4273                     }
4274                     next_out = i;
4275                 }
4276                 continue;
4277             }
4278
4279             i++;                /* skip unparsed character and loop back */
4280         }
4281
4282         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4283 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4284 //          SendToPlayer(&buf[next_out], i - next_out);
4285             started != STARTED_HOLDINGS && leftover_start > next_out) {
4286             SendToPlayer(&buf[next_out], leftover_start - next_out);
4287             next_out = i;
4288         }
4289
4290         leftover_len = buf_len - leftover_start;
4291         /* if buffer ends with something we couldn't parse,
4292            reparse it after appending the next read */
4293
4294     } else if (count == 0) {
4295         RemoveInputSource(isr);
4296         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4297     } else {
4298         DisplayFatalError(_("Error reading from ICS"), error, 1);
4299     }
4300 }
4301
4302
4303 /* Board style 12 looks like this:
4304
4305    <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
4306
4307  * The "<12> " is stripped before it gets to this routine.  The two
4308  * trailing 0's (flip state and clock ticking) are later addition, and
4309  * some chess servers may not have them, or may have only the first.
4310  * Additional trailing fields may be added in the future.
4311  */
4312
4313 #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"
4314
4315 #define RELATION_OBSERVING_PLAYED    0
4316 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4317 #define RELATION_PLAYING_MYMOVE      1
4318 #define RELATION_PLAYING_NOTMYMOVE  -1
4319 #define RELATION_EXAMINING           2
4320 #define RELATION_ISOLATED_BOARD     -3
4321 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4322
4323 void
4324 ParseBoard12 (char *string)
4325 {
4326 #if ZIPPY
4327     int i, takeback;
4328     char *bookHit = NULL; // [HGM] book
4329 #endif
4330     GameMode newGameMode;
4331     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4332     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4333     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4334     char to_play, board_chars[200];
4335     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4336     char black[32], white[32];
4337     Board board;
4338     int prevMove = currentMove;
4339     int ticking = 2;
4340     ChessMove moveType;
4341     int fromX, fromY, toX, toY;
4342     char promoChar;
4343     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4344     Boolean weird = FALSE, reqFlag = FALSE;
4345
4346     fromX = fromY = toX = toY = -1;
4347
4348     newGame = FALSE;
4349
4350     if (appData.debugMode)
4351       fprintf(debugFP, "Parsing board: %s\n", string);
4352
4353     move_str[0] = NULLCHAR;
4354     elapsed_time[0] = NULLCHAR;
4355     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4356         int  i = 0, j;
4357         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4358             if(string[i] == ' ') { ranks++; files = 0; }
4359             else files++;
4360             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4361             i++;
4362         }
4363         for(j = 0; j <i; j++) board_chars[j] = string[j];
4364         board_chars[i] = '\0';
4365         string += i + 1;
4366     }
4367     n = sscanf(string, PATTERN, &to_play, &double_push,
4368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4369                &gamenum, white, black, &relation, &basetime, &increment,
4370                &white_stren, &black_stren, &white_time, &black_time,
4371                &moveNum, str, elapsed_time, move_str, &ics_flip,
4372                &ticking);
4373
4374     if (n < 21) {
4375         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4376         DisplayError(str, 0);
4377         return;
4378     }
4379
4380     /* Convert the move number to internal form */
4381     moveNum = (moveNum - 1) * 2;
4382     if (to_play == 'B') moveNum++;
4383     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4385                         0, 1);
4386       return;
4387     }
4388
4389     switch (relation) {
4390       case RELATION_OBSERVING_PLAYED:
4391       case RELATION_OBSERVING_STATIC:
4392         if (gamenum == -1) {
4393             /* Old ICC buglet */
4394             relation = RELATION_OBSERVING_STATIC;
4395         }
4396         newGameMode = IcsObserving;
4397         break;
4398       case RELATION_PLAYING_MYMOVE:
4399       case RELATION_PLAYING_NOTMYMOVE:
4400         newGameMode =
4401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4402             IcsPlayingWhite : IcsPlayingBlack;
4403         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4404         break;
4405       case RELATION_EXAMINING:
4406         newGameMode = IcsExamining;
4407         break;
4408       case RELATION_ISOLATED_BOARD:
4409       default:
4410         /* Just display this board.  If user was doing something else,
4411            we will forget about it until the next board comes. */
4412         newGameMode = IcsIdle;
4413         break;
4414       case RELATION_STARTING_POSITION:
4415         newGameMode = gameMode;
4416         break;
4417     }
4418
4419     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4420         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4421          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4422       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4423       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4424       static int lastBgGame = -1;
4425       char *toSqr;
4426       for (k = 0; k < ranks; k++) {
4427         for (j = 0; j < files; j++)
4428           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429         if(gameInfo.holdingsWidth > 1) {
4430              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432         }
4433       }
4434       CopyBoard(partnerBoard, board);
4435       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4436         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4437         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4438       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4439       if(toSqr = strchr(str, '-')) {
4440         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4441         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4442       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4443       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4444       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4445       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4446       if(twoBoards) {
4447           DisplayWhiteClock(white_time*fac, to_play == 'W');
4448           DisplayBlackClock(black_time*fac, to_play != 'W');
4449           activePartner = to_play;
4450           if(gamenum != lastBgGame) {
4451               char buf[MSG_SIZ];
4452               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4453               DisplayTitle(buf);
4454           }
4455           lastBgGame = gamenum;
4456           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4457                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4458       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4459                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4460       if(!twoBoards) DisplayMessage(partnerStatus, "");
4461         partnerBoardValid = TRUE;
4462       return;
4463     }
4464
4465     if(appData.dualBoard && appData.bgObserve) {
4466         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4467             SendToICS(ics_prefix), SendToICS("pobserve\n");
4468         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4469             char buf[MSG_SIZ];
4470             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4471             SendToICS(buf);
4472         }
4473     }
4474
4475     /* Modify behavior for initial board display on move listing
4476        of wild games.
4477        */
4478     switch (ics_getting_history) {
4479       case H_FALSE:
4480       case H_REQUESTED:
4481         break;
4482       case H_GOT_REQ_HEADER:
4483       case H_GOT_UNREQ_HEADER:
4484         /* This is the initial position of the current game */
4485         gamenum = ics_gamenum;
4486         moveNum = 0;            /* old ICS bug workaround */
4487         if (to_play == 'B') {
4488           startedFromSetupPosition = TRUE;
4489           blackPlaysFirst = TRUE;
4490           moveNum = 1;
4491           if (forwardMostMove == 0) forwardMostMove = 1;
4492           if (backwardMostMove == 0) backwardMostMove = 1;
4493           if (currentMove == 0) currentMove = 1;
4494         }
4495         newGameMode = gameMode;
4496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4497         break;
4498       case H_GOT_UNWANTED_HEADER:
4499         /* This is an initial board that we don't want */
4500         return;
4501       case H_GETTING_MOVES:
4502         /* Should not happen */
4503         DisplayError(_("Error gathering move list: extra board"), 0);
4504         ics_getting_history = H_FALSE;
4505         return;
4506     }
4507
4508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4509                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4510                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4511      /* [HGM] We seem to have switched variant unexpectedly
4512       * Try to guess new variant from board size
4513       */
4514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4519           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4520           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4521           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4522           /* Get a move list just to see the header, which
4523              will tell us whether this is really bug or zh */
4524           if (ics_getting_history == H_FALSE) {
4525             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528           }
4529     }
4530
4531     /* Take action if this is the first board of a new game, or of a
4532        different game than is currently being displayed.  */
4533     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4534         relation == RELATION_ISOLATED_BOARD) {
4535
4536         /* Forget the old game and get the history (if any) of the new one */
4537         if (gameMode != BeginningOfGame) {
4538           Reset(TRUE, TRUE);
4539         }
4540         newGame = TRUE;
4541         if (appData.autoRaiseBoard) BoardToTop();
4542         prevMove = -3;
4543         if (gamenum == -1) {
4544             newGameMode = IcsIdle;
4545         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4546                    appData.getMoveList && !reqFlag) {
4547             /* Need to get game history */
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552
4553         /* Initially flip the board to have black on the bottom if playing
4554            black or if the ICS flip flag is set, but let the user change
4555            it with the Flip View button. */
4556         flipView = appData.autoFlipView ?
4557           (newGameMode == IcsPlayingBlack) || ics_flip :
4558           appData.flipView;
4559
4560         /* Done with values from previous mode; copy in new ones */
4561         gameMode = newGameMode;
4562         ModeHighlight();
4563         ics_gamenum = gamenum;
4564         if (gamenum == gs_gamenum) {
4565             int klen = strlen(gs_kind);
4566             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4567             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4568             gameInfo.event = StrSave(str);
4569         } else {
4570             gameInfo.event = StrSave("ICS game");
4571         }
4572         gameInfo.site = StrSave(appData.icsHost);
4573         gameInfo.date = PGNDate();
4574         gameInfo.round = StrSave("-");
4575         gameInfo.white = StrSave(white);
4576         gameInfo.black = StrSave(black);
4577         timeControl = basetime * 60 * 1000;
4578         timeControl_2 = 0;
4579         timeIncrement = increment * 1000;
4580         movesPerSession = 0;
4581         gameInfo.timeControl = TimeControlTagValue();
4582         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4583   if (appData.debugMode) {
4584     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4585     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4586     setbuf(debugFP, NULL);
4587   }
4588
4589         gameInfo.outOfBook = NULL;
4590
4591         /* Do we have the ratings? */
4592         if (strcmp(player1Name, white) == 0 &&
4593             strcmp(player2Name, black) == 0) {
4594             if (appData.debugMode)
4595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4596                       player1Rating, player2Rating);
4597             gameInfo.whiteRating = player1Rating;
4598             gameInfo.blackRating = player2Rating;
4599         } else if (strcmp(player2Name, white) == 0 &&
4600                    strcmp(player1Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player2Rating, player1Rating);
4604             gameInfo.whiteRating = player2Rating;
4605             gameInfo.blackRating = player1Rating;
4606         }
4607         player1Name[0] = player2Name[0] = NULLCHAR;
4608
4609         /* Silence shouts if requested */
4610         if (appData.quietPlay &&
4611             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4612             SendToICS(ics_prefix);
4613             SendToICS("set shout 0\n");
4614         }
4615     }
4616
4617     /* Deal with midgame name changes */
4618     if (!newGame) {
4619         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4620             if (gameInfo.white) free(gameInfo.white);
4621             gameInfo.white = StrSave(white);
4622         }
4623         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4624             if (gameInfo.black) free(gameInfo.black);
4625             gameInfo.black = StrSave(black);
4626         }
4627     }
4628
4629     /* Throw away game result if anything actually changes in examine mode */
4630     if (gameMode == IcsExamining && !newGame) {
4631         gameInfo.result = GameUnfinished;
4632         if (gameInfo.resultDetails != NULL) {
4633             free(gameInfo.resultDetails);
4634             gameInfo.resultDetails = NULL;
4635         }
4636     }
4637
4638     /* In pausing && IcsExamining mode, we ignore boards coming
4639        in if they are in a different variation than we are. */
4640     if (pauseExamInvalid) return;
4641     if (pausing && gameMode == IcsExamining) {
4642         if (moveNum <= pauseExamForwardMostMove) {
4643             pauseExamInvalid = TRUE;
4644             forwardMostMove = pauseExamForwardMostMove;
4645             return;
4646         }
4647     }
4648
4649   if (appData.debugMode) {
4650     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4651   }
4652     /* Parse the board */
4653     for (k = 0; k < ranks; k++) {
4654       for (j = 0; j < files; j++)
4655         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4656       if(gameInfo.holdingsWidth > 1) {
4657            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4658            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4659       }
4660     }
4661     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4662       board[5][BOARD_RGHT+1] = WhiteAngel;
4663       board[6][BOARD_RGHT+1] = WhiteMarshall;
4664       board[1][0] = BlackMarshall;
4665       board[2][0] = BlackAngel;
4666       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4667     }
4668     CopyBoard(boards[moveNum], board);
4669     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4670     if (moveNum == 0) {
4671         startedFromSetupPosition =
4672           !CompareBoards(board, initialPosition);
4673         if(startedFromSetupPosition)
4674             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4675     }
4676
4677     /* [HGM] Set castling rights. Take the outermost Rooks,
4678        to make it also work for FRC opening positions. Note that board12
4679        is really defective for later FRC positions, as it has no way to
4680        indicate which Rook can castle if they are on the same side of King.
4681        For the initial position we grant rights to the outermost Rooks,
4682        and remember thos rights, and we then copy them on positions
4683        later in an FRC game. This means WB might not recognize castlings with
4684        Rooks that have moved back to their original position as illegal,
4685        but in ICS mode that is not its job anyway.
4686     */
4687     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4688     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4689
4690         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4691             if(board[0][i] == WhiteRook) j = i;
4692         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4694             if(board[0][i] == WhiteRook) j = i;
4695         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4698         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4701         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702
4703         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4704         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4705         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4706             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4707         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4708             if(board[BOARD_HEIGHT-1][k] == bKing)
4709                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4710         if(gameInfo.variant == VariantTwoKings) {
4711             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4712             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4713             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4714         }
4715     } else { int r;
4716         r = boards[moveNum][CASTLING][0] = initialRights[0];
4717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4718         r = boards[moveNum][CASTLING][1] = initialRights[1];
4719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4720         r = boards[moveNum][CASTLING][3] = initialRights[3];
4721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4722         r = boards[moveNum][CASTLING][4] = initialRights[4];
4723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4724         /* wildcastle kludge: always assume King has rights */
4725         r = boards[moveNum][CASTLING][2] = initialRights[2];
4726         r = boards[moveNum][CASTLING][5] = initialRights[5];
4727     }
4728     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4729     boards[moveNum][EP_STATUS] = EP_NONE;
4730     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4731     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4732     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4733
4734
4735     if (ics_getting_history == H_GOT_REQ_HEADER ||
4736         ics_getting_history == H_GOT_UNREQ_HEADER) {
4737         /* This was an initial position from a move list, not
4738            the current position */
4739         return;
4740     }
4741
4742     /* Update currentMove and known move number limits */
4743     newMove = newGame || moveNum > forwardMostMove;
4744
4745     if (newGame) {
4746         forwardMostMove = backwardMostMove = currentMove = moveNum;
4747         if (gameMode == IcsExamining && moveNum == 0) {
4748           /* Workaround for ICS limitation: we are not told the wild
4749              type when starting to examine a game.  But if we ask for
4750              the move list, the move list header will tell us */
4751             ics_getting_history = H_REQUESTED;
4752             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4753             SendToICS(str);
4754         }
4755     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4756                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4757 #if ZIPPY
4758         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4759         /* [HGM] applied this also to an engine that is silently watching        */
4760         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4761             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4762             gameInfo.variant == currentlyInitializedVariant) {
4763           takeback = forwardMostMove - moveNum;
4764           for (i = 0; i < takeback; i++) {
4765             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4766             SendToProgram("undo\n", &first);
4767           }
4768         }
4769 #endif
4770
4771         forwardMostMove = moveNum;
4772         if (!pausing || currentMove > forwardMostMove)
4773           currentMove = forwardMostMove;
4774     } else {
4775         /* New part of history that is not contiguous with old part */
4776         if (pausing && gameMode == IcsExamining) {
4777             pauseExamInvalid = TRUE;
4778             forwardMostMove = pauseExamForwardMostMove;
4779             return;
4780         }
4781         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4782 #if ZIPPY
4783             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4784                 // [HGM] when we will receive the move list we now request, it will be
4785                 // fed to the engine from the first move on. So if the engine is not
4786                 // in the initial position now, bring it there.
4787                 InitChessProgram(&first, 0);
4788             }
4789 #endif
4790             ics_getting_history = H_REQUESTED;
4791             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792             SendToICS(str);
4793         }
4794         forwardMostMove = backwardMostMove = currentMove = moveNum;
4795     }
4796
4797     /* Update the clocks */
4798     if (strchr(elapsed_time, '.')) {
4799       /* Time is in ms */
4800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4802     } else {
4803       /* Time is in seconds */
4804       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4805       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4806     }
4807
4808
4809 #if ZIPPY
4810     if (appData.zippyPlay && newGame &&
4811         gameMode != IcsObserving && gameMode != IcsIdle &&
4812         gameMode != IcsExamining)
4813       ZippyFirstBoard(moveNum, basetime, increment);
4814 #endif
4815
4816     /* Put the move on the move list, first converting
4817        to canonical algebraic form. */
4818     if (moveNum > 0) {
4819   if (appData.debugMode) {
4820     int f = forwardMostMove;
4821     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4822             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4823             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4825     fprintf(debugFP, "moveNum = %d\n", moveNum);
4826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4827     setbuf(debugFP, NULL);
4828   }
4829         if (moveNum <= backwardMostMove) {
4830             /* We don't know what the board looked like before
4831                this move.  Punt. */
4832           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4833             strcat(parseList[moveNum - 1], " ");
4834             strcat(parseList[moveNum - 1], elapsed_time);
4835             moveList[moveNum - 1][0] = NULLCHAR;
4836         } else if (strcmp(move_str, "none") == 0) {
4837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4838             /* Again, we don't know what the board looked like;
4839                this is really the start of the game. */
4840             parseList[moveNum - 1][0] = NULLCHAR;
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842             backwardMostMove = moveNum;
4843             startedFromSetupPosition = TRUE;
4844             fromX = fromY = toX = toY = -1;
4845         } else {
4846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4847           //                 So we parse the long-algebraic move string in stead of the SAN move
4848           int valid; char buf[MSG_SIZ], *prom;
4849
4850           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4851                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4852           // str looks something like "Q/a1-a2"; kill the slash
4853           if(str[1] == '/')
4854             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4855           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4856           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4857                 strcat(buf, prom); // long move lacks promo specification!
4858           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4859                 if(appData.debugMode)
4860                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4861                 safeStrCpy(move_str, buf, MSG_SIZ);
4862           }
4863           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4864                                 &fromX, &fromY, &toX, &toY, &promoChar)
4865                || ParseOneMove(buf, moveNum - 1, &moveType,
4866                                 &fromX, &fromY, &toX, &toY, &promoChar);
4867           // end of long SAN patch
4868           if (valid) {
4869             (void) CoordsToAlgebraic(boards[moveNum - 1],
4870                                      PosFlags(moveNum - 1),
4871                                      fromY, fromX, toY, toX, promoChar,
4872                                      parseList[moveNum-1]);
4873             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4874               case MT_NONE:
4875               case MT_STALEMATE:
4876               default:
4877                 break;
4878               case MT_CHECK:
4879                 if(!IS_SHOGI(gameInfo.variant))
4880                     strcat(parseList[moveNum - 1], "+");
4881                 break;
4882               case MT_CHECKMATE:
4883               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4884                 strcat(parseList[moveNum - 1], "#");
4885                 break;
4886             }
4887             strcat(parseList[moveNum - 1], " ");
4888             strcat(parseList[moveNum - 1], elapsed_time);
4889             /* currentMoveString is set as a side-effect of ParseOneMove */
4890             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4891             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4892             strcat(moveList[moveNum - 1], "\n");
4893
4894             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4895                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4896               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4897                 ChessSquare old, new = boards[moveNum][k][j];
4898                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4899                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4900                   if(old == new) continue;
4901                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4902                   else if(new == WhiteWazir || new == BlackWazir) {
4903                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4904                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4905                       else boards[moveNum][k][j] = old; // preserve type of Gold
4906                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4907                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4908               }
4909           } else {
4910             /* Move from ICS was illegal!?  Punt. */
4911             if (appData.debugMode) {
4912               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4913               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4914             }
4915             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4916             strcat(parseList[moveNum - 1], " ");
4917             strcat(parseList[moveNum - 1], elapsed_time);
4918             moveList[moveNum - 1][0] = NULLCHAR;
4919             fromX = fromY = toX = toY = -1;
4920           }
4921         }
4922   if (appData.debugMode) {
4923     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4924     setbuf(debugFP, NULL);
4925   }
4926
4927 #if ZIPPY
4928         /* Send move to chess program (BEFORE animating it). */
4929         if (appData.zippyPlay && !newGame && newMove &&
4930            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4931
4932             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4933                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4934                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4936                             move_str);
4937                     DisplayError(str, 0);
4938                 } else {
4939                     if (first.sendTime) {
4940                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4941                     }
4942                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4943                     if (firstMove && !bookHit) {
4944                         firstMove = FALSE;
4945                         if (first.useColors) {
4946                           SendToProgram(gameMode == IcsPlayingWhite ?
4947                                         "white\ngo\n" :
4948                                         "black\ngo\n", &first);
4949                         } else {
4950                           SendToProgram("go\n", &first);
4951                         }
4952                         first.maybeThinking = TRUE;
4953                     }
4954                 }
4955             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4956               if (moveList[moveNum - 1][0] == NULLCHAR) {
4957                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4958                 DisplayError(str, 0);
4959               } else {
4960                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4961                 SendMoveToProgram(moveNum - 1, &first);
4962               }
4963             }
4964         }
4965 #endif
4966     }
4967
4968     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4969         /* If move comes from a remote source, animate it.  If it
4970            isn't remote, it will have already been animated. */
4971         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4972             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4973         }
4974         if (!pausing && appData.highlightLastMove) {
4975             SetHighlights(fromX, fromY, toX, toY);
4976         }
4977     }
4978
4979     /* Start the clocks */
4980     whiteFlag = blackFlag = FALSE;
4981     appData.clockMode = !(basetime == 0 && increment == 0);
4982     if (ticking == 0) {
4983       ics_clock_paused = TRUE;
4984       StopClocks();
4985     } else if (ticking == 1) {
4986       ics_clock_paused = FALSE;
4987     }
4988     if (gameMode == IcsIdle ||
4989         relation == RELATION_OBSERVING_STATIC ||
4990         relation == RELATION_EXAMINING ||
4991         ics_clock_paused)
4992       DisplayBothClocks();
4993     else
4994       StartClocks();
4995
4996     /* Display opponents and material strengths */
4997     if (gameInfo.variant != VariantBughouse &&
4998         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4999         if (tinyLayout || smallLayout) {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5007                     basetime, increment, (int) gameInfo.variant);
5008         } else {
5009             if(gameInfo.variant == VariantNormal)
5010               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5011                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5012                     basetime, increment);
5013             else
5014               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5015                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5016                     basetime, increment, VariantName(gameInfo.variant));
5017         }
5018         DisplayTitle(str);
5019   if (appData.debugMode) {
5020     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5021   }
5022     }
5023
5024
5025     /* Display the board */
5026     if (!pausing && !appData.noGUI) {
5027
5028       if (appData.premove)
5029           if (!gotPremove ||
5030              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5031              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5032               ClearPremoveHighlights();
5033
5034       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5035         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5036       DrawPosition(j, boards[currentMove]);
5037
5038       DisplayMove(moveNum - 1);
5039       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5040             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5041               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5042         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5043       }
5044     }
5045
5046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5047 #if ZIPPY
5048     if(bookHit) { // [HGM] book: simulate book reply
5049         static char bookMove[MSG_SIZ]; // a bit generous?
5050
5051         programStats.nodes = programStats.depth = programStats.time =
5052         programStats.score = programStats.got_only_move = 0;
5053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5054
5055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5056         strcat(bookMove, bookHit);
5057         HandleMachineMove(bookMove, &first);
5058     }
5059 #endif
5060 }
5061
5062 void
5063 GetMoveListEvent ()
5064 {
5065     char buf[MSG_SIZ];
5066     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5067         ics_getting_history = H_REQUESTED;
5068         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5069         SendToICS(buf);
5070     }
5071 }
5072
5073 void
5074 SendToBoth (char *msg)
5075 {   // to make it easy to keep two engines in step in dual analysis
5076     SendToProgram(msg, &first);
5077     if(second.analyzing) SendToProgram(msg, &second);
5078 }
5079
5080 void
5081 AnalysisPeriodicEvent (int force)
5082 {
5083     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5084          && !force) || !appData.periodicUpdates)
5085       return;
5086
5087     /* Send . command to Crafty to collect stats */
5088     SendToBoth(".\n");
5089
5090     /* Don't send another until we get a response (this makes
5091        us stop sending to old Crafty's which don't understand
5092        the "." command (sending illegal cmds resets node count & time,
5093        which looks bad)) */
5094     programStats.ok_to_send = 0;
5095 }
5096
5097 void
5098 ics_update_width (int new_width)
5099 {
5100         ics_printf("set width %d\n", new_width);
5101 }
5102
5103 void
5104 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5105 {
5106     char buf[MSG_SIZ];
5107
5108     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5109         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5110             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5111             SendToProgram(buf, cps);
5112             return;
5113         }
5114         // null move in variant where engine does not understand it (for analysis purposes)
5115         SendBoard(cps, moveNum + 1); // send position after move in stead.
5116         return;
5117     }
5118     if (cps->useUsermove) {
5119       SendToProgram("usermove ", cps);
5120     }
5121     if (cps->useSAN) {
5122       char *space;
5123       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5124         int len = space - parseList[moveNum];
5125         memcpy(buf, parseList[moveNum], len);
5126         buf[len++] = '\n';
5127         buf[len] = NULLCHAR;
5128       } else {
5129         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5130       }
5131       SendToProgram(buf, cps);
5132     } else {
5133       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5134         AlphaRank(moveList[moveNum], 4);
5135         SendToProgram(moveList[moveNum], cps);
5136         AlphaRank(moveList[moveNum], 4); // and back
5137       } else
5138       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5139        * the engine. It would be nice to have a better way to identify castle
5140        * moves here. */
5141       if(appData.fischerCastling && cps->useOOCastle) {
5142         int fromX = moveList[moveNum][0] - AAA;
5143         int fromY = moveList[moveNum][1] - ONE;
5144         int toX = moveList[moveNum][2] - AAA;
5145         int toY = moveList[moveNum][3] - ONE;
5146         if((boards[moveNum][fromY][fromX] == WhiteKing
5147             && boards[moveNum][toY][toX] == WhiteRook)
5148            || (boards[moveNum][fromY][fromX] == BlackKing
5149                && boards[moveNum][toY][toX] == BlackRook)) {
5150           if(toX > fromX) SendToProgram("O-O\n", cps);
5151           else SendToProgram("O-O-O\n", cps);
5152         }
5153         else SendToProgram(moveList[moveNum], cps);
5154       } else
5155       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5156         char *m = moveList[moveNum];
5157         static char c[2];
5158         *c = m[7]; // promoChar
5159         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5160           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5161                                                m[2], m[3] - '0',
5162                                                m[5], m[6] - '0',
5163                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5164         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5165           *c = m[9];
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5167                                                m[7], m[8] - '0',
5168                                                m[7], m[8] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2], m[3] - '0', c);
5172         } else
5173           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5174                                                m[5], m[6] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[2], m[3] - '0', c);
5177           SendToProgram(buf, cps);
5178       } else
5179       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5180         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5181           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5182           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5183                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5186                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5187         SendToProgram(buf, cps);
5188       }
5189       else SendToProgram(moveList[moveNum], cps);
5190       /* End of additions by Tord */
5191     }
5192
5193     /* [HGM] setting up the opening has brought engine in force mode! */
5194     /*       Send 'go' if we are in a mode where machine should play. */
5195     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5196         (gameMode == TwoMachinesPlay   ||
5197 #if ZIPPY
5198          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5199 #endif
5200          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5201         SendToProgram("go\n", cps);
5202   if (appData.debugMode) {
5203     fprintf(debugFP, "(extra)\n");
5204   }
5205     }
5206     setboardSpoiledMachineBlack = 0;
5207 }
5208
5209 void
5210 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5211 {
5212     char user_move[MSG_SIZ];
5213     char suffix[4];
5214
5215     if(gameInfo.variant == VariantSChess && promoChar) {
5216         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5217         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5218     } else suffix[0] = NULLCHAR;
5219
5220     switch (moveType) {
5221       default:
5222         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5223                 (int)moveType, fromX, fromY, toX, toY);
5224         DisplayError(user_move + strlen("say "), 0);
5225         break;
5226       case WhiteKingSideCastle:
5227       case BlackKingSideCastle:
5228       case WhiteQueenSideCastleWild:
5229       case BlackQueenSideCastleWild:
5230       /* PUSH Fabien */
5231       case WhiteHSideCastleFR:
5232       case BlackHSideCastleFR:
5233       /* POP Fabien */
5234         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5235         break;
5236       case WhiteQueenSideCastle:
5237       case BlackQueenSideCastle:
5238       case WhiteKingSideCastleWild:
5239       case BlackKingSideCastleWild:
5240       /* PUSH Fabien */
5241       case WhiteASideCastleFR:
5242       case BlackASideCastleFR:
5243       /* POP Fabien */
5244         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5245         break;
5246       case WhiteNonPromotion:
5247       case BlackNonPromotion:
5248         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5249         break;
5250       case WhitePromotion:
5251       case BlackPromotion:
5252         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5253            gameInfo.variant == VariantMakruk)
5254           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5255                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5256                 PieceToChar(WhiteFerz));
5257         else if(gameInfo.variant == VariantGreat)
5258           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5259                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5260                 PieceToChar(WhiteMan));
5261         else
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 promoChar);
5265         break;
5266       case WhiteDrop:
5267       case BlackDrop:
5268       drop:
5269         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5270                  ToUpper(PieceToChar((ChessSquare) fromX)),
5271                  AAA + toX, ONE + toY);
5272         break;
5273       case IllegalMove:  /* could be a variant we don't quite understand */
5274         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5275       case NormalMove:
5276       case WhiteCapturesEnPassant:
5277       case BlackCapturesEnPassant:
5278         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5279                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5280         break;
5281     }
5282     SendToICS(user_move);
5283     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5284         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5285 }
5286
5287 void
5288 UploadGameEvent ()
5289 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5290     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5291     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5292     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5293       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5294       return;
5295     }
5296     if(gameMode != IcsExamining) { // is this ever not the case?
5297         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5298
5299         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5300           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5301         } else { // on FICS we must first go to general examine mode
5302           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5303         }
5304         if(gameInfo.variant != VariantNormal) {
5305             // try figure out wild number, as xboard names are not always valid on ICS
5306             for(i=1; i<=36; i++) {
5307               snprintf(buf, MSG_SIZ, "wild/%d", i);
5308                 if(StringToVariant(buf) == gameInfo.variant) break;
5309             }
5310             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5311             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5312             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5313         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5314         SendToICS(ics_prefix);
5315         SendToICS(buf);
5316         if(startedFromSetupPosition || backwardMostMove != 0) {
5317           fen = PositionToFEN(backwardMostMove, NULL, 1);
5318           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5319             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5320             SendToICS(buf);
5321           } else { // FICS: everything has to set by separate bsetup commands
5322             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5323             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5324             SendToICS(buf);
5325             if(!WhiteOnMove(backwardMostMove)) {
5326                 SendToICS("bsetup tomove black\n");
5327             }
5328             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5329             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5330             SendToICS(buf);
5331             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5332             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5333             SendToICS(buf);
5334             i = boards[backwardMostMove][EP_STATUS];
5335             if(i >= 0) { // set e.p.
5336               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5337                 SendToICS(buf);
5338             }
5339             bsetup++;
5340           }
5341         }
5342       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5343             SendToICS("bsetup done\n"); // switch to normal examining.
5344     }
5345     for(i = backwardMostMove; i<last; i++) {
5346         char buf[20];
5347         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5348         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5349             int len = strlen(moveList[i]);
5350             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5351             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5352         }
5353         SendToICS(buf);
5354     }
5355     SendToICS(ics_prefix);
5356     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5357 }
5358
5359 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5360 int legNr = 1;
5361
5362 void
5363 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5364 {
5365     if (rf == DROP_RANK) {
5366       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5367       sprintf(move, "%c@%c%c\n",
5368                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5369     } else {
5370         if (promoChar == 'x' || promoChar == NULLCHAR) {
5371           sprintf(move, "%c%c%c%c\n",
5372                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5373           if(killX >= 0 && killY >= 0) {
5374             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5375             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5376           }
5377         } else {
5378             sprintf(move, "%c%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5383           }
5384         }
5385     }
5386 }
5387
5388 void
5389 ProcessICSInitScript (FILE *f)
5390 {
5391     char buf[MSG_SIZ];
5392
5393     while (fgets(buf, MSG_SIZ, f)) {
5394         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5395     }
5396
5397     fclose(f);
5398 }
5399
5400
5401 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5402 int dragging;
5403 static ClickType lastClickType;
5404
5405 int
5406 PieceInString (char *s, ChessSquare piece)
5407 {
5408   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5409   while((p = strchr(s, ID))) {
5410     if(!suffix || p[1] == suffix) return TRUE;
5411     s = p;
5412   }
5413   return FALSE;
5414 }
5415
5416 int
5417 Partner (ChessSquare *p)
5418 { // change piece into promotion partner if one shogi-promotes to the other
5419   ChessSquare partner = promoPartner[*p];
5420   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5421   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5422   *p = partner;
5423   return 1;
5424 }
5425
5426 void
5427 Sweep (int step)
5428 {
5429     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5430     static int toggleFlag;
5431     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5432     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5433     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5434     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5435     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5436     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5437     do {
5438         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5439         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5440         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5441         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5442         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5443         if(!step) step = -1;
5444     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5445             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5446             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5447             promoSweep == pawn ||
5448             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5449             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5450     if(toX >= 0) {
5451         int victim = boards[currentMove][toY][toX];
5452         boards[currentMove][toY][toX] = promoSweep;
5453         DrawPosition(FALSE, boards[currentMove]);
5454         boards[currentMove][toY][toX] = victim;
5455     } else
5456     ChangeDragPiece(promoSweep);
5457 }
5458
5459 int
5460 PromoScroll (int x, int y)
5461 {
5462   int step = 0;
5463
5464   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5465   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5466   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5467   if(!step) return FALSE;
5468   lastX = x; lastY = y;
5469   if((promoSweep < BlackPawn) == flipView) step = -step;
5470   if(step > 0) selectFlag = 1;
5471   if(!selectFlag) Sweep(step);
5472   return FALSE;
5473 }
5474
5475 void
5476 NextPiece (int step)
5477 {
5478     ChessSquare piece = boards[currentMove][toY][toX];
5479     do {
5480         pieceSweep -= step;
5481         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5482         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5483         if(!step) step = -1;
5484     } while(PieceToChar(pieceSweep) == '.');
5485     boards[currentMove][toY][toX] = pieceSweep;
5486     DrawPosition(FALSE, boards[currentMove]);
5487     boards[currentMove][toY][toX] = piece;
5488 }
5489 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5490 void
5491 AlphaRank (char *move, int n)
5492 {
5493 //    char *p = move, c; int x, y;
5494
5495     if (appData.debugMode) {
5496         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5497     }
5498
5499     if(move[1]=='*' &&
5500        move[2]>='0' && move[2]<='9' &&
5501        move[3]>='a' && move[3]<='x'    ) {
5502         move[1] = '@';
5503         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5504         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5505     } else
5506     if(move[0]>='0' && move[0]<='9' &&
5507        move[1]>='a' && move[1]<='x' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         /* input move, Shogi -> normal */
5511         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5512         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5513         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5514         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5515     } else
5516     if(move[1]=='@' &&
5517        move[3]>='0' && move[3]<='9' &&
5518        move[2]>='a' && move[2]<='x'    ) {
5519         move[1] = '*';
5520         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5522     } else
5523     if(
5524        move[0]>='a' && move[0]<='x' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527          /* output move, normal -> Shogi */
5528         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5529         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5530         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5531         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5532         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5533     }
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "   out = '%s'\n", move);
5536     }
5537 }
5538
5539 char yy_textstr[8000];
5540
5541 /* Parser for moves from gnuchess, ICS, or user typein box */
5542 Boolean
5543 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5544 {
5545     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5546
5547     switch (*moveType) {
5548       case WhitePromotion:
5549       case BlackPromotion:
5550       case WhiteNonPromotion:
5551       case BlackNonPromotion:
5552       case NormalMove:
5553       case FirstLeg:
5554       case WhiteCapturesEnPassant:
5555       case BlackCapturesEnPassant:
5556       case WhiteKingSideCastle:
5557       case WhiteQueenSideCastle:
5558       case BlackKingSideCastle:
5559       case BlackQueenSideCastle:
5560       case WhiteKingSideCastleWild:
5561       case WhiteQueenSideCastleWild:
5562       case BlackKingSideCastleWild:
5563       case BlackQueenSideCastleWild:
5564       /* Code added by Tord: */
5565       case WhiteHSideCastleFR:
5566       case WhiteASideCastleFR:
5567       case BlackHSideCastleFR:
5568       case BlackASideCastleFR:
5569       /* End of code added by Tord */
5570       case IllegalMove:         /* bug or odd chess variant */
5571         if(currentMoveString[1] == '@') { // illegal drop
5572           *fromX = WhiteOnMove(moveNum) ?
5573             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5574             (int) CharToPiece(ToLower(currentMoveString[0]));
5575           goto drop;
5576         }
5577         *fromX = currentMoveString[0] - AAA;
5578         *fromY = currentMoveString[1] - ONE;
5579         *toX = currentMoveString[2] - AAA;
5580         *toY = currentMoveString[3] - ONE;
5581         *promoChar = currentMoveString[4];
5582         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5583         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5584             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5585     if (appData.debugMode) {
5586         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5587     }
5588             *fromX = *fromY = *toX = *toY = 0;
5589             return FALSE;
5590         }
5591         if (appData.testLegality) {
5592           return (*moveType != IllegalMove);
5593         } else {
5594           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5595                          // [HGM] lion: if this is a double move we are less critical
5596                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5597         }
5598
5599       case WhiteDrop:
5600       case BlackDrop:
5601         *fromX = *moveType == WhiteDrop ?
5602           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5603           (int) CharToPiece(ToLower(currentMoveString[0]));
5604       drop:
5605         *fromY = DROP_RANK;
5606         *toX = currentMoveString[2] - AAA;
5607         *toY = currentMoveString[3] - ONE;
5608         *promoChar = NULLCHAR;
5609         return TRUE;
5610
5611       case AmbiguousMove:
5612       case ImpossibleMove:
5613       case EndOfFile:
5614       case ElapsedTime:
5615       case Comment:
5616       case PGNTag:
5617       case NAG:
5618       case WhiteWins:
5619       case BlackWins:
5620       case GameIsDrawn:
5621       default:
5622     if (appData.debugMode) {
5623         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5624     }
5625         /* bug? */
5626         *fromX = *fromY = *toX = *toY = 0;
5627         *promoChar = NULLCHAR;
5628         return FALSE;
5629     }
5630 }
5631
5632 Boolean pushed = FALSE;
5633 char *lastParseAttempt;
5634
5635 void
5636 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5637 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5638   int fromX, fromY, toX, toY; char promoChar;
5639   ChessMove moveType;
5640   Boolean valid;
5641   int nr = 0;
5642
5643   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5644   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5645     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5646     pushed = TRUE;
5647   }
5648   endPV = forwardMostMove;
5649   do {
5650     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5651     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5652     lastParseAttempt = pv;
5653     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5654     if(!valid && nr == 0 &&
5655        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5656         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5657         // Hande case where played move is different from leading PV move
5658         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5659         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5660         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5661         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5662           endPV += 2; // if position different, keep this
5663           moveList[endPV-1][0] = fromX + AAA;
5664           moveList[endPV-1][1] = fromY + ONE;
5665           moveList[endPV-1][2] = toX + AAA;
5666           moveList[endPV-1][3] = toY + ONE;
5667           parseList[endPV-1][0] = NULLCHAR;
5668           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5669         }
5670       }
5671     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5672     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5673     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5674     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5675         valid++; // allow comments in PV
5676         continue;
5677     }
5678     nr++;
5679     if(endPV+1 > framePtr) break; // no space, truncate
5680     if(!valid) break;
5681     endPV++;
5682     CopyBoard(boards[endPV], boards[endPV-1]);
5683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5684     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5685     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5686     CoordsToAlgebraic(boards[endPV - 1],
5687                              PosFlags(endPV - 1),
5688                              fromY, fromX, toY, toX, promoChar,
5689                              parseList[endPV - 1]);
5690   } while(valid);
5691   if(atEnd == 2) return; // used hidden, for PV conversion
5692   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5693   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5694   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5695                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5696   DrawPosition(TRUE, boards[currentMove]);
5697 }
5698
5699 int
5700 MultiPV (ChessProgramState *cps, int kind)
5701 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5702         int i;
5703         for(i=0; i<cps->nrOptions; i++) {
5704             char *s = cps->option[i].name;
5705             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5706             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5707                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5708         }
5709         return -1;
5710 }
5711
5712 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5713 static int multi, pv_margin;
5714 static ChessProgramState *activeCps;
5715
5716 Boolean
5717 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5718 {
5719         int startPV, lineStart, origIndex = index;
5720         char *p, buf2[MSG_SIZ];
5721         ChessProgramState *cps = (pane ? &second : &first);
5722
5723         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5724         lastX = x; lastY = y;
5725         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5726         lineStart = startPV = index;
5727         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5728         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5729         index = startPV;
5730         do{ while(buf[index] && buf[index] != '\n') index++;
5731         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5732         buf[index] = 0;
5733         if(lineStart == 0 && gameMode == AnalyzeMode) {
5734             int n = 0;
5735             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5736             if(n == 0) { // click not on "fewer" or "more"
5737                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5738                     pv_margin = cps->option[multi].value;
5739                     activeCps = cps; // non-null signals margin adjustment
5740                 }
5741             } else if((multi = MultiPV(cps, 1)) >= 0) {
5742                 n += cps->option[multi].value; if(n < 1) n = 1;
5743                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5744                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5745                 cps->option[multi].value = n;
5746                 *start = *end = 0;
5747                 return FALSE;
5748             }
5749         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5750                 ExcludeClick(origIndex - lineStart);
5751                 return FALSE;
5752         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5753                 Collapse(origIndex - lineStart);
5754                 return FALSE;
5755         }
5756         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5757         *start = startPV; *end = index-1;
5758         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5759         return TRUE;
5760 }
5761
5762 char *
5763 PvToSAN (char *pv)
5764 {
5765         static char buf[10*MSG_SIZ];
5766         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5767         *buf = NULLCHAR;
5768         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5769         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5770         for(i = forwardMostMove; i<endPV; i++){
5771             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5772             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5773             k += strlen(buf+k);
5774         }
5775         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5776         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5777         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5778         endPV = savedEnd;
5779         return buf;
5780 }
5781
5782 Boolean
5783 LoadPV (int x, int y)
5784 { // called on right mouse click to load PV
5785   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5786   lastX = x; lastY = y;
5787   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5788   extendGame = FALSE;
5789   return TRUE;
5790 }
5791
5792 void
5793 UnLoadPV ()
5794 {
5795   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5796   if(activeCps) {
5797     if(pv_margin != activeCps->option[multi].value) {
5798       char buf[MSG_SIZ];
5799       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5800       SendToProgram(buf, activeCps);
5801       activeCps->option[multi].value = pv_margin;
5802     }
5803     activeCps = NULL;
5804     return;
5805   }
5806   if(endPV < 0) return;
5807   if(appData.autoCopyPV) CopyFENToClipboard();
5808   endPV = -1;
5809   if(extendGame && currentMove > forwardMostMove) {
5810         Boolean saveAnimate = appData.animate;
5811         if(pushed) {
5812             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5813                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5814             } else storedGames--; // abandon shelved tail of original game
5815         }
5816         pushed = FALSE;
5817         forwardMostMove = currentMove;
5818         currentMove = oldFMM;
5819         appData.animate = FALSE;
5820         ToNrEvent(forwardMostMove);
5821         appData.animate = saveAnimate;
5822   }
5823   currentMove = forwardMostMove;
5824   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5825   ClearPremoveHighlights();
5826   DrawPosition(TRUE, boards[currentMove]);
5827 }
5828
5829 void
5830 MovePV (int x, int y, int h)
5831 { // step through PV based on mouse coordinates (called on mouse move)
5832   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5833
5834   if(activeCps) { // adjusting engine's multi-pv margin
5835     if(x > lastX) pv_margin++; else
5836     if(x < lastX) pv_margin -= (pv_margin > 0);
5837     if(x != lastX) {
5838       char buf[MSG_SIZ];
5839       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5840       DisplayMessage(buf, "");
5841     }
5842     lastX = x;
5843     return;
5844   }
5845   // we must somehow check if right button is still down (might be released off board!)
5846   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5847   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5848   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5849   if(!step) return;
5850   lastX = x; lastY = y;
5851
5852   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5853   if(endPV < 0) return;
5854   if(y < margin) step = 1; else
5855   if(y > h - margin) step = -1;
5856   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5857   currentMove += step;
5858   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5859   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5860                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5861   DrawPosition(FALSE, boards[currentMove]);
5862 }
5863
5864
5865 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5866 // All positions will have equal probability, but the current method will not provide a unique
5867 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5868 #define DARK 1
5869 #define LITE 2
5870 #define ANY 3
5871
5872 int squaresLeft[4];
5873 int piecesLeft[(int)BlackPawn];
5874 int seed, nrOfShuffles;
5875
5876 void
5877 GetPositionNumber ()
5878 {       // sets global variable seed
5879         int i;
5880
5881         seed = appData.defaultFrcPosition;
5882         if(seed < 0) { // randomize based on time for negative FRC position numbers
5883                 for(i=0; i<50; i++) seed += random();
5884                 seed = random() ^ random() >> 8 ^ random() << 8;
5885                 if(seed<0) seed = -seed;
5886         }
5887 }
5888
5889 int
5890 put (Board board, int pieceType, int rank, int n, int shade)
5891 // put the piece on the (n-1)-th empty squares of the given shade
5892 {
5893         int i;
5894
5895         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5896                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5897                         board[rank][i] = (ChessSquare) pieceType;
5898                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5899                         squaresLeft[ANY]--;
5900                         piecesLeft[pieceType]--;
5901                         return i;
5902                 }
5903         }
5904         return -1;
5905 }
5906
5907
5908 void
5909 AddOnePiece (Board board, int pieceType, int rank, int shade)
5910 // calculate where the next piece goes, (any empty square), and put it there
5911 {
5912         int i;
5913
5914         i = seed % squaresLeft[shade];
5915         nrOfShuffles *= squaresLeft[shade];
5916         seed /= squaresLeft[shade];
5917         put(board, pieceType, rank, i, shade);
5918 }
5919
5920 void
5921 AddTwoPieces (Board board, int pieceType, int rank)
5922 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5923 {
5924         int i, n=squaresLeft[ANY], j=n-1, k;
5925
5926         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5927         i = seed % k;  // pick one
5928         nrOfShuffles *= k;
5929         seed /= k;
5930         while(i >= j) i -= j--;
5931         j = n - 1 - j; i += j;
5932         put(board, pieceType, rank, j, ANY);
5933         put(board, pieceType, rank, i, ANY);
5934 }
5935
5936 void
5937 SetUpShuffle (Board board, int number)
5938 {
5939         int i, p, first=1;
5940
5941         GetPositionNumber(); nrOfShuffles = 1;
5942
5943         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5944         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5945         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5946
5947         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5948
5949         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5950             p = (int) board[0][i];
5951             if(p < (int) BlackPawn) piecesLeft[p] ++;
5952             board[0][i] = EmptySquare;
5953         }
5954
5955         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5956             // shuffles restricted to allow normal castling put KRR first
5957             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5958                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5959             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5960                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5961             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5962                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5963             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5964                 put(board, WhiteRook, 0, 0, ANY);
5965             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5966         }
5967
5968         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5969             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5970             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5971                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5972                 while(piecesLeft[p] >= 2) {
5973                     AddOnePiece(board, p, 0, LITE);
5974                     AddOnePiece(board, p, 0, DARK);
5975                 }
5976                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5977             }
5978
5979         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5980             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5981             // but we leave King and Rooks for last, to possibly obey FRC restriction
5982             if(p == (int)WhiteRook) continue;
5983             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5984             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5985         }
5986
5987         // now everything is placed, except perhaps King (Unicorn) and Rooks
5988
5989         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5990             // Last King gets castling rights
5991             while(piecesLeft[(int)WhiteUnicorn]) {
5992                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5993                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5994             }
5995
5996             while(piecesLeft[(int)WhiteKing]) {
5997                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5999             }
6000
6001
6002         } else {
6003             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6004             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6005         }
6006
6007         // Only Rooks can be left; simply place them all
6008         while(piecesLeft[(int)WhiteRook]) {
6009                 i = put(board, WhiteRook, 0, 0, ANY);
6010                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6011                         if(first) {
6012                                 first=0;
6013                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6014                         }
6015                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6016                 }
6017         }
6018         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6019             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6020         }
6021
6022         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6023 }
6024
6025 int
6026 ptclen (const char *s, char *escapes)
6027 {
6028     int n = 0;
6029     if(!*escapes) return strlen(s);
6030     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6031     return n;
6032 }
6033
6034 int
6035 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6036 /* [HGM] moved here from winboard.c because of its general usefulness */
6037 /*       Basically a safe strcpy that uses the last character as King */
6038 {
6039     int result = FALSE; int NrPieces;
6040     unsigned char partner[EmptySquare];
6041
6042     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6043                     && NrPieces >= 12 && !(NrPieces&1)) {
6044         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6045
6046         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6047         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6048             char *p, c=0;
6049             if(map[j] == '/') offs = WhitePBishop - i, j++;
6050             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6051             table[i+offs] = map[j++];
6052             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6053             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6054             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6055         }
6056         table[(int) WhiteKing]  = map[j++];
6057         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6058             char *p, c=0;
6059             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6060             i = WHITE_TO_BLACK ii;
6061             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6062             table[i+offs] = map[j++];
6063             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6064             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6065             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6066         }
6067         table[(int) BlackKing]  = map[j++];
6068
6069
6070         if(*escapes) { // set up promotion pairing
6071             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6072             // pieceToChar entirely filled, so we can look up specified partners
6073             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6074                 int c = table[i];
6075                 if(c == '^' || c == '-') { // has specified partner
6076                     int p;
6077                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6078                     if(c == '^') table[i] = '+';
6079                     if(p < EmptySquare) {
6080                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6081                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6082                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6083                     }
6084                 } else if(c == '*') {
6085                     table[i] = partner[i];
6086                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6087                 }
6088             }
6089         }
6090
6091         result = TRUE;
6092     }
6093
6094     return result;
6095 }
6096
6097 int
6098 SetCharTable (unsigned char *table, const char * map)
6099 {
6100     return SetCharTableEsc(table, map, "");
6101 }
6102
6103 void
6104 Prelude (Board board)
6105 {       // [HGM] superchess: random selection of exo-pieces
6106         int i, j, k; ChessSquare p;
6107         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6108
6109         GetPositionNumber(); // use FRC position number
6110
6111         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6112             SetCharTable(pieceToChar, appData.pieceToCharTable);
6113             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6114                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6115         }
6116
6117         j = seed%4;                 seed /= 4;
6118         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6119         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6120         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6121         j = seed%3 + (seed%3 >= j); seed /= 3;
6122         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6123         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6124         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6125         j = seed%3;                 seed /= 3;
6126         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%2 + (seed%2 >= j); seed /= 2;
6130         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6134         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6135         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6136         put(board, exoPieces[0],    0, 0, ANY);
6137         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6138 }
6139
6140 void
6141 InitPosition (int redraw)
6142 {
6143     ChessSquare (* pieces)[BOARD_FILES];
6144     int i, j, pawnRow=1, pieceRows=1, overrule,
6145     oldx = gameInfo.boardWidth,
6146     oldy = gameInfo.boardHeight,
6147     oldh = gameInfo.holdingsWidth;
6148     static int oldv;
6149
6150     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6151
6152     /* [AS] Initialize pv info list [HGM] and game status */
6153     {
6154         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6155             pvInfoList[i].depth = 0;
6156             boards[i][EP_STATUS] = EP_NONE;
6157             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6158         }
6159
6160         initialRulePlies = 0; /* 50-move counter start */
6161
6162         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6163         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6164     }
6165
6166
6167     /* [HGM] logic here is completely changed. In stead of full positions */
6168     /* the initialized data only consist of the two backranks. The switch */
6169     /* selects which one we will use, which is than copied to the Board   */
6170     /* initialPosition, which for the rest is initialized by Pawns and    */
6171     /* empty squares. This initial position is then copied to boards[0],  */
6172     /* possibly after shuffling, so that it remains available.            */
6173
6174     gameInfo.holdingsWidth = 0; /* default board sizes */
6175     gameInfo.boardWidth    = 8;
6176     gameInfo.boardHeight   = 8;
6177     gameInfo.holdingsSize  = 0;
6178     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6179     for(i=0; i<BOARD_FILES-6; i++)
6180       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6181     initialPosition[EP_STATUS] = EP_NONE;
6182     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6183     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6184     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6185          SetCharTable(pieceNickName, appData.pieceNickNames);
6186     else SetCharTable(pieceNickName, "............");
6187     pieces = FIDEArray;
6188
6189     switch (gameInfo.variant) {
6190     case VariantFischeRandom:
6191       shuffleOpenings = TRUE;
6192       appData.fischerCastling = TRUE;
6193     default:
6194       break;
6195     case VariantShatranj:
6196       pieces = ShatranjArray;
6197       nrCastlingRights = 0;
6198       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6199       break;
6200     case VariantMakruk:
6201       pieces = makrukArray;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6204       break;
6205     case VariantASEAN:
6206       pieces = aseanArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6209       break;
6210     case VariantTwoKings:
6211       pieces = twoKingsArray;
6212       break;
6213     case VariantGrand:
6214       pieces = GrandArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6217       gameInfo.boardWidth = 10;
6218       gameInfo.boardHeight = 10;
6219       gameInfo.holdingsSize = 7;
6220       break;
6221     case VariantCapaRandom:
6222       shuffleOpenings = TRUE;
6223       appData.fischerCastling = TRUE;
6224     case VariantCapablanca:
6225       pieces = CapablancaArray;
6226       gameInfo.boardWidth = 10;
6227       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6228       break;
6229     case VariantGothic:
6230       pieces = GothicArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6233       break;
6234     case VariantSChess:
6235       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6236       gameInfo.holdingsSize = 7;
6237       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6238       break;
6239     case VariantJanus:
6240       pieces = JanusArray;
6241       gameInfo.boardWidth = 10;
6242       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6243       nrCastlingRights = 6;
6244         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6245         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6246         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6247         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6248         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6249         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6250       break;
6251     case VariantFalcon:
6252       pieces = FalconArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6255       break;
6256     case VariantXiangqi:
6257       pieces = XiangqiArray;
6258       gameInfo.boardWidth  = 9;
6259       gameInfo.boardHeight = 10;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6262       break;
6263     case VariantShogi:
6264       pieces = ShogiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 9;
6267       gameInfo.holdingsSize = 7;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6270       break;
6271     case VariantChu:
6272       pieces = ChuArray; pieceRows = 3;
6273       gameInfo.boardWidth  = 12;
6274       gameInfo.boardHeight = 12;
6275       nrCastlingRights = 0;
6276       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6277                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6278       break;
6279     case VariantCourier:
6280       pieces = CourierArray;
6281       gameInfo.boardWidth  = 12;
6282       nrCastlingRights = 0;
6283       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6284       break;
6285     case VariantKnightmate:
6286       pieces = KnightmateArray;
6287       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6288       break;
6289     case VariantSpartan:
6290       pieces = SpartanArray;
6291       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6292       break;
6293     case VariantLion:
6294       pieces = lionArray;
6295       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6296       break;
6297     case VariantChuChess:
6298       pieces = ChuChessArray;
6299       gameInfo.boardWidth = 10;
6300       gameInfo.boardHeight = 10;
6301       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6302       break;
6303     case VariantFairy:
6304       pieces = fairyArray;
6305       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6306       break;
6307     case VariantGreat:
6308       pieces = GreatArray;
6309       gameInfo.boardWidth = 10;
6310       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6311       gameInfo.holdingsSize = 8;
6312       break;
6313     case VariantSuper:
6314       pieces = FIDEArray;
6315       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6316       gameInfo.holdingsSize = 8;
6317       startedFromSetupPosition = TRUE;
6318       break;
6319     case VariantCrazyhouse:
6320     case VariantBughouse:
6321       pieces = FIDEArray;
6322       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6323       gameInfo.holdingsSize = 5;
6324       break;
6325     case VariantWildCastle:
6326       pieces = FIDEArray;
6327       /* !!?shuffle with kings guaranteed to be on d or e file */
6328       shuffleOpenings = 1;
6329       break;
6330     case VariantNoCastle:
6331       pieces = FIDEArray;
6332       nrCastlingRights = 0;
6333       /* !!?unconstrained back-rank shuffle */
6334       shuffleOpenings = 1;
6335       break;
6336     }
6337
6338     overrule = 0;
6339     if(appData.NrFiles >= 0) {
6340         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6341         gameInfo.boardWidth = appData.NrFiles;
6342     }
6343     if(appData.NrRanks >= 0) {
6344         gameInfo.boardHeight = appData.NrRanks;
6345     }
6346     if(appData.holdingsSize >= 0) {
6347         i = appData.holdingsSize;
6348         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6349         gameInfo.holdingsSize = i;
6350     }
6351     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6352     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6353         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6354
6355     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6356     if(pawnRow < 1) pawnRow = 1;
6357     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6358        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6359     if(gameInfo.variant == VariantChu) pawnRow = 3;
6360
6361     /* User pieceToChar list overrules defaults */
6362     if(appData.pieceToCharTable != NULL)
6363         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6364
6365     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6366
6367         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6368             s = (ChessSquare) 0; /* account holding counts in guard band */
6369         for( i=0; i<BOARD_HEIGHT; i++ )
6370             initialPosition[i][j] = s;
6371
6372         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6373         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6374         initialPosition[pawnRow][j] = WhitePawn;
6375         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6376         if(gameInfo.variant == VariantXiangqi) {
6377             if(j&1) {
6378                 initialPosition[pawnRow][j] =
6379                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6380                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6381                    initialPosition[2][j] = WhiteCannon;
6382                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6383                 }
6384             }
6385         }
6386         if(gameInfo.variant == VariantChu) {
6387              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6388                initialPosition[pawnRow+1][j] = WhiteCobra,
6389                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6390              for(i=1; i<pieceRows; i++) {
6391                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6392                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6393              }
6394         }
6395         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6396             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6397                initialPosition[0][j] = WhiteRook;
6398                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6399             }
6400         }
6401         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6402     }
6403     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6404     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6405
6406             j=BOARD_LEFT+1;
6407             initialPosition[1][j] = WhiteBishop;
6408             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6409             j=BOARD_RGHT-2;
6410             initialPosition[1][j] = WhiteRook;
6411             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6412     }
6413
6414     if( nrCastlingRights == -1) {
6415         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6416         /*       This sets default castling rights from none to normal corners   */
6417         /* Variants with other castling rights must set them themselves above    */
6418         nrCastlingRights = 6;
6419
6420         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6421         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6422         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6423         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6424         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6425         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6426      }
6427
6428      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6429      if(gameInfo.variant == VariantGreat) { // promotion commoners
6430         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6431         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6432         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6433         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6434      }
6435      if( gameInfo.variant == VariantSChess ) {
6436       initialPosition[1][0] = BlackMarshall;
6437       initialPosition[2][0] = BlackAngel;
6438       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6439       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6440       initialPosition[1][1] = initialPosition[2][1] =
6441       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6442      }
6443   if (appData.debugMode) {
6444     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6445   }
6446     if(shuffleOpenings) {
6447         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6448         startedFromSetupPosition = TRUE;
6449     }
6450     if(startedFromPositionFile) {
6451       /* [HGM] loadPos: use PositionFile for every new game */
6452       CopyBoard(initialPosition, filePosition);
6453       for(i=0; i<nrCastlingRights; i++)
6454           initialRights[i] = filePosition[CASTLING][i];
6455       startedFromSetupPosition = TRUE;
6456     }
6457
6458     CopyBoard(boards[0], initialPosition);
6459
6460     if(oldx != gameInfo.boardWidth ||
6461        oldy != gameInfo.boardHeight ||
6462        oldv != gameInfo.variant ||
6463        oldh != gameInfo.holdingsWidth
6464                                          )
6465             InitDrawingSizes(-2 ,0);
6466
6467     oldv = gameInfo.variant;
6468     if (redraw)
6469       DrawPosition(TRUE, boards[currentMove]);
6470 }
6471
6472 void
6473 SendBoard (ChessProgramState *cps, int moveNum)
6474 {
6475     char message[MSG_SIZ];
6476
6477     if (cps->useSetboard) {
6478       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6479       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6480       SendToProgram(message, cps);
6481       free(fen);
6482
6483     } else {
6484       ChessSquare *bp;
6485       int i, j, left=0, right=BOARD_WIDTH;
6486       /* Kludge to set black to move, avoiding the troublesome and now
6487        * deprecated "black" command.
6488        */
6489       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6490         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6491
6492       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6493
6494       SendToProgram("edit\n", cps);
6495       SendToProgram("#\n", cps);
6496       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6497         bp = &boards[moveNum][i][left];
6498         for (j = left; j < right; j++, bp++) {
6499           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6500           if ((int) *bp < (int) BlackPawn) {
6501             if(j == BOARD_RGHT+1)
6502                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6503             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6504             if(message[0] == '+' || message[0] == '~') {
6505               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6506                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6507                         AAA + j, ONE + i - '0');
6508             }
6509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6510                 message[1] = BOARD_RGHT   - 1 - j + '1';
6511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6512             }
6513             SendToProgram(message, cps);
6514           }
6515         }
6516       }
6517
6518       SendToProgram("c\n", cps);
6519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6520         bp = &boards[moveNum][i][left];
6521         for (j = left; j < right; j++, bp++) {
6522           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6523           if (((int) *bp != (int) EmptySquare)
6524               && ((int) *bp >= (int) BlackPawn)) {
6525             if(j == BOARD_LEFT-2)
6526                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6527             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6528                     AAA + j, ONE + i - '0');
6529             if(message[0] == '+' || message[0] == '~') {
6530               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6531                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6532                         AAA + j, ONE + i - '0');
6533             }
6534             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6535                 message[1] = BOARD_RGHT   - 1 - j + '1';
6536                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6537             }
6538             SendToProgram(message, cps);
6539           }
6540         }
6541       }
6542
6543       SendToProgram(".\n", cps);
6544     }
6545     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6546 }
6547
6548 char exclusionHeader[MSG_SIZ];
6549 int exCnt, excludePtr;
6550 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6551 static Exclusion excluTab[200];
6552 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6553
6554 static void
6555 WriteMap (int s)
6556 {
6557     int j;
6558     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6559     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6560 }
6561
6562 static void
6563 ClearMap ()
6564 {
6565     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6566     excludePtr = 24; exCnt = 0;
6567     WriteMap(0);
6568 }
6569
6570 static void
6571 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6572 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6573     char buf[2*MOVE_LEN], *p;
6574     Exclusion *e = excluTab;
6575     int i;
6576     for(i=0; i<exCnt; i++)
6577         if(e[i].ff == fromX && e[i].fr == fromY &&
6578            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6579     if(i == exCnt) { // was not in exclude list; add it
6580         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6581         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6582             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6583             return; // abort
6584         }
6585         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6586         excludePtr++; e[i].mark = excludePtr++;
6587         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6588         exCnt++;
6589     }
6590     exclusionHeader[e[i].mark] = state;
6591 }
6592
6593 static int
6594 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6595 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6596     char buf[MSG_SIZ];
6597     int j, k;
6598     ChessMove moveType;
6599     if((signed char)promoChar == -1) { // kludge to indicate best move
6600         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6601             return 1; // if unparsable, abort
6602     }
6603     // update exclusion map (resolving toggle by consulting existing state)
6604     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6605     j = k%8; k >>= 3;
6606     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6607     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6608          excludeMap[k] |=   1<<j;
6609     else excludeMap[k] &= ~(1<<j);
6610     // update header
6611     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6612     // inform engine
6613     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6614     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6615     SendToBoth(buf);
6616     return (state == '+');
6617 }
6618
6619 static void
6620 ExcludeClick (int index)
6621 {
6622     int i, j;
6623     Exclusion *e = excluTab;
6624     if(index < 25) { // none, best or tail clicked
6625         if(index < 13) { // none: include all
6626             WriteMap(0); // clear map
6627             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6628             SendToBoth("include all\n"); // and inform engine
6629         } else if(index > 18) { // tail
6630             if(exclusionHeader[19] == '-') { // tail was excluded
6631                 SendToBoth("include all\n");
6632                 WriteMap(0); // clear map completely
6633                 // now re-exclude selected moves
6634                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6635                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6636             } else { // tail was included or in mixed state
6637                 SendToBoth("exclude all\n");
6638                 WriteMap(0xFF); // fill map completely
6639                 // now re-include selected moves
6640                 j = 0; // count them
6641                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6642                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6643                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6644             }
6645         } else { // best
6646             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6647         }
6648     } else {
6649         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6650             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6651             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6652             break;
6653         }
6654     }
6655 }
6656
6657 ChessSquare
6658 DefaultPromoChoice (int white)
6659 {
6660     ChessSquare result;
6661     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6662        gameInfo.variant == VariantMakruk)
6663         result = WhiteFerz; // no choice
6664     else if(gameInfo.variant == VariantASEAN)
6665         result = WhiteRook; // no choice
6666     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6667         result= WhiteKing; // in Suicide Q is the last thing we want
6668     else if(gameInfo.variant == VariantSpartan)
6669         result = white ? WhiteQueen : WhiteAngel;
6670     else result = WhiteQueen;
6671     if(!white) result = WHITE_TO_BLACK result;
6672     return result;
6673 }
6674
6675 static int autoQueen; // [HGM] oneclick
6676
6677 int
6678 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6679 {
6680     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6681     /* [HGM] add Shogi promotions */
6682     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6683     ChessSquare piece, partner;
6684     ChessMove moveType;
6685     Boolean premove;
6686
6687     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6688     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6689
6690     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6691       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6692         return FALSE;
6693
6694     piece = boards[currentMove][fromY][fromX];
6695     if(gameInfo.variant == VariantChu) {
6696         promotionZoneSize = BOARD_HEIGHT/3;
6697         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6698     } else if(gameInfo.variant == VariantShogi) {
6699         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6700         highestPromotingPiece = (int)WhiteAlfil;
6701     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6702         promotionZoneSize = 3;
6703     }
6704
6705     // Treat Lance as Pawn when it is not representing Amazon or Lance
6706     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6707         if(piece == WhiteLance) piece = WhitePawn; else
6708         if(piece == BlackLance) piece = BlackPawn;
6709     }
6710
6711     // next weed out all moves that do not touch the promotion zone at all
6712     if((int)piece >= BlackPawn) {
6713         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6714              return FALSE;
6715         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6716         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6717     } else {
6718         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6719            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6720         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6721              return FALSE;
6722     }
6723
6724     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6725
6726     // weed out mandatory Shogi promotions
6727     if(gameInfo.variant == VariantShogi) {
6728         if(piece >= BlackPawn) {
6729             if(toY == 0 && piece == BlackPawn ||
6730                toY == 0 && piece == BlackQueen ||
6731                toY <= 1 && piece == BlackKnight) {
6732                 *promoChoice = '+';
6733                 return FALSE;
6734             }
6735         } else {
6736             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6737                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6738                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         }
6743     }
6744
6745     // weed out obviously illegal Pawn moves
6746     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6747         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6748         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6749         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6750         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6751         // note we are not allowed to test for valid (non-)capture, due to premove
6752     }
6753
6754     // we either have a choice what to promote to, or (in Shogi) whether to promote
6755     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6756        gameInfo.variant == VariantMakruk) {
6757         ChessSquare p=BlackFerz;  // no choice
6758         while(p < EmptySquare) {  //but make sure we use piece that exists
6759             *promoChoice = PieceToChar(p++);
6760             if(*promoChoice != '.') break;
6761         }
6762         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6763     }
6764     // no sense asking what we must promote to if it is going to explode...
6765     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6766         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6767         return FALSE;
6768     }
6769     // give caller the default choice even if we will not make it
6770     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6771     partner = piece; // pieces can promote if the pieceToCharTable says so
6772     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6773     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6774     if(        sweepSelect && gameInfo.variant != VariantGreat
6775                            && gameInfo.variant != VariantGrand
6776                            && gameInfo.variant != VariantSuper) return FALSE;
6777     if(autoQueen) return FALSE; // predetermined
6778
6779     // suppress promotion popup on illegal moves that are not premoves
6780     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6781               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6782     if(appData.testLegality && !premove) {
6783         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6784                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6785         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6786         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6787             return FALSE;
6788     }
6789
6790     return TRUE;
6791 }
6792
6793 int
6794 InPalace (int row, int column)
6795 {   /* [HGM] for Xiangqi */
6796     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6797          column < (BOARD_WIDTH + 4)/2 &&
6798          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6799     return FALSE;
6800 }
6801
6802 int
6803 PieceForSquare (int x, int y)
6804 {
6805   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6806      return -1;
6807   else
6808      return boards[currentMove][y][x];
6809 }
6810
6811 int
6812 OKToStartUserMove (int x, int y)
6813 {
6814     ChessSquare from_piece;
6815     int white_piece;
6816
6817     if (matchMode) return FALSE;
6818     if (gameMode == EditPosition) return TRUE;
6819
6820     if (x >= 0 && y >= 0)
6821       from_piece = boards[currentMove][y][x];
6822     else
6823       from_piece = EmptySquare;
6824
6825     if (from_piece == EmptySquare) return FALSE;
6826
6827     white_piece = (int)from_piece >= (int)WhitePawn &&
6828       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6829
6830     switch (gameMode) {
6831       case AnalyzeFile:
6832       case TwoMachinesPlay:
6833       case EndOfGame:
6834         return FALSE;
6835
6836       case IcsObserving:
6837       case IcsIdle:
6838         return FALSE;
6839
6840       case MachinePlaysWhite:
6841       case IcsPlayingBlack:
6842         if (appData.zippyPlay) return FALSE;
6843         if (white_piece) {
6844             DisplayMoveError(_("You are playing Black"));
6845             return FALSE;
6846         }
6847         break;
6848
6849       case MachinePlaysBlack:
6850       case IcsPlayingWhite:
6851         if (appData.zippyPlay) return FALSE;
6852         if (!white_piece) {
6853             DisplayMoveError(_("You are playing White"));
6854             return FALSE;
6855         }
6856         break;
6857
6858       case PlayFromGameFile:
6859             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6860       case EditGame:
6861       case AnalyzeMode:
6862         if (!white_piece && WhiteOnMove(currentMove)) {
6863             DisplayMoveError(_("It is White's turn"));
6864             return FALSE;
6865         }
6866         if (white_piece && !WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return FALSE;
6869         }
6870         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6871             /* Editing correspondence game history */
6872             /* Could disallow this or prompt for confirmation */
6873             cmailOldMove = -1;
6874         }
6875         break;
6876
6877       case BeginningOfGame:
6878         if (appData.icsActive) return FALSE;
6879         if (!appData.noChessProgram) {
6880             if (!white_piece) {
6881                 DisplayMoveError(_("You are playing White"));
6882                 return FALSE;
6883             }
6884         }
6885         break;
6886
6887       case Training:
6888         if (!white_piece && WhiteOnMove(currentMove)) {
6889             DisplayMoveError(_("It is White's turn"));
6890             return FALSE;
6891         }
6892         if (white_piece && !WhiteOnMove(currentMove)) {
6893             DisplayMoveError(_("It is Black's turn"));
6894             return FALSE;
6895         }
6896         break;
6897
6898       default:
6899       case IcsExamining:
6900         break;
6901     }
6902     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6903         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6904         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6905         && gameMode != AnalyzeFile && gameMode != Training) {
6906         DisplayMoveError(_("Displayed position is not current"));
6907         return FALSE;
6908     }
6909     return TRUE;
6910 }
6911
6912 Boolean
6913 OnlyMove (int *x, int *y, Boolean captures)
6914 {
6915     DisambiguateClosure cl;
6916     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6917     switch(gameMode) {
6918       case MachinePlaysBlack:
6919       case IcsPlayingWhite:
6920       case BeginningOfGame:
6921         if(!WhiteOnMove(currentMove)) return FALSE;
6922         break;
6923       case MachinePlaysWhite:
6924       case IcsPlayingBlack:
6925         if(WhiteOnMove(currentMove)) return FALSE;
6926         break;
6927       case EditGame:
6928         break;
6929       default:
6930         return FALSE;
6931     }
6932     cl.pieceIn = EmptySquare;
6933     cl.rfIn = *y;
6934     cl.ffIn = *x;
6935     cl.rtIn = -1;
6936     cl.ftIn = -1;
6937     cl.promoCharIn = NULLCHAR;
6938     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6939     if( cl.kind == NormalMove ||
6940         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6941         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6942         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6943       fromX = cl.ff;
6944       fromY = cl.rf;
6945       *x = cl.ft;
6946       *y = cl.rt;
6947       return TRUE;
6948     }
6949     if(cl.kind != ImpossibleMove) return FALSE;
6950     cl.pieceIn = EmptySquare;
6951     cl.rfIn = -1;
6952     cl.ffIn = -1;
6953     cl.rtIn = *y;
6954     cl.ftIn = *x;
6955     cl.promoCharIn = NULLCHAR;
6956     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6957     if( cl.kind == NormalMove ||
6958         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6959         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6960         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6961       fromX = cl.ff;
6962       fromY = cl.rf;
6963       *x = cl.ft;
6964       *y = cl.rt;
6965       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6966       return TRUE;
6967     }
6968     return FALSE;
6969 }
6970
6971 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6972 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6973 int lastLoadGameUseList = FALSE;
6974 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6975 ChessMove lastLoadGameStart = EndOfFile;
6976 int doubleClick;
6977 Boolean addToBookFlag;
6978
6979 void
6980 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6981 {
6982     ChessMove moveType;
6983     ChessSquare pup;
6984     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6985
6986     /* Check if the user is playing in turn.  This is complicated because we
6987        let the user "pick up" a piece before it is his turn.  So the piece he
6988        tried to pick up may have been captured by the time he puts it down!
6989        Therefore we use the color the user is supposed to be playing in this
6990        test, not the color of the piece that is currently on the starting
6991        square---except in EditGame mode, where the user is playing both
6992        sides; fortunately there the capture race can't happen.  (It can
6993        now happen in IcsExamining mode, but that's just too bad.  The user
6994        will get a somewhat confusing message in that case.)
6995        */
6996
6997     switch (gameMode) {
6998       case AnalyzeFile:
6999       case TwoMachinesPlay:
7000       case EndOfGame:
7001       case IcsObserving:
7002       case IcsIdle:
7003         /* We switched into a game mode where moves are not accepted,
7004            perhaps while the mouse button was down. */
7005         return;
7006
7007       case MachinePlaysWhite:
7008         /* User is moving for Black */
7009         if (WhiteOnMove(currentMove)) {
7010             DisplayMoveError(_("It is White's turn"));
7011             return;
7012         }
7013         break;
7014
7015       case MachinePlaysBlack:
7016         /* User is moving for White */
7017         if (!WhiteOnMove(currentMove)) {
7018             DisplayMoveError(_("It is Black's turn"));
7019             return;
7020         }
7021         break;
7022
7023       case PlayFromGameFile:
7024             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7025       case EditGame:
7026       case IcsExamining:
7027       case BeginningOfGame:
7028       case AnalyzeMode:
7029       case Training:
7030         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7031         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7032             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7033             /* User is moving for Black */
7034             if (WhiteOnMove(currentMove)) {
7035                 DisplayMoveError(_("It is White's turn"));
7036                 return;
7037             }
7038         } else {
7039             /* User is moving for White */
7040             if (!WhiteOnMove(currentMove)) {
7041                 DisplayMoveError(_("It is Black's turn"));
7042                 return;
7043             }
7044         }
7045         break;
7046
7047       case IcsPlayingBlack:
7048         /* User is moving for Black */
7049         if (WhiteOnMove(currentMove)) {
7050             if (!appData.premove) {
7051                 DisplayMoveError(_("It is White's turn"));
7052             } else if (toX >= 0 && toY >= 0) {
7053                 premoveToX = toX;
7054                 premoveToY = toY;
7055                 premoveFromX = fromX;
7056                 premoveFromY = fromY;
7057                 premovePromoChar = promoChar;
7058                 gotPremove = 1;
7059                 if (appData.debugMode)
7060                     fprintf(debugFP, "Got premove: fromX %d,"
7061                             "fromY %d, toX %d, toY %d\n",
7062                             fromX, fromY, toX, toY);
7063             }
7064             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7065             return;
7066         }
7067         break;
7068
7069       case IcsPlayingWhite:
7070         /* User is moving for White */
7071         if (!WhiteOnMove(currentMove)) {
7072             if (!appData.premove) {
7073                 DisplayMoveError(_("It is Black's turn"));
7074             } else if (toX >= 0 && toY >= 0) {
7075                 premoveToX = toX;
7076                 premoveToY = toY;
7077                 premoveFromX = fromX;
7078                 premoveFromY = fromY;
7079                 premovePromoChar = promoChar;
7080                 gotPremove = 1;
7081                 if (appData.debugMode)
7082                     fprintf(debugFP, "Got premove: fromX %d,"
7083                             "fromY %d, toX %d, toY %d\n",
7084                             fromX, fromY, toX, toY);
7085             }
7086             DrawPosition(TRUE, boards[currentMove]);
7087             return;
7088         }
7089         break;
7090
7091       default:
7092         break;
7093
7094       case EditPosition:
7095         /* EditPosition, empty square, or different color piece;
7096            click-click move is possible */
7097         if (toX == -2 || toY == -2) {
7098             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7099             DrawPosition(FALSE, boards[currentMove]);
7100             return;
7101         } else if (toX >= 0 && toY >= 0) {
7102             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7103                 ChessSquare p = boards[0][rf][ff];
7104                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7105                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7106             }
7107             boards[0][toY][toX] = boards[0][fromY][fromX];
7108             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7109                 if(boards[0][fromY][0] != EmptySquare) {
7110                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7111                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7112                 }
7113             } else
7114             if(fromX == BOARD_RGHT+1) {
7115                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7116                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7117                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7118                 }
7119             } else
7120             boards[0][fromY][fromX] = gatingPiece;
7121             ClearHighlights();
7122             DrawPosition(FALSE, boards[currentMove]);
7123             return;
7124         }
7125         return;
7126     }
7127
7128     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7129     pup = boards[currentMove][toY][toX];
7130
7131     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7132     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7133          if( pup != EmptySquare ) return;
7134          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7135            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7136                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7137            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7138            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7139            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7140            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7141          fromY = DROP_RANK;
7142     }
7143
7144     /* [HGM] always test for legality, to get promotion info */
7145     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7146                                          fromY, fromX, toY, toX, promoChar);
7147
7148     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7149
7150     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7151
7152     /* [HGM] but possibly ignore an IllegalMove result */
7153     if (appData.testLegality) {
7154         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7155             DisplayMoveError(_("Illegal move"));
7156             return;
7157         }
7158     }
7159
7160     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7161         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7162              ClearPremoveHighlights(); // was included
7163         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7164         return;
7165     }
7166
7167     if(addToBookFlag) { // adding moves to book
7168         char buf[MSG_SIZ], move[MSG_SIZ];
7169         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7170         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7171                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7172         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7173         AddBookMove(buf);
7174         addToBookFlag = FALSE;
7175         ClearHighlights();
7176         return;
7177     }
7178
7179     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7180 }
7181
7182 /* Common tail of UserMoveEvent and DropMenuEvent */
7183 int
7184 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7185 {
7186     char *bookHit = 0;
7187
7188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7189         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7191         if(WhiteOnMove(currentMove)) {
7192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7193         } else {
7194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7195         }
7196     }
7197
7198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7199        move type in caller when we know the move is a legal promotion */
7200     if(moveType == NormalMove && promoChar)
7201         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7202
7203     /* [HGM] <popupFix> The following if has been moved here from
7204        UserMoveEvent(). Because it seemed to belong here (why not allow
7205        piece drops in training games?), and because it can only be
7206        performed after it is known to what we promote. */
7207     if (gameMode == Training) {
7208       /* compare the move played on the board to the next move in the
7209        * game. If they match, display the move and the opponent's response.
7210        * If they don't match, display an error message.
7211        */
7212       int saveAnimate;
7213       Board testBoard;
7214       CopyBoard(testBoard, boards[currentMove]);
7215       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7216
7217       if (CompareBoards(testBoard, boards[currentMove+1])) {
7218         ForwardInner(currentMove+1);
7219
7220         /* Autoplay the opponent's response.
7221          * if appData.animate was TRUE when Training mode was entered,
7222          * the response will be animated.
7223          */
7224         saveAnimate = appData.animate;
7225         appData.animate = animateTraining;
7226         ForwardInner(currentMove+1);
7227         appData.animate = saveAnimate;
7228
7229         /* check for the end of the game */
7230         if (currentMove >= forwardMostMove) {
7231           gameMode = PlayFromGameFile;
7232           ModeHighlight();
7233           SetTrainingModeOff();
7234           DisplayInformation(_("End of game"));
7235         }
7236       } else {
7237         DisplayError(_("Incorrect move"), 0);
7238       }
7239       return 1;
7240     }
7241
7242   /* Ok, now we know that the move is good, so we can kill
7243      the previous line in Analysis Mode */
7244   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7245                                 && currentMove < forwardMostMove) {
7246     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7247     else forwardMostMove = currentMove;
7248   }
7249
7250   ClearMap();
7251
7252   /* If we need the chess program but it's dead, restart it */
7253   ResurrectChessProgram();
7254
7255   /* A user move restarts a paused game*/
7256   if (pausing)
7257     PauseEvent();
7258
7259   thinkOutput[0] = NULLCHAR;
7260
7261   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7262
7263   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7264     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7265     return 1;
7266   }
7267
7268   if (gameMode == BeginningOfGame) {
7269     if (appData.noChessProgram) {
7270       gameMode = EditGame;
7271       SetGameInfo();
7272     } else {
7273       char buf[MSG_SIZ];
7274       gameMode = MachinePlaysBlack;
7275       StartClocks();
7276       SetGameInfo();
7277       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7278       DisplayTitle(buf);
7279       if (first.sendName) {
7280         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7281         SendToProgram(buf, &first);
7282       }
7283       StartClocks();
7284     }
7285     ModeHighlight();
7286   }
7287
7288   /* Relay move to ICS or chess engine */
7289   if (appData.icsActive) {
7290     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7291         gameMode == IcsExamining) {
7292       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7293         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7294         SendToICS("draw ");
7295         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7296       }
7297       // also send plain move, in case ICS does not understand atomic claims
7298       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7299       ics_user_moved = 1;
7300     }
7301   } else {
7302     if (first.sendTime && (gameMode == BeginningOfGame ||
7303                            gameMode == MachinePlaysWhite ||
7304                            gameMode == MachinePlaysBlack)) {
7305       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7306     }
7307     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7308          // [HGM] book: if program might be playing, let it use book
7309         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7310         first.maybeThinking = TRUE;
7311     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7312         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7313         SendBoard(&first, currentMove+1);
7314         if(second.analyzing) {
7315             if(!second.useSetboard) SendToProgram("undo\n", &second);
7316             SendBoard(&second, currentMove+1);
7317         }
7318     } else {
7319         SendMoveToProgram(forwardMostMove-1, &first);
7320         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7321     }
7322     if (currentMove == cmailOldMove + 1) {
7323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7324     }
7325   }
7326
7327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7328
7329   switch (gameMode) {
7330   case EditGame:
7331     if(appData.testLegality)
7332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7333     case MT_NONE:
7334     case MT_CHECK:
7335       break;
7336     case MT_CHECKMATE:
7337     case MT_STAINMATE:
7338       if (WhiteOnMove(currentMove)) {
7339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7340       } else {
7341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7342       }
7343       break;
7344     case MT_STALEMATE:
7345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7346       break;
7347     }
7348     break;
7349
7350   case MachinePlaysBlack:
7351   case MachinePlaysWhite:
7352     /* disable certain menu options while machine is thinking */
7353     SetMachineThinkingEnables();
7354     break;
7355
7356   default:
7357     break;
7358   }
7359
7360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7362
7363   if(bookHit) { // [HGM] book: simulate book reply
7364         static char bookMove[MSG_SIZ]; // a bit generous?
7365
7366         programStats.nodes = programStats.depth = programStats.time =
7367         programStats.score = programStats.got_only_move = 0;
7368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7369
7370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7371         strcat(bookMove, bookHit);
7372         HandleMachineMove(bookMove, &first);
7373   }
7374   return 1;
7375 }
7376
7377 void
7378 MarkByFEN(char *fen)
7379 {
7380         int r, f;
7381         if(!appData.markers || !appData.highlightDragging) return;
7382         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7383         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7384         while(*fen) {
7385             int s = 0;
7386             marker[r][f] = 0;
7387             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7388             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7389             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7390             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7391             if(*fen == 'T') marker[r][f++] = 0; else
7392             if(*fen == 'Y') marker[r][f++] = 1; else
7393             if(*fen == 'G') marker[r][f++] = 3; else
7394             if(*fen == 'B') marker[r][f++] = 4; else
7395             if(*fen == 'C') marker[r][f++] = 5; else
7396             if(*fen == 'M') marker[r][f++] = 6; else
7397             if(*fen == 'W') marker[r][f++] = 7; else
7398             if(*fen == 'D') marker[r][f++] = 8; else
7399             if(*fen == 'R') marker[r][f++] = 2; else {
7400                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7401               f += s; fen -= s>0;
7402             }
7403             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7404             if(r < 0) break;
7405             fen++;
7406         }
7407         DrawPosition(TRUE, NULL);
7408 }
7409
7410 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7411
7412 void
7413 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7414 {
7415     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7416     Markers *m = (Markers *) closure;
7417     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7418                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7419         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7420                          || kind == WhiteCapturesEnPassant
7421                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7422     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7423 }
7424
7425 static int hoverSavedValid;
7426
7427 void
7428 MarkTargetSquares (int clear)
7429 {
7430   int x, y, sum=0;
7431   if(clear) { // no reason to ever suppress clearing
7432     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7433     hoverSavedValid = 0;
7434     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7435   } else {
7436     int capt = 0;
7437     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7438        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7439     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7440     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7441       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7442       if(capt)
7443       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7444     }
7445   }
7446   DrawPosition(FALSE, NULL);
7447 }
7448
7449 int
7450 Explode (Board board, int fromX, int fromY, int toX, int toY)
7451 {
7452     if(gameInfo.variant == VariantAtomic &&
7453        (board[toY][toX] != EmptySquare ||                     // capture?
7454         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7455                          board[fromY][fromX] == BlackPawn   )
7456       )) {
7457         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7458         return TRUE;
7459     }
7460     return FALSE;
7461 }
7462
7463 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7464
7465 int
7466 CanPromote (ChessSquare piece, int y)
7467 {
7468         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7469         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7470         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7471         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7472            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7473           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7474            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7475         return (piece == BlackPawn && y <= zone ||
7476                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7477                 piece == BlackLance && y <= zone ||
7478                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7479 }
7480
7481 void
7482 HoverEvent (int xPix, int yPix, int x, int y)
7483 {
7484         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7485         int r, f;
7486         if(!first.highlight) return;
7487         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7488         if(x == oldX && y == oldY) return; // only do something if we enter new square
7489         oldFromX = fromX; oldFromY = fromY;
7490         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7491           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7492             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7493           hoverSavedValid = 1;
7494         } else if(oldX != x || oldY != y) {
7495           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7496           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7497           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7498             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7499           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7500             char buf[MSG_SIZ];
7501             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7502             SendToProgram(buf, &first);
7503           }
7504           oldX = x; oldY = y;
7505 //        SetHighlights(fromX, fromY, x, y);
7506         }
7507 }
7508
7509 void ReportClick(char *action, int x, int y)
7510 {
7511         char buf[MSG_SIZ]; // Inform engine of what user does
7512         int r, f;
7513         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7516         if(!first.highlight || gameMode == EditPosition) return;
7517         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7518         SendToProgram(buf, &first);
7519 }
7520
7521 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7522
7523 void
7524 LeftClick (ClickType clickType, int xPix, int yPix)
7525 {
7526     int x, y;
7527     Boolean saveAnimate;
7528     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7529     char promoChoice = NULLCHAR;
7530     ChessSquare piece;
7531     static TimeMark lastClickTime, prevClickTime;
7532
7533     x = EventToSquare(xPix, BOARD_WIDTH);
7534     y = EventToSquare(yPix, BOARD_HEIGHT);
7535     if (!flipView && y >= 0) {
7536         y = BOARD_HEIGHT - 1 - y;
7537     }
7538     if (flipView && x >= 0) {
7539         x = BOARD_WIDTH - 1 - x;
7540     }
7541
7542     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7543         static int dummy;
7544         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7545         right = TRUE;
7546         return;
7547     }
7548
7549     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7550
7551     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7552
7553     if (clickType == Press) ErrorPopDown();
7554     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7555
7556     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7557         defaultPromoChoice = promoSweep;
7558         promoSweep = EmptySquare;   // terminate sweep
7559         promoDefaultAltered = TRUE;
7560         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7561     }
7562
7563     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7564         if(clickType == Release) return; // ignore upclick of click-click destination
7565         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7566         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7567         if(gameInfo.holdingsWidth &&
7568                 (WhiteOnMove(currentMove)
7569                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7570                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7571             // click in right holdings, for determining promotion piece
7572             ChessSquare p = boards[currentMove][y][x];
7573             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7574             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7575             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7576                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7577                 fromX = fromY = -1;
7578                 return;
7579             }
7580         }
7581         DrawPosition(FALSE, boards[currentMove]);
7582         return;
7583     }
7584
7585     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7586     if(clickType == Press
7587             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7588               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7589               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7590         return;
7591
7592     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7593         // could be static click on premove from-square: abort premove
7594         gotPremove = 0;
7595         ClearPremoveHighlights();
7596     }
7597
7598     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7599         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7600
7601     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7602         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7603                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7604         defaultPromoChoice = DefaultPromoChoice(side);
7605     }
7606
7607     autoQueen = appData.alwaysPromoteToQueen;
7608
7609     if (fromX == -1) {
7610       int originalY = y;
7611       gatingPiece = EmptySquare;
7612       if (clickType != Press) {
7613         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7614             DragPieceEnd(xPix, yPix); dragging = 0;
7615             DrawPosition(FALSE, NULL);
7616         }
7617         return;
7618       }
7619       doubleClick = FALSE;
7620       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7621         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7622       }
7623       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7624       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7625          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7626          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7627             /* First square */
7628             if (OKToStartUserMove(fromX, fromY)) {
7629                 second = 0;
7630                 ReportClick("lift", x, y);
7631                 MarkTargetSquares(0);
7632                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7633                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7634                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7635                     promoSweep = defaultPromoChoice;
7636                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7637                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7638                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7639                 }
7640                 if (appData.highlightDragging) {
7641                     SetHighlights(fromX, fromY, -1, -1);
7642                 } else {
7643                     ClearHighlights();
7644                 }
7645             } else fromX = fromY = -1;
7646             return;
7647         }
7648     }
7649
7650     /* fromX != -1 */
7651     if (clickType == Press && gameMode != EditPosition) {
7652         ChessSquare fromP;
7653         ChessSquare toP;
7654         int frc;
7655
7656         // ignore off-board to clicks
7657         if(y < 0 || x < 0) return;
7658
7659         /* Check if clicking again on the same color piece */
7660         fromP = boards[currentMove][fromY][fromX];
7661         toP = boards[currentMove][y][x];
7662         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7663         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7664             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7665            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7666              WhitePawn <= toP && toP <= WhiteKing &&
7667              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7668              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7669             (BlackPawn <= fromP && fromP <= BlackKing &&
7670              BlackPawn <= toP && toP <= BlackKing &&
7671              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7672              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7673             /* Clicked again on same color piece -- changed his mind */
7674             second = (x == fromX && y == fromY);
7675             killX = killY = kill2X = kill2Y = -1;
7676             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7677                 second = FALSE; // first double-click rather than scond click
7678                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7679             }
7680             promoDefaultAltered = FALSE;
7681            if(!second) MarkTargetSquares(1);
7682            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7683             if (appData.highlightDragging) {
7684                 SetHighlights(x, y, -1, -1);
7685             } else {
7686                 ClearHighlights();
7687             }
7688             if (OKToStartUserMove(x, y)) {
7689                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7690                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7691                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7692                  gatingPiece = boards[currentMove][fromY][fromX];
7693                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7694                 fromX = x;
7695                 fromY = y; dragging = 1;
7696                 if(!second) ReportClick("lift", x, y);
7697                 MarkTargetSquares(0);
7698                 DragPieceBegin(xPix, yPix, FALSE);
7699                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7700                     promoSweep = defaultPromoChoice;
7701                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7702                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7703                 }
7704             }
7705            }
7706            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7707            second = FALSE;
7708         }
7709         // ignore clicks on holdings
7710         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7711     }
7712
7713     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7714         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7715         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7716         return;
7717     }
7718
7719     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7720         DragPieceEnd(xPix, yPix); dragging = 0;
7721         if(clearFlag) {
7722             // a deferred attempt to click-click move an empty square on top of a piece
7723             boards[currentMove][y][x] = EmptySquare;
7724             ClearHighlights();
7725             DrawPosition(FALSE, boards[currentMove]);
7726             fromX = fromY = -1; clearFlag = 0;
7727             return;
7728         }
7729         if (appData.animateDragging) {
7730             /* Undo animation damage if any */
7731             DrawPosition(FALSE, NULL);
7732         }
7733         if (second) {
7734             /* Second up/down in same square; just abort move */
7735             second = 0;
7736             fromX = fromY = -1;
7737             gatingPiece = EmptySquare;
7738             ClearHighlights();
7739             gotPremove = 0;
7740             ClearPremoveHighlights();
7741             MarkTargetSquares(-1);
7742             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7743         } else {
7744             /* First upclick in same square; start click-click mode */
7745             SetHighlights(x, y, -1, -1);
7746         }
7747         return;
7748     }
7749
7750     clearFlag = 0;
7751
7752     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7753        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7754         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7755         DisplayMessage(_("only marked squares are legal"),"");
7756         DrawPosition(TRUE, NULL);
7757         return; // ignore to-click
7758     }
7759
7760     /* we now have a different from- and (possibly off-board) to-square */
7761     /* Completed move */
7762     if(!sweepSelecting) {
7763         toX = x;
7764         toY = y;
7765     }
7766
7767     piece = boards[currentMove][fromY][fromX];
7768
7769     saveAnimate = appData.animate;
7770     if (clickType == Press) {
7771         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7772         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7773             // must be Edit Position mode with empty-square selected
7774             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7775             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7776             return;
7777         }
7778         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7779             return;
7780         }
7781         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7782             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7783         } else
7784         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7785         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7786           if(appData.sweepSelect) {
7787             promoSweep = defaultPromoChoice;
7788             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7789             selectFlag = 0; lastX = xPix; lastY = yPix;
7790             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7791             Sweep(0); // Pawn that is going to promote: preview promotion piece
7792             sweepSelecting = 1;
7793             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7794             MarkTargetSquares(1);
7795           }
7796           return; // promo popup appears on up-click
7797         }
7798         /* Finish clickclick move */
7799         if (appData.animate || appData.highlightLastMove) {
7800             SetHighlights(fromX, fromY, toX, toY);
7801         } else {
7802             ClearHighlights();
7803         }
7804         MarkTargetSquares(1);
7805     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7806         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7807         *promoRestrict = 0;
7808         if (appData.animate || appData.highlightLastMove) {
7809             SetHighlights(fromX, fromY, toX, toY);
7810         } else {
7811             ClearHighlights();
7812         }
7813         MarkTargetSquares(1);
7814     } else {
7815 #if 0
7816 // [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
7817         /* Finish drag move */
7818         if (appData.highlightLastMove) {
7819             SetHighlights(fromX, fromY, toX, toY);
7820         } else {
7821             ClearHighlights();
7822         }
7823 #endif
7824         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7825           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7826         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7827         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7828           dragging *= 2;            // flag button-less dragging if we are dragging
7829           MarkTargetSquares(1);
7830           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7831           else {
7832             kill2X = killX; kill2Y = killY;
7833             killX = x; killY = y;     // remember this square as intermediate
7834             ReportClick("put", x, y); // and inform engine
7835             ReportClick("lift", x, y);
7836             MarkTargetSquares(0);
7837             return;
7838           }
7839         }
7840         DragPieceEnd(xPix, yPix); dragging = 0;
7841         /* Don't animate move and drag both */
7842         appData.animate = FALSE;
7843         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7844     }
7845
7846     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7847     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7848         ChessSquare piece = boards[currentMove][fromY][fromX];
7849         if(gameMode == EditPosition && piece != EmptySquare &&
7850            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7851             int n;
7852
7853             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7854                 n = PieceToNumber(piece - (int)BlackPawn);
7855                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7856                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7857                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7858             } else
7859             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7860                 n = PieceToNumber(piece);
7861                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7862                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7863                 boards[currentMove][n][BOARD_WIDTH-2]++;
7864             }
7865             boards[currentMove][fromY][fromX] = EmptySquare;
7866         }
7867         ClearHighlights();
7868         fromX = fromY = -1;
7869         MarkTargetSquares(1);
7870         DrawPosition(TRUE, boards[currentMove]);
7871         return;
7872     }
7873
7874     // off-board moves should not be highlighted
7875     if(x < 0 || y < 0) ClearHighlights();
7876     else ReportClick("put", x, y);
7877
7878     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7879
7880     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7881
7882     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7883         SetHighlights(fromX, fromY, toX, toY);
7884         MarkTargetSquares(1);
7885         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7886             // [HGM] super: promotion to captured piece selected from holdings
7887             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7888             promotionChoice = TRUE;
7889             // kludge follows to temporarily execute move on display, without promoting yet
7890             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7891             boards[currentMove][toY][toX] = p;
7892             DrawPosition(FALSE, boards[currentMove]);
7893             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7894             boards[currentMove][toY][toX] = q;
7895             DisplayMessage("Click in holdings to choose piece", "");
7896             return;
7897         }
7898         PromotionPopUp(promoChoice);
7899     } else {
7900         int oldMove = currentMove;
7901         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7902         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7903         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7904         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7905            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7906             DrawPosition(TRUE, boards[currentMove]);
7907         fromX = fromY = -1;
7908     }
7909     appData.animate = saveAnimate;
7910     if (appData.animate || appData.animateDragging) {
7911         /* Undo animation damage if needed */
7912 //      DrawPosition(FALSE, NULL);
7913     }
7914 }
7915
7916 int
7917 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7918 {   // front-end-free part taken out of PieceMenuPopup
7919     int whichMenu; int xSqr, ySqr;
7920
7921     if(seekGraphUp) { // [HGM] seekgraph
7922         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7923         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7924         return -2;
7925     }
7926
7927     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7928          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7929         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7930         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7931         if(action == Press)   {
7932             originalFlip = flipView;
7933             flipView = !flipView; // temporarily flip board to see game from partners perspective
7934             DrawPosition(TRUE, partnerBoard);
7935             DisplayMessage(partnerStatus, "");
7936             partnerUp = TRUE;
7937         } else if(action == Release) {
7938             flipView = originalFlip;
7939             DrawPosition(TRUE, boards[currentMove]);
7940             partnerUp = FALSE;
7941         }
7942         return -2;
7943     }
7944
7945     xSqr = EventToSquare(x, BOARD_WIDTH);
7946     ySqr = EventToSquare(y, BOARD_HEIGHT);
7947     if (action == Release) {
7948         if(pieceSweep != EmptySquare) {
7949             EditPositionMenuEvent(pieceSweep, toX, toY);
7950             pieceSweep = EmptySquare;
7951         } else UnLoadPV(); // [HGM] pv
7952     }
7953     if (action != Press) return -2; // return code to be ignored
7954     switch (gameMode) {
7955       case IcsExamining:
7956         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7957       case EditPosition:
7958         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7959         if (xSqr < 0 || ySqr < 0) return -1;
7960         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7961         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7962         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7963         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7964         NextPiece(0);
7965         return 2; // grab
7966       case IcsObserving:
7967         if(!appData.icsEngineAnalyze) return -1;
7968       case IcsPlayingWhite:
7969       case IcsPlayingBlack:
7970         if(!appData.zippyPlay) goto noZip;
7971       case AnalyzeMode:
7972       case AnalyzeFile:
7973       case MachinePlaysWhite:
7974       case MachinePlaysBlack:
7975       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7976         if (!appData.dropMenu) {
7977           LoadPV(x, y);
7978           return 2; // flag front-end to grab mouse events
7979         }
7980         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7981            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7982       case EditGame:
7983       noZip:
7984         if (xSqr < 0 || ySqr < 0) return -1;
7985         if (!appData.dropMenu || appData.testLegality &&
7986             gameInfo.variant != VariantBughouse &&
7987             gameInfo.variant != VariantCrazyhouse) return -1;
7988         whichMenu = 1; // drop menu
7989         break;
7990       default:
7991         return -1;
7992     }
7993
7994     if (((*fromX = xSqr) < 0) ||
7995         ((*fromY = ySqr) < 0)) {
7996         *fromX = *fromY = -1;
7997         return -1;
7998     }
7999     if (flipView)
8000       *fromX = BOARD_WIDTH - 1 - *fromX;
8001     else
8002       *fromY = BOARD_HEIGHT - 1 - *fromY;
8003
8004     return whichMenu;
8005 }
8006
8007 void
8008 Wheel (int dir, int x, int y)
8009 {
8010     if(gameMode == EditPosition) {
8011         int xSqr = EventToSquare(x, BOARD_WIDTH);
8012         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8013         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8014         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8015         do {
8016             boards[currentMove][ySqr][xSqr] += dir;
8017             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8018             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8019         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8020         DrawPosition(FALSE, boards[currentMove]);
8021     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8022 }
8023
8024 void
8025 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8026 {
8027 //    char * hint = lastHint;
8028     FrontEndProgramStats stats;
8029
8030     stats.which = cps == &first ? 0 : 1;
8031     stats.depth = cpstats->depth;
8032     stats.nodes = cpstats->nodes;
8033     stats.score = cpstats->score;
8034     stats.time = cpstats->time;
8035     stats.pv = cpstats->movelist;
8036     stats.hint = lastHint;
8037     stats.an_move_index = 0;
8038     stats.an_move_count = 0;
8039
8040     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8041         stats.hint = cpstats->move_name;
8042         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8043         stats.an_move_count = cpstats->nr_moves;
8044     }
8045
8046     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
8047
8048     SetProgramStats( &stats );
8049 }
8050
8051 void
8052 ClearEngineOutputPane (int which)
8053 {
8054     static FrontEndProgramStats dummyStats;
8055     dummyStats.which = which;
8056     dummyStats.pv = "#";
8057     SetProgramStats( &dummyStats );
8058 }
8059
8060 #define MAXPLAYERS 500
8061
8062 char *
8063 TourneyStandings (int display)
8064 {
8065     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8066     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8067     char result, *p, *names[MAXPLAYERS];
8068
8069     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8070         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8071     names[0] = p = strdup(appData.participants);
8072     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8073
8074     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8075
8076     while(result = appData.results[nr]) {
8077         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8078         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8079         wScore = bScore = 0;
8080         switch(result) {
8081           case '+': wScore = 2; break;
8082           case '-': bScore = 2; break;
8083           case '=': wScore = bScore = 1; break;
8084           case ' ':
8085           case '*': return strdup("busy"); // tourney not finished
8086         }
8087         score[w] += wScore;
8088         score[b] += bScore;
8089         games[w]++;
8090         games[b]++;
8091         nr++;
8092     }
8093     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8094     for(w=0; w<nPlayers; w++) {
8095         bScore = -1;
8096         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8097         ranking[w] = b; points[w] = bScore; score[b] = -2;
8098     }
8099     p = malloc(nPlayers*34+1);
8100     for(w=0; w<nPlayers && w<display; w++)
8101         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8102     free(names[0]);
8103     return p;
8104 }
8105
8106 void
8107 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8108 {       // count all piece types
8109         int p, f, r;
8110         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8111         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8112         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8113                 p = board[r][f];
8114                 pCnt[p]++;
8115                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8116                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8117                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8118                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8119                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8120                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8121         }
8122 }
8123
8124 int
8125 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8126 {
8127         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8128         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8129
8130         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8131         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8132         if(myPawns == 2 && nMine == 3) // KPP
8133             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8134         if(myPawns == 1 && nMine == 2) // KP
8135             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8136         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8137             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8138         if(myPawns) return FALSE;
8139         if(pCnt[WhiteRook+side])
8140             return pCnt[BlackRook-side] ||
8141                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8142                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8143                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8144         if(pCnt[WhiteCannon+side]) {
8145             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8146             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8147         }
8148         if(pCnt[WhiteKnight+side])
8149             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8150         return FALSE;
8151 }
8152
8153 int
8154 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8155 {
8156         VariantClass v = gameInfo.variant;
8157
8158         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8159         if(v == VariantShatranj) return TRUE; // always winnable through baring
8160         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8161         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8162
8163         if(v == VariantXiangqi) {
8164                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8165
8166                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8167                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8168                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8169                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8170                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8171                 if(stale) // we have at least one last-rank P plus perhaps C
8172                     return majors // KPKX
8173                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8174                 else // KCA*E*
8175                     return pCnt[WhiteFerz+side] // KCAK
8176                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8177                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8178                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8179
8180         } else if(v == VariantKnightmate) {
8181                 if(nMine == 1) return FALSE;
8182                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8183         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8184                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8185
8186                 if(nMine == 1) return FALSE; // bare King
8187                 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
8188                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8189                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8190                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8191                 if(pCnt[WhiteKnight+side])
8192                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8193                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8194                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8195                 if(nBishops)
8196                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8197                 if(pCnt[WhiteAlfil+side])
8198                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8199                 if(pCnt[WhiteWazir+side])
8200                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8201         }
8202
8203         return TRUE;
8204 }
8205
8206 int
8207 CompareWithRights (Board b1, Board b2)
8208 {
8209     int rights = 0;
8210     if(!CompareBoards(b1, b2)) return FALSE;
8211     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8212     /* compare castling rights */
8213     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8214            rights++; /* King lost rights, while rook still had them */
8215     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8216         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8217            rights++; /* but at least one rook lost them */
8218     }
8219     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8220            rights++;
8221     if( b1[CASTLING][5] != NoRights ) {
8222         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8223            rights++;
8224     }
8225     return rights == 0;
8226 }
8227
8228 int
8229 Adjudicate (ChessProgramState *cps)
8230 {       // [HGM] some adjudications useful with buggy engines
8231         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8232         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8233         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8234         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8235         int k, drop, count = 0; static int bare = 1;
8236         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8237         Boolean canAdjudicate = !appData.icsActive;
8238
8239         // most tests only when we understand the game, i.e. legality-checking on
8240             if( appData.testLegality )
8241             {   /* [HGM] Some more adjudications for obstinate engines */
8242                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8243                 static int moveCount = 6;
8244                 ChessMove result;
8245                 char *reason = NULL;
8246
8247                 /* Count what is on board. */
8248                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8249
8250                 /* Some material-based adjudications that have to be made before stalemate test */
8251                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8252                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8253                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8254                      if(canAdjudicate && appData.checkMates) {
8255                          if(engineOpponent)
8256                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8257                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8258                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8259                          return 1;
8260                      }
8261                 }
8262
8263                 /* Bare King in Shatranj (loses) or Losers (wins) */
8264                 if( nrW == 1 || nrB == 1) {
8265                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8266                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8267                      if(canAdjudicate && appData.checkMates) {
8268                          if(engineOpponent)
8269                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8270                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8271                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8272                          return 1;
8273                      }
8274                   } else
8275                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8276                   {    /* bare King */
8277                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8278                         if(canAdjudicate && appData.checkMates) {
8279                             /* but only adjudicate if adjudication enabled */
8280                             if(engineOpponent)
8281                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8282                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8283                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8284                             return 1;
8285                         }
8286                   }
8287                 } else bare = 1;
8288
8289
8290             // don't wait for engine to announce game end if we can judge ourselves
8291             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8292               case MT_CHECK:
8293                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8294                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8295                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8296                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8297                             checkCnt++;
8298                         if(checkCnt >= 2) {
8299                             reason = "Xboard adjudication: 3rd check";
8300                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8301                             break;
8302                         }
8303                     }
8304                 }
8305               case MT_NONE:
8306               default:
8307                 break;
8308               case MT_STEALMATE:
8309               case MT_STALEMATE:
8310               case MT_STAINMATE:
8311                 reason = "Xboard adjudication: Stalemate";
8312                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8313                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8314                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8315                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8316                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8317                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8318                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8319                                                                         EP_CHECKMATE : EP_WINS);
8320                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8321                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8322                 }
8323                 break;
8324               case MT_CHECKMATE:
8325                 reason = "Xboard adjudication: Checkmate";
8326                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8327                 if(gameInfo.variant == VariantShogi) {
8328                     if(forwardMostMove > backwardMostMove
8329                        && moveList[forwardMostMove-1][1] == '@'
8330                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8331                         reason = "XBoard adjudication: pawn-drop mate";
8332                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8333                     }
8334                 }
8335                 break;
8336             }
8337
8338                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8339                     case EP_STALEMATE:
8340                         result = GameIsDrawn; break;
8341                     case EP_CHECKMATE:
8342                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8343                     case EP_WINS:
8344                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8345                     default:
8346                         result = EndOfFile;
8347                 }
8348                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8349                     if(engineOpponent)
8350                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8351                     GameEnds( result, reason, GE_XBOARD );
8352                     return 1;
8353                 }
8354
8355                 /* Next absolutely insufficient mating material. */
8356                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8357                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8358                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8359
8360                      /* always flag draws, for judging claims */
8361                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8362
8363                      if(canAdjudicate && appData.materialDraws) {
8364                          /* but only adjudicate them if adjudication enabled */
8365                          if(engineOpponent) {
8366                            SendToProgram("force\n", engineOpponent); // suppress reply
8367                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8368                          }
8369                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8370                          return 1;
8371                      }
8372                 }
8373
8374                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8375                 if(gameInfo.variant == VariantXiangqi ?
8376                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8377                  : nrW + nrB == 4 &&
8378                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8379                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8380                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8381                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8382                    ) ) {
8383                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8384                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8385                           if(engineOpponent) {
8386                             SendToProgram("force\n", engineOpponent); // suppress reply
8387                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8388                           }
8389                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8390                           return 1;
8391                      }
8392                 } else moveCount = 6;
8393             }
8394
8395         // Repetition draws and 50-move rule can be applied independently of legality testing
8396
8397                 /* Check for rep-draws */
8398                 count = 0;
8399                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8400                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8401                 for(k = forwardMostMove-2;
8402                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8403                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8404                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8405                     k-=2)
8406                 {   int rights=0;
8407                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8408                         /* compare castling rights */
8409                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8410                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8411                                 rights++; /* King lost rights, while rook still had them */
8412                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8413                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8414                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8415                                    rights++; /* but at least one rook lost them */
8416                         }
8417                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8418                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8419                                 rights++;
8420                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8421                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8422                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8423                                    rights++;
8424                         }
8425                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8426                             && appData.drawRepeats > 1) {
8427                              /* adjudicate after user-specified nr of repeats */
8428                              int result = GameIsDrawn;
8429                              char *details = "XBoard adjudication: repetition draw";
8430                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8431                                 // [HGM] xiangqi: check for forbidden perpetuals
8432                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8433                                 for(m=forwardMostMove; m>k; m-=2) {
8434                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8435                                         ourPerpetual = 0; // the current mover did not always check
8436                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8437                                         hisPerpetual = 0; // the opponent did not always check
8438                                 }
8439                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8440                                                                         ourPerpetual, hisPerpetual);
8441                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8442                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8443                                     details = "Xboard adjudication: perpetual checking";
8444                                 } else
8445                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8446                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8447                                 } else
8448                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8449                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8450                                         result = BlackWins;
8451                                         details = "Xboard adjudication: repetition";
8452                                     }
8453                                 } else // it must be XQ
8454                                 // Now check for perpetual chases
8455                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8456                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8457                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8458                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8459                                         static char resdet[MSG_SIZ];
8460                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8461                                         details = resdet;
8462                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8463                                     } else
8464                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8465                                         break; // Abort repetition-checking loop.
8466                                 }
8467                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8468                              }
8469                              if(engineOpponent) {
8470                                SendToProgram("force\n", engineOpponent); // suppress reply
8471                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8472                              }
8473                              GameEnds( result, details, GE_XBOARD );
8474                              return 1;
8475                         }
8476                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8477                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8478                     }
8479                 }
8480
8481                 /* Now we test for 50-move draws. Determine ply count */
8482                 count = forwardMostMove;
8483                 /* look for last irreversble move */
8484                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8485                     count--;
8486                 /* if we hit starting position, add initial plies */
8487                 if( count == backwardMostMove )
8488                     count -= initialRulePlies;
8489                 count = forwardMostMove - count;
8490                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8491                         // adjust reversible move counter for checks in Xiangqi
8492                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8493                         if(i < backwardMostMove) i = backwardMostMove;
8494                         while(i <= forwardMostMove) {
8495                                 lastCheck = inCheck; // check evasion does not count
8496                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8497                                 if(inCheck || lastCheck) count--; // check does not count
8498                                 i++;
8499                         }
8500                 }
8501                 if( count >= 100)
8502                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8503                          /* this is used to judge if draw claims are legal */
8504                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8505                          if(engineOpponent) {
8506                            SendToProgram("force\n", engineOpponent); // suppress reply
8507                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8508                          }
8509                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8510                          return 1;
8511                 }
8512
8513                 /* if draw offer is pending, treat it as a draw claim
8514                  * when draw condition present, to allow engines a way to
8515                  * claim draws before making their move to avoid a race
8516                  * condition occurring after their move
8517                  */
8518                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8519                          char *p = NULL;
8520                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8521                              p = "Draw claim: 50-move rule";
8522                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8523                              p = "Draw claim: 3-fold repetition";
8524                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8525                              p = "Draw claim: insufficient mating material";
8526                          if( p != NULL && canAdjudicate) {
8527                              if(engineOpponent) {
8528                                SendToProgram("force\n", engineOpponent); // suppress reply
8529                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8530                              }
8531                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8532                              return 1;
8533                          }
8534                 }
8535
8536                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8537                     if(engineOpponent) {
8538                       SendToProgram("force\n", engineOpponent); // suppress reply
8539                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8540                     }
8541                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8542                     return 1;
8543                 }
8544         return 0;
8545 }
8546
8547 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8548 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8549 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8550
8551 static int
8552 BitbaseProbe ()
8553 {
8554     int pieces[10], squares[10], cnt=0, r, f, res;
8555     static int loaded;
8556     static PPROBE_EGBB probeBB;
8557     if(!appData.testLegality) return 10;
8558     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8559     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8560     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8561     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8562         ChessSquare piece = boards[forwardMostMove][r][f];
8563         int black = (piece >= BlackPawn);
8564         int type = piece - black*BlackPawn;
8565         if(piece == EmptySquare) continue;
8566         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8567         if(type == WhiteKing) type = WhiteQueen + 1;
8568         type = egbbCode[type];
8569         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8570         pieces[cnt] = type + black*6;
8571         if(++cnt > 5) return 11;
8572     }
8573     pieces[cnt] = squares[cnt] = 0;
8574     // probe EGBB
8575     if(loaded == 2) return 13; // loading failed before
8576     if(loaded == 0) {
8577         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8578         HMODULE lib;
8579         PLOAD_EGBB loadBB;
8580         loaded = 2; // prepare for failure
8581         if(!path) return 13; // no egbb installed
8582         strncpy(buf, path + 8, MSG_SIZ);
8583         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8584         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8585         lib = LoadLibrary(buf);
8586         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8587         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8588         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8589         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8590         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8591         loaded = 1; // success!
8592     }
8593     res = probeBB(forwardMostMove & 1, pieces, squares);
8594     return res > 0 ? 1 : res < 0 ? -1 : 0;
8595 }
8596
8597 char *
8598 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8599 {   // [HGM] book: this routine intercepts moves to simulate book replies
8600     char *bookHit = NULL;
8601
8602     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8603         char buf[MSG_SIZ];
8604         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8605         SendToProgram(buf, cps);
8606     }
8607     //first determine if the incoming move brings opponent into his book
8608     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8609         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8610     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8611     if(bookHit != NULL && !cps->bookSuspend) {
8612         // make sure opponent is not going to reply after receiving move to book position
8613         SendToProgram("force\n", cps);
8614         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8615     }
8616     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8617     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8618     // now arrange restart after book miss
8619     if(bookHit) {
8620         // after a book hit we never send 'go', and the code after the call to this routine
8621         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8622         char buf[MSG_SIZ], *move = bookHit;
8623         if(cps->useSAN) {
8624             int fromX, fromY, toX, toY;
8625             char promoChar;
8626             ChessMove moveType;
8627             move = buf + 30;
8628             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8629                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8630                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8631                                     PosFlags(forwardMostMove),
8632                                     fromY, fromX, toY, toX, promoChar, move);
8633             } else {
8634                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8635                 bookHit = NULL;
8636             }
8637         }
8638         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8639         SendToProgram(buf, cps);
8640         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8641     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8642         SendToProgram("go\n", cps);
8643         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8644     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8645         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8646             SendToProgram("go\n", cps);
8647         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8648     }
8649     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8650 }
8651
8652 int
8653 LoadError (char *errmess, ChessProgramState *cps)
8654 {   // unloads engine and switches back to -ncp mode if it was first
8655     if(cps->initDone) return FALSE;
8656     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8657     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8658     cps->pr = NoProc;
8659     if(cps == &first) {
8660         appData.noChessProgram = TRUE;
8661         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8662         gameMode = BeginningOfGame; ModeHighlight();
8663         SetNCPMode();
8664     }
8665     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8666     DisplayMessage("", ""); // erase waiting message
8667     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8668     return TRUE;
8669 }
8670
8671 char *savedMessage;
8672 ChessProgramState *savedState;
8673 void
8674 DeferredBookMove (void)
8675 {
8676         if(savedState->lastPing != savedState->lastPong)
8677                     ScheduleDelayedEvent(DeferredBookMove, 10);
8678         else
8679         HandleMachineMove(savedMessage, savedState);
8680 }
8681
8682 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8683 static ChessProgramState *stalledEngine;
8684 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8685
8686 void
8687 HandleMachineMove (char *message, ChessProgramState *cps)
8688 {
8689     static char firstLeg[20], legs;
8690     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8691     char realname[MSG_SIZ];
8692     int fromX, fromY, toX, toY;
8693     ChessMove moveType;
8694     char promoChar, roar;
8695     char *p, *pv=buf1;
8696     int oldError;
8697     char *bookHit;
8698
8699     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8700         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8701         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8702             DisplayError(_("Invalid pairing from pairing engine"), 0);
8703             return;
8704         }
8705         pairingReceived = 1;
8706         NextMatchGame();
8707         return; // Skim the pairing messages here.
8708     }
8709
8710     oldError = cps->userError; cps->userError = 0;
8711
8712 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8713     /*
8714      * Kludge to ignore BEL characters
8715      */
8716     while (*message == '\007') message++;
8717
8718     /*
8719      * [HGM] engine debug message: ignore lines starting with '#' character
8720      */
8721     if(cps->debug && *message == '#') return;
8722
8723     /*
8724      * Look for book output
8725      */
8726     if (cps == &first && bookRequested) {
8727         if (message[0] == '\t' || message[0] == ' ') {
8728             /* Part of the book output is here; append it */
8729             strcat(bookOutput, message);
8730             strcat(bookOutput, "  \n");
8731             return;
8732         } else if (bookOutput[0] != NULLCHAR) {
8733             /* All of book output has arrived; display it */
8734             char *p = bookOutput;
8735             while (*p != NULLCHAR) {
8736                 if (*p == '\t') *p = ' ';
8737                 p++;
8738             }
8739             DisplayInformation(bookOutput);
8740             bookRequested = FALSE;
8741             /* Fall through to parse the current output */
8742         }
8743     }
8744
8745     /*
8746      * Look for machine move.
8747      */
8748     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8749         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8750     {
8751         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8752             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8753             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8754             stalledEngine = cps;
8755             if(appData.ponderNextMove) { // bring opponent out of ponder
8756                 if(gameMode == TwoMachinesPlay) {
8757                     if(cps->other->pause)
8758                         PauseEngine(cps->other);
8759                     else
8760                         SendToProgram("easy\n", cps->other);
8761                 }
8762             }
8763             StopClocks();
8764             return;
8765         }
8766
8767       if(cps->usePing) {
8768
8769         /* This method is only useful on engines that support ping */
8770         if(abortEngineThink) {
8771             if (appData.debugMode) {
8772                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8773             }
8774             SendToProgram("undo\n", cps);
8775             return;
8776         }
8777
8778         if (cps->lastPing != cps->lastPong) {
8779             /* Extra move from before last new; ignore */
8780             if (appData.debugMode) {
8781                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8782             }
8783           return;
8784         }
8785
8786       } else {
8787
8788         int machineWhite = FALSE;
8789
8790         switch (gameMode) {
8791           case BeginningOfGame:
8792             /* Extra move from before last reset; ignore */
8793             if (appData.debugMode) {
8794                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8795             }
8796             return;
8797
8798           case EndOfGame:
8799           case IcsIdle:
8800           default:
8801             /* Extra move after we tried to stop.  The mode test is
8802                not a reliable way of detecting this problem, but it's
8803                the best we can do on engines that don't support ping.
8804             */
8805             if (appData.debugMode) {
8806                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8807                         cps->which, gameMode);
8808             }
8809             SendToProgram("undo\n", cps);
8810             return;
8811
8812           case MachinePlaysWhite:
8813           case IcsPlayingWhite:
8814             machineWhite = TRUE;
8815             break;
8816
8817           case MachinePlaysBlack:
8818           case IcsPlayingBlack:
8819             machineWhite = FALSE;
8820             break;
8821
8822           case TwoMachinesPlay:
8823             machineWhite = (cps->twoMachinesColor[0] == 'w');
8824             break;
8825         }
8826         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8827             if (appData.debugMode) {
8828                 fprintf(debugFP,
8829                         "Ignoring move out of turn by %s, gameMode %d"
8830                         ", forwardMost %d\n",
8831                         cps->which, gameMode, forwardMostMove);
8832             }
8833             return;
8834         }
8835       }
8836
8837         if(cps->alphaRank) AlphaRank(machineMove, 4);
8838
8839         // [HGM] lion: (some very limited) support for Alien protocol
8840         killX = killY = kill2X = kill2Y = -1;
8841         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8842             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8843             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8844             return;
8845         }
8846         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8847             char *q = strchr(p+1, ',');            // second comma?
8848             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8849             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8850             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8851         }
8852         if(firstLeg[0]) { // there was a previous leg;
8853             // only support case where same piece makes two step
8854             char buf[20], *p = machineMove+1, *q = buf+1, f;
8855             safeStrCpy(buf, machineMove, 20);
8856             while(isdigit(*q)) q++; // find start of to-square
8857             safeStrCpy(machineMove, firstLeg, 20);
8858             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8859             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8860             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8861             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8862             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8863             firstLeg[0] = NULLCHAR; legs = 0;
8864         }
8865
8866         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8867                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8868             /* Machine move could not be parsed; ignore it. */
8869           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8870                     machineMove, _(cps->which));
8871             DisplayMoveError(buf1);
8872             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8873                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8874             if (gameMode == TwoMachinesPlay) {
8875               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8876                        buf1, GE_XBOARD);
8877             }
8878             return;
8879         }
8880
8881         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8882         /* So we have to redo legality test with true e.p. status here,  */
8883         /* to make sure an illegal e.p. capture does not slip through,   */
8884         /* to cause a forfeit on a justified illegal-move complaint      */
8885         /* of the opponent.                                              */
8886         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8887            ChessMove moveType;
8888            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8889                              fromY, fromX, toY, toX, promoChar);
8890             if(moveType == IllegalMove) {
8891               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8892                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8893                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8894                            buf1, GE_XBOARD);
8895                 return;
8896            } else if(!appData.fischerCastling)
8897            /* [HGM] Kludge to handle engines that send FRC-style castling
8898               when they shouldn't (like TSCP-Gothic) */
8899            switch(moveType) {
8900              case WhiteASideCastleFR:
8901              case BlackASideCastleFR:
8902                toX+=2;
8903                currentMoveString[2]++;
8904                break;
8905              case WhiteHSideCastleFR:
8906              case BlackHSideCastleFR:
8907                toX--;
8908                currentMoveString[2]--;
8909                break;
8910              default: ; // nothing to do, but suppresses warning of pedantic compilers
8911            }
8912         }
8913         hintRequested = FALSE;
8914         lastHint[0] = NULLCHAR;
8915         bookRequested = FALSE;
8916         /* Program may be pondering now */
8917         cps->maybeThinking = TRUE;
8918         if (cps->sendTime == 2) cps->sendTime = 1;
8919         if (cps->offeredDraw) cps->offeredDraw--;
8920
8921         /* [AS] Save move info*/
8922         pvInfoList[ forwardMostMove ].score = programStats.score;
8923         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8924         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8925
8926         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8927
8928         /* Test suites abort the 'game' after one move */
8929         if(*appData.finger) {
8930            static FILE *f;
8931            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8932            if(!f) f = fopen(appData.finger, "w");
8933            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8934            else { DisplayFatalError("Bad output file", errno, 0); return; }
8935            free(fen);
8936            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8937         }
8938         if(appData.epd) {
8939            if(solvingTime >= 0) {
8940               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8941               totalTime += solvingTime; first.matchWins++;
8942            } else {
8943               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8944               second.matchWins++;
8945            }
8946            OutputKibitz(2, buf1);
8947            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8948         }
8949
8950         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8951         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8952             int count = 0;
8953
8954             while( count < adjudicateLossPlies ) {
8955                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8956
8957                 if( count & 1 ) {
8958                     score = -score; /* Flip score for winning side */
8959                 }
8960
8961                 if( score > appData.adjudicateLossThreshold ) {
8962                     break;
8963                 }
8964
8965                 count++;
8966             }
8967
8968             if( count >= adjudicateLossPlies ) {
8969                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8970
8971                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8972                     "Xboard adjudication",
8973                     GE_XBOARD );
8974
8975                 return;
8976             }
8977         }
8978
8979         if(Adjudicate(cps)) {
8980             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8981             return; // [HGM] adjudicate: for all automatic game ends
8982         }
8983
8984 #if ZIPPY
8985         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8986             first.initDone) {
8987           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8988                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8989                 SendToICS("draw ");
8990                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8991           }
8992           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8993           ics_user_moved = 1;
8994           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8995                 char buf[3*MSG_SIZ];
8996
8997                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8998                         programStats.score / 100.,
8999                         programStats.depth,
9000                         programStats.time / 100.,
9001                         (unsigned int)programStats.nodes,
9002                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9003                         programStats.movelist);
9004                 SendToICS(buf);
9005           }
9006         }
9007 #endif
9008
9009         /* [AS] Clear stats for next move */
9010         ClearProgramStats();
9011         thinkOutput[0] = NULLCHAR;
9012         hiddenThinkOutputState = 0;
9013
9014         bookHit = NULL;
9015         if (gameMode == TwoMachinesPlay) {
9016             /* [HGM] relaying draw offers moved to after reception of move */
9017             /* and interpreting offer as claim if it brings draw condition */
9018             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9019                 SendToProgram("draw\n", cps->other);
9020             }
9021             if (cps->other->sendTime) {
9022                 SendTimeRemaining(cps->other,
9023                                   cps->other->twoMachinesColor[0] == 'w');
9024             }
9025             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9026             if (firstMove && !bookHit) {
9027                 firstMove = FALSE;
9028                 if (cps->other->useColors) {
9029                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9030                 }
9031                 SendToProgram("go\n", cps->other);
9032             }
9033             cps->other->maybeThinking = TRUE;
9034         }
9035
9036         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9037
9038         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9039
9040         if (!pausing && appData.ringBellAfterMoves) {
9041             if(!roar) RingBell();
9042         }
9043
9044         /*
9045          * Reenable menu items that were disabled while
9046          * machine was thinking
9047          */
9048         if (gameMode != TwoMachinesPlay)
9049             SetUserThinkingEnables();
9050
9051         // [HGM] book: after book hit opponent has received move and is now in force mode
9052         // force the book reply into it, and then fake that it outputted this move by jumping
9053         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9054         if(bookHit) {
9055                 static char bookMove[MSG_SIZ]; // a bit generous?
9056
9057                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9058                 strcat(bookMove, bookHit);
9059                 message = bookMove;
9060                 cps = cps->other;
9061                 programStats.nodes = programStats.depth = programStats.time =
9062                 programStats.score = programStats.got_only_move = 0;
9063                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9064
9065                 if(cps->lastPing != cps->lastPong) {
9066                     savedMessage = message; // args for deferred call
9067                     savedState = cps;
9068                     ScheduleDelayedEvent(DeferredBookMove, 10);
9069                     return;
9070                 }
9071                 goto FakeBookMove;
9072         }
9073
9074         return;
9075     }
9076
9077     /* Set special modes for chess engines.  Later something general
9078      *  could be added here; for now there is just one kludge feature,
9079      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9080      *  when "xboard" is given as an interactive command.
9081      */
9082     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9083         cps->useSigint = FALSE;
9084         cps->useSigterm = FALSE;
9085     }
9086     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9087       ParseFeatures(message+8, cps);
9088       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9089     }
9090
9091     if (!strncmp(message, "setup ", 6) && 
9092         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9093           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9094                                         ) { // [HGM] allow first engine to define opening position
9095       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9096       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9097       *buf = NULLCHAR;
9098       if(sscanf(message, "setup (%s", buf) == 1) {
9099         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9100         ASSIGN(appData.pieceToCharTable, buf);
9101       }
9102       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9103       if(dummy >= 3) {
9104         while(message[s] && message[s++] != ' ');
9105         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9106            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9107             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9108             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9109           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9110           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9111           startedFromSetupPosition = FALSE;
9112         }
9113       }
9114       if(startedFromSetupPosition) return;
9115       ParseFEN(boards[0], &dummy, message+s, FALSE);
9116       DrawPosition(TRUE, boards[0]);
9117       CopyBoard(initialPosition, boards[0]);
9118       startedFromSetupPosition = TRUE;
9119       return;
9120     }
9121     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9122       ChessSquare piece = WhitePawn;
9123       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9124       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9125       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9126       piece += CharToPiece(ID & 255) - WhitePawn;
9127       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9128       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9129       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9130       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9131       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9132       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9133                                                && gameInfo.variant != VariantGreat
9134                                                && gameInfo.variant != VariantFairy    ) return;
9135       if(piece < EmptySquare) {
9136         pieceDefs = TRUE;
9137         ASSIGN(pieceDesc[piece], buf1);
9138         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9139       }
9140       return;
9141     }
9142     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9143       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9144       Sweep(0);
9145       return;
9146     }
9147     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9148      * want this, I was asked to put it in, and obliged.
9149      */
9150     if (!strncmp(message, "setboard ", 9)) {
9151         Board initial_position;
9152
9153         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9154
9155         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9156             DisplayError(_("Bad FEN received from engine"), 0);
9157             return ;
9158         } else {
9159            Reset(TRUE, FALSE);
9160            CopyBoard(boards[0], initial_position);
9161            initialRulePlies = FENrulePlies;
9162            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9163            else gameMode = MachinePlaysBlack;
9164            DrawPosition(FALSE, boards[currentMove]);
9165         }
9166         return;
9167     }
9168
9169     /*
9170      * Look for communication commands
9171      */
9172     if (!strncmp(message, "telluser ", 9)) {
9173         if(message[9] == '\\' && message[10] == '\\')
9174             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9175         PlayTellSound();
9176         DisplayNote(message + 9);
9177         return;
9178     }
9179     if (!strncmp(message, "tellusererror ", 14)) {
9180         cps->userError = 1;
9181         if(message[14] == '\\' && message[15] == '\\')
9182             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9183         PlayTellSound();
9184         DisplayError(message + 14, 0);
9185         return;
9186     }
9187     if (!strncmp(message, "tellopponent ", 13)) {
9188       if (appData.icsActive) {
9189         if (loggedOn) {
9190           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9191           SendToICS(buf1);
9192         }
9193       } else {
9194         DisplayNote(message + 13);
9195       }
9196       return;
9197     }
9198     if (!strncmp(message, "tellothers ", 11)) {
9199       if (appData.icsActive) {
9200         if (loggedOn) {
9201           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9202           SendToICS(buf1);
9203         }
9204       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9205       return;
9206     }
9207     if (!strncmp(message, "tellall ", 8)) {
9208       if (appData.icsActive) {
9209         if (loggedOn) {
9210           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9211           SendToICS(buf1);
9212         }
9213       } else {
9214         DisplayNote(message + 8);
9215       }
9216       return;
9217     }
9218     if (strncmp(message, "warning", 7) == 0) {
9219         /* Undocumented feature, use tellusererror in new code */
9220         DisplayError(message, 0);
9221         return;
9222     }
9223     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9224         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9225         strcat(realname, " query");
9226         AskQuestion(realname, buf2, buf1, cps->pr);
9227         return;
9228     }
9229     /* Commands from the engine directly to ICS.  We don't allow these to be
9230      *  sent until we are logged on. Crafty kibitzes have been known to
9231      *  interfere with the login process.
9232      */
9233     if (loggedOn) {
9234         if (!strncmp(message, "tellics ", 8)) {
9235             SendToICS(message + 8);
9236             SendToICS("\n");
9237             return;
9238         }
9239         if (!strncmp(message, "tellicsnoalias ", 15)) {
9240             SendToICS(ics_prefix);
9241             SendToICS(message + 15);
9242             SendToICS("\n");
9243             return;
9244         }
9245         /* The following are for backward compatibility only */
9246         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9247             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9248             SendToICS(ics_prefix);
9249             SendToICS(message);
9250             SendToICS("\n");
9251             return;
9252         }
9253     }
9254     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9255         if(initPing == cps->lastPong) {
9256             if(gameInfo.variant == VariantUnknown) {
9257                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9258                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9259                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9260             }
9261             initPing = -1;
9262         }
9263         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9264             abortEngineThink = FALSE;
9265             DisplayMessage("", "");
9266             ThawUI();
9267         }
9268         return;
9269     }
9270     if(!strncmp(message, "highlight ", 10)) {
9271         if(appData.testLegality && !*engineVariant && appData.markers) return;
9272         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9273         return;
9274     }
9275     if(!strncmp(message, "click ", 6)) {
9276         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9277         if(appData.testLegality || !appData.oneClick) return;
9278         sscanf(message+6, "%c%d%c", &f, &y, &c);
9279         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9280         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9281         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9282         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9283         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9284         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9285             LeftClick(Release, lastLeftX, lastLeftY);
9286         controlKey  = (c == ',');
9287         LeftClick(Press, x, y);
9288         LeftClick(Release, x, y);
9289         first.highlight = f;
9290         return;
9291     }
9292     /*
9293      * If the move is illegal, cancel it and redraw the board.
9294      * Also deal with other error cases.  Matching is rather loose
9295      * here to accommodate engines written before the spec.
9296      */
9297     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9298         strncmp(message, "Error", 5) == 0) {
9299         if (StrStr(message, "name") ||
9300             StrStr(message, "rating") || StrStr(message, "?") ||
9301             StrStr(message, "result") || StrStr(message, "board") ||
9302             StrStr(message, "bk") || StrStr(message, "computer") ||
9303             StrStr(message, "variant") || StrStr(message, "hint") ||
9304             StrStr(message, "random") || StrStr(message, "depth") ||
9305             StrStr(message, "accepted")) {
9306             return;
9307         }
9308         if (StrStr(message, "protover")) {
9309           /* Program is responding to input, so it's apparently done
9310              initializing, and this error message indicates it is
9311              protocol version 1.  So we don't need to wait any longer
9312              for it to initialize and send feature commands. */
9313           FeatureDone(cps, 1);
9314           cps->protocolVersion = 1;
9315           return;
9316         }
9317         cps->maybeThinking = FALSE;
9318
9319         if (StrStr(message, "draw")) {
9320             /* Program doesn't have "draw" command */
9321             cps->sendDrawOffers = 0;
9322             return;
9323         }
9324         if (cps->sendTime != 1 &&
9325             (StrStr(message, "time") || StrStr(message, "otim"))) {
9326           /* Program apparently doesn't have "time" or "otim" command */
9327           cps->sendTime = 0;
9328           return;
9329         }
9330         if (StrStr(message, "analyze")) {
9331             cps->analysisSupport = FALSE;
9332             cps->analyzing = FALSE;
9333 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9334             EditGameEvent(); // [HGM] try to preserve loaded game
9335             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9336             DisplayError(buf2, 0);
9337             return;
9338         }
9339         if (StrStr(message, "(no matching move)st")) {
9340           /* Special kludge for GNU Chess 4 only */
9341           cps->stKludge = TRUE;
9342           SendTimeControl(cps, movesPerSession, timeControl,
9343                           timeIncrement, appData.searchDepth,
9344                           searchTime);
9345           return;
9346         }
9347         if (StrStr(message, "(no matching move)sd")) {
9348           /* Special kludge for GNU Chess 4 only */
9349           cps->sdKludge = TRUE;
9350           SendTimeControl(cps, movesPerSession, timeControl,
9351                           timeIncrement, appData.searchDepth,
9352                           searchTime);
9353           return;
9354         }
9355         if (!StrStr(message, "llegal")) {
9356             return;
9357         }
9358         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9359             gameMode == IcsIdle) return;
9360         if (forwardMostMove <= backwardMostMove) return;
9361         if (pausing) PauseEvent();
9362       if(appData.forceIllegal) {
9363             // [HGM] illegal: machine refused move; force position after move into it
9364           SendToProgram("force\n", cps);
9365           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9366                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9367                 // when black is to move, while there might be nothing on a2 or black
9368                 // might already have the move. So send the board as if white has the move.
9369                 // But first we must change the stm of the engine, as it refused the last move
9370                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9371                 if(WhiteOnMove(forwardMostMove)) {
9372                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9373                     SendBoard(cps, forwardMostMove); // kludgeless board
9374                 } else {
9375                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9376                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9377                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9378                 }
9379           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9380             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9381                  gameMode == TwoMachinesPlay)
9382               SendToProgram("go\n", cps);
9383             return;
9384       } else
9385         if (gameMode == PlayFromGameFile) {
9386             /* Stop reading this game file */
9387             gameMode = EditGame;
9388             ModeHighlight();
9389         }
9390         /* [HGM] illegal-move claim should forfeit game when Xboard */
9391         /* only passes fully legal moves                            */
9392         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9393             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9394                                 "False illegal-move claim", GE_XBOARD );
9395             return; // do not take back move we tested as valid
9396         }
9397         currentMove = forwardMostMove-1;
9398         DisplayMove(currentMove-1); /* before DisplayMoveError */
9399         SwitchClocks(forwardMostMove-1); // [HGM] race
9400         DisplayBothClocks();
9401         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9402                 parseList[currentMove], _(cps->which));
9403         DisplayMoveError(buf1);
9404         DrawPosition(FALSE, boards[currentMove]);
9405
9406         SetUserThinkingEnables();
9407         return;
9408     }
9409     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9410         /* Program has a broken "time" command that
9411            outputs a string not ending in newline.
9412            Don't use it. */
9413         cps->sendTime = 0;
9414     }
9415     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9416         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9417             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9418     }
9419
9420     /*
9421      * If chess program startup fails, exit with an error message.
9422      * Attempts to recover here are futile. [HGM] Well, we try anyway
9423      */
9424     if ((StrStr(message, "unknown host") != NULL)
9425         || (StrStr(message, "No remote directory") != NULL)
9426         || (StrStr(message, "not found") != NULL)
9427         || (StrStr(message, "No such file") != NULL)
9428         || (StrStr(message, "can't alloc") != NULL)
9429         || (StrStr(message, "Permission denied") != NULL)) {
9430
9431         cps->maybeThinking = FALSE;
9432         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9433                 _(cps->which), cps->program, cps->host, message);
9434         RemoveInputSource(cps->isr);
9435         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9436             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9437             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9438         }
9439         return;
9440     }
9441
9442     /*
9443      * Look for hint output
9444      */
9445     if (sscanf(message, "Hint: %s", buf1) == 1) {
9446         if (cps == &first && hintRequested) {
9447             hintRequested = FALSE;
9448             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9449                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9450                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9451                                     PosFlags(forwardMostMove),
9452                                     fromY, fromX, toY, toX, promoChar, buf1);
9453                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9454                 DisplayInformation(buf2);
9455             } else {
9456                 /* Hint move could not be parsed!? */
9457               snprintf(buf2, sizeof(buf2),
9458                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9459                         buf1, _(cps->which));
9460                 DisplayError(buf2, 0);
9461             }
9462         } else {
9463           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9464         }
9465         return;
9466     }
9467
9468     /*
9469      * Ignore other messages if game is not in progress
9470      */
9471     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9472         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9473
9474     /*
9475      * look for win, lose, draw, or draw offer
9476      */
9477     if (strncmp(message, "1-0", 3) == 0) {
9478         char *p, *q, *r = "";
9479         p = strchr(message, '{');
9480         if (p) {
9481             q = strchr(p, '}');
9482             if (q) {
9483                 *q = NULLCHAR;
9484                 r = p + 1;
9485             }
9486         }
9487         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9488         return;
9489     } else if (strncmp(message, "0-1", 3) == 0) {
9490         char *p, *q, *r = "";
9491         p = strchr(message, '{');
9492         if (p) {
9493             q = strchr(p, '}');
9494             if (q) {
9495                 *q = NULLCHAR;
9496                 r = p + 1;
9497             }
9498         }
9499         /* Kludge for Arasan 4.1 bug */
9500         if (strcmp(r, "Black resigns") == 0) {
9501             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9502             return;
9503         }
9504         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9505         return;
9506     } else if (strncmp(message, "1/2", 3) == 0) {
9507         char *p, *q, *r = "";
9508         p = strchr(message, '{');
9509         if (p) {
9510             q = strchr(p, '}');
9511             if (q) {
9512                 *q = NULLCHAR;
9513                 r = p + 1;
9514             }
9515         }
9516
9517         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9518         return;
9519
9520     } else if (strncmp(message, "White resign", 12) == 0) {
9521         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9522         return;
9523     } else if (strncmp(message, "Black resign", 12) == 0) {
9524         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9525         return;
9526     } else if (strncmp(message, "White matches", 13) == 0 ||
9527                strncmp(message, "Black matches", 13) == 0   ) {
9528         /* [HGM] ignore GNUShogi noises */
9529         return;
9530     } else if (strncmp(message, "White", 5) == 0 &&
9531                message[5] != '(' &&
9532                StrStr(message, "Black") == NULL) {
9533         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9534         return;
9535     } else if (strncmp(message, "Black", 5) == 0 &&
9536                message[5] != '(') {
9537         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9538         return;
9539     } else if (strcmp(message, "resign") == 0 ||
9540                strcmp(message, "computer resigns") == 0) {
9541         switch (gameMode) {
9542           case MachinePlaysBlack:
9543           case IcsPlayingBlack:
9544             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9545             break;
9546           case MachinePlaysWhite:
9547           case IcsPlayingWhite:
9548             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9549             break;
9550           case TwoMachinesPlay:
9551             if (cps->twoMachinesColor[0] == 'w')
9552               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9553             else
9554               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9555             break;
9556           default:
9557             /* can't happen */
9558             break;
9559         }
9560         return;
9561     } else if (strncmp(message, "opponent mates", 14) == 0) {
9562         switch (gameMode) {
9563           case MachinePlaysBlack:
9564           case IcsPlayingBlack:
9565             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9566             break;
9567           case MachinePlaysWhite:
9568           case IcsPlayingWhite:
9569             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9570             break;
9571           case TwoMachinesPlay:
9572             if (cps->twoMachinesColor[0] == 'w')
9573               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9574             else
9575               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9576             break;
9577           default:
9578             /* can't happen */
9579             break;
9580         }
9581         return;
9582     } else if (strncmp(message, "computer mates", 14) == 0) {
9583         switch (gameMode) {
9584           case MachinePlaysBlack:
9585           case IcsPlayingBlack:
9586             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9587             break;
9588           case MachinePlaysWhite:
9589           case IcsPlayingWhite:
9590             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9591             break;
9592           case TwoMachinesPlay:
9593             if (cps->twoMachinesColor[0] == 'w')
9594               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9595             else
9596               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9597             break;
9598           default:
9599             /* can't happen */
9600             break;
9601         }
9602         return;
9603     } else if (strncmp(message, "checkmate", 9) == 0) {
9604         if (WhiteOnMove(forwardMostMove)) {
9605             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9606         } else {
9607             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9608         }
9609         return;
9610     } else if (strstr(message, "Draw") != NULL ||
9611                strstr(message, "game is a draw") != NULL) {
9612         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9613         return;
9614     } else if (strstr(message, "offer") != NULL &&
9615                strstr(message, "draw") != NULL) {
9616 #if ZIPPY
9617         if (appData.zippyPlay && first.initDone) {
9618             /* Relay offer to ICS */
9619             SendToICS(ics_prefix);
9620             SendToICS("draw\n");
9621         }
9622 #endif
9623         cps->offeredDraw = 2; /* valid until this engine moves twice */
9624         if (gameMode == TwoMachinesPlay) {
9625             if (cps->other->offeredDraw) {
9626                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9627             /* [HGM] in two-machine mode we delay relaying draw offer      */
9628             /* until after we also have move, to see if it is really claim */
9629             }
9630         } else if (gameMode == MachinePlaysWhite ||
9631                    gameMode == MachinePlaysBlack) {
9632           if (userOfferedDraw) {
9633             DisplayInformation(_("Machine accepts your draw offer"));
9634             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9635           } else {
9636             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9637           }
9638         }
9639     }
9640
9641
9642     /*
9643      * Look for thinking output
9644      */
9645     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9646           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9647                                 ) {
9648         int plylev, mvleft, mvtot, curscore, time;
9649         char mvname[MOVE_LEN];
9650         u64 nodes; // [DM]
9651         char plyext;
9652         int ignore = FALSE;
9653         int prefixHint = FALSE;
9654         mvname[0] = NULLCHAR;
9655
9656         switch (gameMode) {
9657           case MachinePlaysBlack:
9658           case IcsPlayingBlack:
9659             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9660             break;
9661           case MachinePlaysWhite:
9662           case IcsPlayingWhite:
9663             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9664             break;
9665           case AnalyzeMode:
9666           case AnalyzeFile:
9667             break;
9668           case IcsObserving: /* [DM] icsEngineAnalyze */
9669             if (!appData.icsEngineAnalyze) ignore = TRUE;
9670             break;
9671           case TwoMachinesPlay:
9672             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9673                 ignore = TRUE;
9674             }
9675             break;
9676           default:
9677             ignore = TRUE;
9678             break;
9679         }
9680
9681         if (!ignore) {
9682             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9683             buf1[0] = NULLCHAR;
9684             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9685                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9686                 char score_buf[MSG_SIZ];
9687
9688                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9689                     nodes += u64Const(0x100000000);
9690
9691                 if (plyext != ' ' && plyext != '\t') {
9692                     time *= 100;
9693                 }
9694
9695                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9696                 if( cps->scoreIsAbsolute &&
9697                     ( gameMode == MachinePlaysBlack ||
9698                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9699                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9700                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9701                      !WhiteOnMove(currentMove)
9702                     ) )
9703                 {
9704                     curscore = -curscore;
9705                 }
9706
9707                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9708
9709                 if(*bestMove) { // rememer time best EPD move was first found
9710                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9711                     ChessMove mt;
9712                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9713                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9714                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9715                 }
9716
9717                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9718                         char buf[MSG_SIZ];
9719                         FILE *f;
9720                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9721                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9722                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9723                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9724                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9725                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9726                                 fclose(f);
9727                         }
9728                         else
9729                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9730                           DisplayError(_("failed writing PV"), 0);
9731                 }
9732
9733                 tempStats.depth = plylev;
9734                 tempStats.nodes = nodes;
9735                 tempStats.time = time;
9736                 tempStats.score = curscore;
9737                 tempStats.got_only_move = 0;
9738
9739                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9740                         int ticklen;
9741
9742                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9743                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9744                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9745                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9746                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9747                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9748                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9749                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9750                 }
9751
9752                 /* Buffer overflow protection */
9753                 if (pv[0] != NULLCHAR) {
9754                     if (strlen(pv) >= sizeof(tempStats.movelist)
9755                         && appData.debugMode) {
9756                         fprintf(debugFP,
9757                                 "PV is too long; using the first %u bytes.\n",
9758                                 (unsigned) sizeof(tempStats.movelist) - 1);
9759                     }
9760
9761                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9762                 } else {
9763                     sprintf(tempStats.movelist, " no PV\n");
9764                 }
9765
9766                 if (tempStats.seen_stat) {
9767                     tempStats.ok_to_send = 1;
9768                 }
9769
9770                 if (strchr(tempStats.movelist, '(') != NULL) {
9771                     tempStats.line_is_book = 1;
9772                     tempStats.nr_moves = 0;
9773                     tempStats.moves_left = 0;
9774                 } else {
9775                     tempStats.line_is_book = 0;
9776                 }
9777
9778                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9779                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9780
9781                 SendProgramStatsToFrontend( cps, &tempStats );
9782
9783                 /*
9784                     [AS] Protect the thinkOutput buffer from overflow... this
9785                     is only useful if buf1 hasn't overflowed first!
9786                 */
9787                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9788                 if(curscore >= MATE_SCORE) 
9789                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9790                 else if(curscore <= -MATE_SCORE) 
9791                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9792                 else
9793                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9794                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9795                          plylev,
9796                          (gameMode == TwoMachinesPlay ?
9797                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9798                          score_buf,
9799                          prefixHint ? lastHint : "",
9800                          prefixHint ? " " : "" );
9801
9802                 if( buf1[0] != NULLCHAR ) {
9803                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9804
9805                     if( strlen(pv) > max_len ) {
9806                         if( appData.debugMode) {
9807                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9808                         }
9809                         pv[max_len+1] = '\0';
9810                     }
9811
9812                     strcat( thinkOutput, pv);
9813                 }
9814
9815                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9816                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9817                     DisplayMove(currentMove - 1);
9818                 }
9819                 return;
9820
9821             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9822                 /* crafty (9.25+) says "(only move) <move>"
9823                  * if there is only 1 legal move
9824                  */
9825                 sscanf(p, "(only move) %s", buf1);
9826                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9827                 sprintf(programStats.movelist, "%s (only move)", buf1);
9828                 programStats.depth = 1;
9829                 programStats.nr_moves = 1;
9830                 programStats.moves_left = 1;
9831                 programStats.nodes = 1;
9832                 programStats.time = 1;
9833                 programStats.got_only_move = 1;
9834
9835                 /* Not really, but we also use this member to
9836                    mean "line isn't going to change" (Crafty
9837                    isn't searching, so stats won't change) */
9838                 programStats.line_is_book = 1;
9839
9840                 SendProgramStatsToFrontend( cps, &programStats );
9841
9842                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9843                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9844                     DisplayMove(currentMove - 1);
9845                 }
9846                 return;
9847             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9848                               &time, &nodes, &plylev, &mvleft,
9849                               &mvtot, mvname) >= 5) {
9850                 /* The stat01: line is from Crafty (9.29+) in response
9851                    to the "." command */
9852                 programStats.seen_stat = 1;
9853                 cps->maybeThinking = TRUE;
9854
9855                 if (programStats.got_only_move || !appData.periodicUpdates)
9856                   return;
9857
9858                 programStats.depth = plylev;
9859                 programStats.time = time;
9860                 programStats.nodes = nodes;
9861                 programStats.moves_left = mvleft;
9862                 programStats.nr_moves = mvtot;
9863                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9864                 programStats.ok_to_send = 1;
9865                 programStats.movelist[0] = '\0';
9866
9867                 SendProgramStatsToFrontend( cps, &programStats );
9868
9869                 return;
9870
9871             } else if (strncmp(message,"++",2) == 0) {
9872                 /* Crafty 9.29+ outputs this */
9873                 programStats.got_fail = 2;
9874                 return;
9875
9876             } else if (strncmp(message,"--",2) == 0) {
9877                 /* Crafty 9.29+ outputs this */
9878                 programStats.got_fail = 1;
9879                 return;
9880
9881             } else if (thinkOutput[0] != NULLCHAR &&
9882                        strncmp(message, "    ", 4) == 0) {
9883                 unsigned message_len;
9884
9885                 p = message;
9886                 while (*p && *p == ' ') p++;
9887
9888                 message_len = strlen( p );
9889
9890                 /* [AS] Avoid buffer overflow */
9891                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9892                     strcat(thinkOutput, " ");
9893                     strcat(thinkOutput, p);
9894                 }
9895
9896                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9897                     strcat(programStats.movelist, " ");
9898                     strcat(programStats.movelist, p);
9899                 }
9900
9901                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9902                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9903                     DisplayMove(currentMove - 1);
9904                 }
9905                 return;
9906             }
9907         }
9908         else {
9909             buf1[0] = NULLCHAR;
9910
9911             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9912                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9913             {
9914                 ChessProgramStats cpstats;
9915
9916                 if (plyext != ' ' && plyext != '\t') {
9917                     time *= 100;
9918                 }
9919
9920                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9921                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9922                     curscore = -curscore;
9923                 }
9924
9925                 cpstats.depth = plylev;
9926                 cpstats.nodes = nodes;
9927                 cpstats.time = time;
9928                 cpstats.score = curscore;
9929                 cpstats.got_only_move = 0;
9930                 cpstats.movelist[0] = '\0';
9931
9932                 if (buf1[0] != NULLCHAR) {
9933                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9934                 }
9935
9936                 cpstats.ok_to_send = 0;
9937                 cpstats.line_is_book = 0;
9938                 cpstats.nr_moves = 0;
9939                 cpstats.moves_left = 0;
9940
9941                 SendProgramStatsToFrontend( cps, &cpstats );
9942             }
9943         }
9944     }
9945 }
9946
9947
9948 /* Parse a game score from the character string "game", and
9949    record it as the history of the current game.  The game
9950    score is NOT assumed to start from the standard position.
9951    The display is not updated in any way.
9952    */
9953 void
9954 ParseGameHistory (char *game)
9955 {
9956     ChessMove moveType;
9957     int fromX, fromY, toX, toY, boardIndex;
9958     char promoChar;
9959     char *p, *q;
9960     char buf[MSG_SIZ];
9961
9962     if (appData.debugMode)
9963       fprintf(debugFP, "Parsing game history: %s\n", game);
9964
9965     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9966     gameInfo.site = StrSave(appData.icsHost);
9967     gameInfo.date = PGNDate();
9968     gameInfo.round = StrSave("-");
9969
9970     /* Parse out names of players */
9971     while (*game == ' ') game++;
9972     p = buf;
9973     while (*game != ' ') *p++ = *game++;
9974     *p = NULLCHAR;
9975     gameInfo.white = StrSave(buf);
9976     while (*game == ' ') game++;
9977     p = buf;
9978     while (*game != ' ' && *game != '\n') *p++ = *game++;
9979     *p = NULLCHAR;
9980     gameInfo.black = StrSave(buf);
9981
9982     /* Parse moves */
9983     boardIndex = blackPlaysFirst ? 1 : 0;
9984     yynewstr(game);
9985     for (;;) {
9986         yyboardindex = boardIndex;
9987         moveType = (ChessMove) Myylex();
9988         switch (moveType) {
9989           case IllegalMove:             /* maybe suicide chess, etc. */
9990   if (appData.debugMode) {
9991     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9992     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9993     setbuf(debugFP, NULL);
9994   }
9995           case WhitePromotion:
9996           case BlackPromotion:
9997           case WhiteNonPromotion:
9998           case BlackNonPromotion:
9999           case NormalMove:
10000           case FirstLeg:
10001           case WhiteCapturesEnPassant:
10002           case BlackCapturesEnPassant:
10003           case WhiteKingSideCastle:
10004           case WhiteQueenSideCastle:
10005           case BlackKingSideCastle:
10006           case BlackQueenSideCastle:
10007           case WhiteKingSideCastleWild:
10008           case WhiteQueenSideCastleWild:
10009           case BlackKingSideCastleWild:
10010           case BlackQueenSideCastleWild:
10011           /* PUSH Fabien */
10012           case WhiteHSideCastleFR:
10013           case WhiteASideCastleFR:
10014           case BlackHSideCastleFR:
10015           case BlackASideCastleFR:
10016           /* POP Fabien */
10017             fromX = currentMoveString[0] - AAA;
10018             fromY = currentMoveString[1] - ONE;
10019             toX = currentMoveString[2] - AAA;
10020             toY = currentMoveString[3] - ONE;
10021             promoChar = currentMoveString[4];
10022             break;
10023           case WhiteDrop:
10024           case BlackDrop:
10025             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10026             fromX = moveType == WhiteDrop ?
10027               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10028             (int) CharToPiece(ToLower(currentMoveString[0]));
10029             fromY = DROP_RANK;
10030             toX = currentMoveString[2] - AAA;
10031             toY = currentMoveString[3] - ONE;
10032             promoChar = NULLCHAR;
10033             break;
10034           case AmbiguousMove:
10035             /* bug? */
10036             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10037   if (appData.debugMode) {
10038     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10039     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10040     setbuf(debugFP, NULL);
10041   }
10042             DisplayError(buf, 0);
10043             return;
10044           case ImpossibleMove:
10045             /* bug? */
10046             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10047   if (appData.debugMode) {
10048     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10049     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10050     setbuf(debugFP, NULL);
10051   }
10052             DisplayError(buf, 0);
10053             return;
10054           case EndOfFile:
10055             if (boardIndex < backwardMostMove) {
10056                 /* Oops, gap.  How did that happen? */
10057                 DisplayError(_("Gap in move list"), 0);
10058                 return;
10059             }
10060             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10061             if (boardIndex > forwardMostMove) {
10062                 forwardMostMove = boardIndex;
10063             }
10064             return;
10065           case ElapsedTime:
10066             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10067                 strcat(parseList[boardIndex-1], " ");
10068                 strcat(parseList[boardIndex-1], yy_text);
10069             }
10070             continue;
10071           case Comment:
10072           case PGNTag:
10073           case NAG:
10074           default:
10075             /* ignore */
10076             continue;
10077           case WhiteWins:
10078           case BlackWins:
10079           case GameIsDrawn:
10080           case GameUnfinished:
10081             if (gameMode == IcsExamining) {
10082                 if (boardIndex < backwardMostMove) {
10083                     /* Oops, gap.  How did that happen? */
10084                     return;
10085                 }
10086                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10087                 return;
10088             }
10089             gameInfo.result = moveType;
10090             p = strchr(yy_text, '{');
10091             if (p == NULL) p = strchr(yy_text, '(');
10092             if (p == NULL) {
10093                 p = yy_text;
10094                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10095             } else {
10096                 q = strchr(p, *p == '{' ? '}' : ')');
10097                 if (q != NULL) *q = NULLCHAR;
10098                 p++;
10099             }
10100             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10101             gameInfo.resultDetails = StrSave(p);
10102             continue;
10103         }
10104         if (boardIndex >= forwardMostMove &&
10105             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10106             backwardMostMove = blackPlaysFirst ? 1 : 0;
10107             return;
10108         }
10109         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10110                                  fromY, fromX, toY, toX, promoChar,
10111                                  parseList[boardIndex]);
10112         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10113         /* currentMoveString is set as a side-effect of yylex */
10114         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10115         strcat(moveList[boardIndex], "\n");
10116         boardIndex++;
10117         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10118         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10119           case MT_NONE:
10120           case MT_STALEMATE:
10121           default:
10122             break;
10123           case MT_CHECK:
10124             if(!IS_SHOGI(gameInfo.variant))
10125                 strcat(parseList[boardIndex - 1], "+");
10126             break;
10127           case MT_CHECKMATE:
10128           case MT_STAINMATE:
10129             strcat(parseList[boardIndex - 1], "#");
10130             break;
10131         }
10132     }
10133 }
10134
10135
10136 /* Apply a move to the given board  */
10137 void
10138 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10139 {
10140   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10141   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10142
10143     /* [HGM] compute & store e.p. status and castling rights for new position */
10144     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10145
10146       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10147       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10148       board[EP_STATUS] = EP_NONE;
10149       board[EP_FILE] = board[EP_RANK] = 100;
10150
10151   if (fromY == DROP_RANK) {
10152         /* must be first */
10153         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10154             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10155             return;
10156         }
10157         piece = board[toY][toX] = (ChessSquare) fromX;
10158   } else {
10159 //      ChessSquare victim;
10160       int i;
10161
10162       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10163 //           victim = board[killY][killX],
10164            killed = board[killY][killX],
10165            board[killY][killX] = EmptySquare,
10166            board[EP_STATUS] = EP_CAPTURE;
10167            if( kill2X >= 0 && kill2Y >= 0)
10168              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10169       }
10170
10171       if( board[toY][toX] != EmptySquare ) {
10172            board[EP_STATUS] = EP_CAPTURE;
10173            if( (fromX != toX || fromY != toY) && // not igui!
10174                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10175                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10176                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10177            }
10178       }
10179
10180       pawn = board[fromY][fromX];
10181       if( pawn == WhiteLance || pawn == BlackLance ) {
10182            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10183                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10184                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10185            }
10186       }
10187       if( pawn == WhitePawn ) {
10188            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10189                board[EP_STATUS] = EP_PAWN_MOVE;
10190            if( toY-fromY>=2) {
10191                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10192                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10193                         gameInfo.variant != VariantBerolina || toX < fromX)
10194                       board[EP_STATUS] = toX | berolina;
10195                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10196                         gameInfo.variant != VariantBerolina || toX > fromX)
10197                       board[EP_STATUS] = toX;
10198            }
10199       } else
10200       if( pawn == BlackPawn ) {
10201            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10202                board[EP_STATUS] = EP_PAWN_MOVE;
10203            if( toY-fromY<= -2) {
10204                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10205                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10206                         gameInfo.variant != VariantBerolina || toX < fromX)
10207                       board[EP_STATUS] = toX | berolina;
10208                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10209                         gameInfo.variant != VariantBerolina || toX > fromX)
10210                       board[EP_STATUS] = toX;
10211            }
10212        }
10213
10214        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10215        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10216        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10217        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10218
10219        for(i=0; i<nrCastlingRights; i++) {
10220            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10221               board[CASTLING][i] == toX   && castlingRank[i] == toY
10222              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10223        }
10224
10225        if(gameInfo.variant == VariantSChess) { // update virginity
10226            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10227            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10228            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10229            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10230        }
10231
10232      if (fromX == toX && fromY == toY && killX < 0) return;
10233
10234      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10235      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10236      if(gameInfo.variant == VariantKnightmate)
10237          king += (int) WhiteUnicorn - (int) WhiteKing;
10238
10239     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10240        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10241         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10242         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10243         board[EP_STATUS] = EP_NONE; // capture was fake!
10244     } else
10245     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10246         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10247         board[toY][toX] = piece;
10248         board[EP_STATUS] = EP_NONE; // capture was fake!
10249     } else
10250     /* Code added by Tord: */
10251     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10252     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10253         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10254       board[EP_STATUS] = EP_NONE; // capture was fake!
10255       board[fromY][fromX] = EmptySquare;
10256       board[toY][toX] = EmptySquare;
10257       if((toX > fromX) != (piece == WhiteRook)) {
10258         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10259       } else {
10260         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10261       }
10262     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10263                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10264       board[EP_STATUS] = EP_NONE;
10265       board[fromY][fromX] = EmptySquare;
10266       board[toY][toX] = EmptySquare;
10267       if((toX > fromX) != (piece == BlackRook)) {
10268         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10269       } else {
10270         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10271       }
10272     /* End of code added by Tord */
10273
10274     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10275         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10276         board[toY][toX] = piece;
10277     } else if (board[fromY][fromX] == king
10278         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10279         && toY == fromY && toX > fromX+1) {
10280         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10281         board[fromY][toX-1] = board[fromY][rookX];
10282         board[fromY][rookX] = EmptySquare;
10283         board[fromY][fromX] = EmptySquare;
10284         board[toY][toX] = king;
10285     } else if (board[fromY][fromX] == king
10286         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10287                && toY == fromY && toX < fromX-1) {
10288         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10289         board[fromY][toX+1] = board[fromY][rookX];
10290         board[fromY][rookX] = EmptySquare;
10291         board[fromY][fromX] = EmptySquare;
10292         board[toY][toX] = king;
10293     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10294                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10295                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10296                ) {
10297         /* white pawn promotion */
10298         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10299         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10300             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10301         board[fromY][fromX] = EmptySquare;
10302     } else if ((fromY >= BOARD_HEIGHT>>1)
10303                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10304                && (toX != fromX)
10305                && gameInfo.variant != VariantXiangqi
10306                && gameInfo.variant != VariantBerolina
10307                && (pawn == WhitePawn)
10308                && (board[toY][toX] == EmptySquare)) {
10309         board[fromY][fromX] = EmptySquare;
10310         board[toY][toX] = piece;
10311         if(toY == epRank - 128 + 1)
10312             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10313         else
10314             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10315     } else if ((fromY == BOARD_HEIGHT-4)
10316                && (toX == fromX)
10317                && gameInfo.variant == VariantBerolina
10318                && (board[fromY][fromX] == WhitePawn)
10319                && (board[toY][toX] == EmptySquare)) {
10320         board[fromY][fromX] = EmptySquare;
10321         board[toY][toX] = WhitePawn;
10322         if(oldEP & EP_BEROLIN_A) {
10323                 captured = board[fromY][fromX-1];
10324                 board[fromY][fromX-1] = EmptySquare;
10325         }else{  captured = board[fromY][fromX+1];
10326                 board[fromY][fromX+1] = EmptySquare;
10327         }
10328     } else if (board[fromY][fromX] == king
10329         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10330                && toY == fromY && toX > fromX+1) {
10331         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10332         board[fromY][toX-1] = board[fromY][rookX];
10333         board[fromY][rookX] = EmptySquare;
10334         board[fromY][fromX] = EmptySquare;
10335         board[toY][toX] = king;
10336     } else if (board[fromY][fromX] == king
10337         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10338                && toY == fromY && toX < fromX-1) {
10339         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10340         board[fromY][toX+1] = board[fromY][rookX];
10341         board[fromY][rookX] = EmptySquare;
10342         board[fromY][fromX] = EmptySquare;
10343         board[toY][toX] = king;
10344     } else if (fromY == 7 && fromX == 3
10345                && board[fromY][fromX] == BlackKing
10346                && toY == 7 && toX == 5) {
10347         board[fromY][fromX] = EmptySquare;
10348         board[toY][toX] = BlackKing;
10349         board[fromY][7] = EmptySquare;
10350         board[toY][4] = BlackRook;
10351     } else if (fromY == 7 && fromX == 3
10352                && board[fromY][fromX] == BlackKing
10353                && toY == 7 && toX == 1) {
10354         board[fromY][fromX] = EmptySquare;
10355         board[toY][toX] = BlackKing;
10356         board[fromY][0] = EmptySquare;
10357         board[toY][2] = BlackRook;
10358     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10359                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10360                && toY < promoRank && promoChar
10361                ) {
10362         /* black pawn promotion */
10363         board[toY][toX] = CharToPiece(ToLower(promoChar));
10364         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10365             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10366         board[fromY][fromX] = EmptySquare;
10367     } else if ((fromY < BOARD_HEIGHT>>1)
10368                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10369                && (toX != fromX)
10370                && gameInfo.variant != VariantXiangqi
10371                && gameInfo.variant != VariantBerolina
10372                && (pawn == BlackPawn)
10373                && (board[toY][toX] == EmptySquare)) {
10374         board[fromY][fromX] = EmptySquare;
10375         board[toY][toX] = piece;
10376         if(toY == epRank - 128 - 1)
10377             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10378         else
10379             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10380     } else if ((fromY == 3)
10381                && (toX == fromX)
10382                && gameInfo.variant == VariantBerolina
10383                && (board[fromY][fromX] == BlackPawn)
10384                && (board[toY][toX] == EmptySquare)) {
10385         board[fromY][fromX] = EmptySquare;
10386         board[toY][toX] = BlackPawn;
10387         if(oldEP & EP_BEROLIN_A) {
10388                 captured = board[fromY][fromX-1];
10389                 board[fromY][fromX-1] = EmptySquare;
10390         }else{  captured = board[fromY][fromX+1];
10391                 board[fromY][fromX+1] = EmptySquare;
10392         }
10393     } else {
10394         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10395         board[fromY][fromX] = EmptySquare;
10396         board[toY][toX] = piece;
10397     }
10398   }
10399
10400     if (gameInfo.holdingsWidth != 0) {
10401
10402       /* !!A lot more code needs to be written to support holdings  */
10403       /* [HGM] OK, so I have written it. Holdings are stored in the */
10404       /* penultimate board files, so they are automaticlly stored   */
10405       /* in the game history.                                       */
10406       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10407                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10408         /* Delete from holdings, by decreasing count */
10409         /* and erasing image if necessary            */
10410         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10411         if(p < (int) BlackPawn) { /* white drop */
10412              p -= (int)WhitePawn;
10413                  p = PieceToNumber((ChessSquare)p);
10414              if(p >= gameInfo.holdingsSize) p = 0;
10415              if(--board[p][BOARD_WIDTH-2] <= 0)
10416                   board[p][BOARD_WIDTH-1] = EmptySquare;
10417              if((int)board[p][BOARD_WIDTH-2] < 0)
10418                         board[p][BOARD_WIDTH-2] = 0;
10419         } else {                  /* black drop */
10420              p -= (int)BlackPawn;
10421                  p = PieceToNumber((ChessSquare)p);
10422              if(p >= gameInfo.holdingsSize) p = 0;
10423              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10424                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10425              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10426                         board[BOARD_HEIGHT-1-p][1] = 0;
10427         }
10428       }
10429       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10430           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10431         /* [HGM] holdings: Add to holdings, if holdings exist */
10432         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10433                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10434                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10435         }
10436         p = (int) captured;
10437         if (p >= (int) BlackPawn) {
10438           p -= (int)BlackPawn;
10439           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10440                   /* Restore shogi-promoted piece to its original  first */
10441                   captured = (ChessSquare) (DEMOTED(captured));
10442                   p = DEMOTED(p);
10443           }
10444           p = PieceToNumber((ChessSquare)p);
10445           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10446           board[p][BOARD_WIDTH-2]++;
10447           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10448         } else {
10449           p -= (int)WhitePawn;
10450           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10451                   captured = (ChessSquare) (DEMOTED(captured));
10452                   p = DEMOTED(p);
10453           }
10454           p = PieceToNumber((ChessSquare)p);
10455           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10456           board[BOARD_HEIGHT-1-p][1]++;
10457           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10458         }
10459       }
10460     } else if (gameInfo.variant == VariantAtomic) {
10461       if (captured != EmptySquare) {
10462         int y, x;
10463         for (y = toY-1; y <= toY+1; y++) {
10464           for (x = toX-1; x <= toX+1; x++) {
10465             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10466                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10467               board[y][x] = EmptySquare;
10468             }
10469           }
10470         }
10471         board[toY][toX] = EmptySquare;
10472       }
10473     }
10474
10475     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10476         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10477     } else
10478     if(promoChar == '+') {
10479         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10480         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10481         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10482           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10483     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10484         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10485         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10486            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10487         board[toY][toX] = newPiece;
10488     }
10489     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10490                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10491         // [HGM] superchess: take promotion piece out of holdings
10492         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10493         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10494             if(!--board[k][BOARD_WIDTH-2])
10495                 board[k][BOARD_WIDTH-1] = EmptySquare;
10496         } else {
10497             if(!--board[BOARD_HEIGHT-1-k][1])
10498                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10499         }
10500     }
10501 }
10502
10503 /* Updates forwardMostMove */
10504 void
10505 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10506 {
10507     int x = toX, y = toY;
10508     char *s = parseList[forwardMostMove];
10509     ChessSquare p = boards[forwardMostMove][toY][toX];
10510 //    forwardMostMove++; // [HGM] bare: moved downstream
10511
10512     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10513     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10514     (void) CoordsToAlgebraic(boards[forwardMostMove],
10515                              PosFlags(forwardMostMove),
10516                              fromY, fromX, y, x, (killX < 0)*promoChar,
10517                              s);
10518     if(kill2X >= 0 && kill2Y >= 0)
10519         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10520     if(killX >= 0 && killY >= 0)
10521         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10522                                            toX + AAA, toY + ONE - '0', promoChar);
10523
10524     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10525         int timeLeft; static int lastLoadFlag=0; int king, piece;
10526         piece = boards[forwardMostMove][fromY][fromX];
10527         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10528         if(gameInfo.variant == VariantKnightmate)
10529             king += (int) WhiteUnicorn - (int) WhiteKing;
10530         if(forwardMostMove == 0) {
10531             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10532                 fprintf(serverMoves, "%s;", UserName());
10533             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10534                 fprintf(serverMoves, "%s;", second.tidy);
10535             fprintf(serverMoves, "%s;", first.tidy);
10536             if(gameMode == MachinePlaysWhite)
10537                 fprintf(serverMoves, "%s;", UserName());
10538             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10539                 fprintf(serverMoves, "%s;", second.tidy);
10540         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10541         lastLoadFlag = loadFlag;
10542         // print base move
10543         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10544         // print castling suffix
10545         if( toY == fromY && piece == king ) {
10546             if(toX-fromX > 1)
10547                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10548             if(fromX-toX >1)
10549                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10550         }
10551         // e.p. suffix
10552         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10553              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10554              boards[forwardMostMove][toY][toX] == EmptySquare
10555              && fromX != toX && fromY != toY)
10556                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10557         // promotion suffix
10558         if(promoChar != NULLCHAR) {
10559             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10560                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10561                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10562             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10563         }
10564         if(!loadFlag) {
10565                 char buf[MOVE_LEN*2], *p; int len;
10566             fprintf(serverMoves, "/%d/%d",
10567                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10568             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10569             else                      timeLeft = blackTimeRemaining/1000;
10570             fprintf(serverMoves, "/%d", timeLeft);
10571                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10572                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10573                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10574                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10575             fprintf(serverMoves, "/%s", buf);
10576         }
10577         fflush(serverMoves);
10578     }
10579
10580     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10581         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10582       return;
10583     }
10584     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10585     if (commentList[forwardMostMove+1] != NULL) {
10586         free(commentList[forwardMostMove+1]);
10587         commentList[forwardMostMove+1] = NULL;
10588     }
10589     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10590     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10591     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10592     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10593     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10594     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10595     adjustedClock = FALSE;
10596     gameInfo.result = GameUnfinished;
10597     if (gameInfo.resultDetails != NULL) {
10598         free(gameInfo.resultDetails);
10599         gameInfo.resultDetails = NULL;
10600     }
10601     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10602                               moveList[forwardMostMove - 1]);
10603     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10604       case MT_NONE:
10605       case MT_STALEMATE:
10606       default:
10607         break;
10608       case MT_CHECK:
10609         if(!IS_SHOGI(gameInfo.variant))
10610             strcat(parseList[forwardMostMove - 1], "+");
10611         break;
10612       case MT_CHECKMATE:
10613       case MT_STAINMATE:
10614         strcat(parseList[forwardMostMove - 1], "#");
10615         break;
10616     }
10617 }
10618
10619 /* Updates currentMove if not pausing */
10620 void
10621 ShowMove (int fromX, int fromY, int toX, int toY)
10622 {
10623     int instant = (gameMode == PlayFromGameFile) ?
10624         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10625     if(appData.noGUI) return;
10626     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10627         if (!instant) {
10628             if (forwardMostMove == currentMove + 1) {
10629                 AnimateMove(boards[forwardMostMove - 1],
10630                             fromX, fromY, toX, toY);
10631             }
10632         }
10633         currentMove = forwardMostMove;
10634     }
10635
10636     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10637
10638     if (instant) return;
10639
10640     DisplayMove(currentMove - 1);
10641     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10642             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10643                 SetHighlights(fromX, fromY, toX, toY);
10644             }
10645     }
10646     DrawPosition(FALSE, boards[currentMove]);
10647     DisplayBothClocks();
10648     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10649 }
10650
10651 void
10652 SendEgtPath (ChessProgramState *cps)
10653 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10654         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10655
10656         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10657
10658         while(*p) {
10659             char c, *q = name+1, *r, *s;
10660
10661             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10662             while(*p && *p != ',') *q++ = *p++;
10663             *q++ = ':'; *q = 0;
10664             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10665                 strcmp(name, ",nalimov:") == 0 ) {
10666                 // take nalimov path from the menu-changeable option first, if it is defined
10667               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10668                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10669             } else
10670             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10671                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10672                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10673                 s = r = StrStr(s, ":") + 1; // beginning of path info
10674                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10675                 c = *r; *r = 0;             // temporarily null-terminate path info
10676                     *--q = 0;               // strip of trailig ':' from name
10677                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10678                 *r = c;
10679                 SendToProgram(buf,cps);     // send egtbpath command for this format
10680             }
10681             if(*p == ',') p++; // read away comma to position for next format name
10682         }
10683 }
10684
10685 static int
10686 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10687 {
10688       int width = 8, height = 8, holdings = 0;             // most common sizes
10689       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10690       // correct the deviations default for each variant
10691       if( v == VariantXiangqi ) width = 9,  height = 10;
10692       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10693       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10694       if( v == VariantCapablanca || v == VariantCapaRandom ||
10695           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10696                                 width = 10;
10697       if( v == VariantCourier ) width = 12;
10698       if( v == VariantSuper )                            holdings = 8;
10699       if( v == VariantGreat )   width = 10,              holdings = 8;
10700       if( v == VariantSChess )                           holdings = 7;
10701       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10702       if( v == VariantChuChess) width = 10, height = 10;
10703       if( v == VariantChu )     width = 12, height = 12;
10704       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10705              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10706              holdingsSize >= 0 && holdingsSize != holdings;
10707 }
10708
10709 char variantError[MSG_SIZ];
10710
10711 char *
10712 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10713 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10714       char *p, *variant = VariantName(v);
10715       static char b[MSG_SIZ];
10716       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10717            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10718                                                holdingsSize, variant); // cook up sized variant name
10719            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10720            if(StrStr(list, b) == NULL) {
10721                // specific sized variant not known, check if general sizing allowed
10722                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10723                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10724                             boardWidth, boardHeight, holdingsSize, engine);
10725                    return NULL;
10726                }
10727                /* [HGM] here we really should compare with the maximum supported board size */
10728            }
10729       } else snprintf(b, MSG_SIZ,"%s", variant);
10730       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10731       p = StrStr(list, b);
10732       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10733       if(p == NULL) {
10734           // occurs not at all in list, or only as sub-string
10735           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10736           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10737               int l = strlen(variantError);
10738               char *q;
10739               while(p != list && p[-1] != ',') p--;
10740               q = strchr(p, ',');
10741               if(q) *q = NULLCHAR;
10742               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10743               if(q) *q= ',';
10744           }
10745           return NULL;
10746       }
10747       return b;
10748 }
10749
10750 void
10751 InitChessProgram (ChessProgramState *cps, int setup)
10752 /* setup needed to setup FRC opening position */
10753 {
10754     char buf[MSG_SIZ], *b;
10755     if (appData.noChessProgram) return;
10756     hintRequested = FALSE;
10757     bookRequested = FALSE;
10758
10759     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10760     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10761     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10762     if(cps->memSize) { /* [HGM] memory */
10763       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10764         SendToProgram(buf, cps);
10765     }
10766     SendEgtPath(cps); /* [HGM] EGT */
10767     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10768       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10769         SendToProgram(buf, cps);
10770     }
10771
10772     setboardSpoiledMachineBlack = FALSE;
10773     SendToProgram(cps->initString, cps);
10774     if (gameInfo.variant != VariantNormal &&
10775         gameInfo.variant != VariantLoadable
10776         /* [HGM] also send variant if board size non-standard */
10777         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10778
10779       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10780                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10781       if (b == NULL) {
10782         VariantClass v;
10783         char c, *q = cps->variants, *p = strchr(q, ',');
10784         if(p) *p = NULLCHAR;
10785         v = StringToVariant(q);
10786         DisplayError(variantError, 0);
10787         if(v != VariantUnknown && cps == &first) {
10788             int w, h, s;
10789             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10790                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10791             ASSIGN(appData.variant, q);
10792             Reset(TRUE, FALSE);
10793         }
10794         if(p) *p = ',';
10795         return;
10796       }
10797
10798       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10799       SendToProgram(buf, cps);
10800     }
10801     currentlyInitializedVariant = gameInfo.variant;
10802
10803     /* [HGM] send opening position in FRC to first engine */
10804     if(setup) {
10805           SendToProgram("force\n", cps);
10806           SendBoard(cps, 0);
10807           /* engine is now in force mode! Set flag to wake it up after first move. */
10808           setboardSpoiledMachineBlack = 1;
10809     }
10810
10811     if (cps->sendICS) {
10812       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10813       SendToProgram(buf, cps);
10814     }
10815     cps->maybeThinking = FALSE;
10816     cps->offeredDraw = 0;
10817     if (!appData.icsActive) {
10818         SendTimeControl(cps, movesPerSession, timeControl,
10819                         timeIncrement, appData.searchDepth,
10820                         searchTime);
10821     }
10822     if (appData.showThinking
10823         // [HGM] thinking: four options require thinking output to be sent
10824         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10825                                 ) {
10826         SendToProgram("post\n", cps);
10827     }
10828     SendToProgram("hard\n", cps);
10829     if (!appData.ponderNextMove) {
10830         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10831            it without being sure what state we are in first.  "hard"
10832            is not a toggle, so that one is OK.
10833          */
10834         SendToProgram("easy\n", cps);
10835     }
10836     if (cps->usePing) {
10837       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10838       SendToProgram(buf, cps);
10839     }
10840     cps->initDone = TRUE;
10841     ClearEngineOutputPane(cps == &second);
10842 }
10843
10844
10845 void
10846 ResendOptions (ChessProgramState *cps)
10847 { // send the stored value of the options
10848   int i;
10849   char buf[MSG_SIZ];
10850   Option *opt = cps->option;
10851   for(i=0; i<cps->nrOptions; i++, opt++) {
10852       switch(opt->type) {
10853         case Spin:
10854         case Slider:
10855         case CheckBox:
10856             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10857           break;
10858         case ComboBox:
10859           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10860           break;
10861         default:
10862             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10863           break;
10864         case Button:
10865         case SaveButton:
10866           continue;
10867       }
10868       SendToProgram(buf, cps);
10869   }
10870 }
10871
10872 void
10873 StartChessProgram (ChessProgramState *cps)
10874 {
10875     char buf[MSG_SIZ];
10876     int err;
10877
10878     if (appData.noChessProgram) return;
10879     cps->initDone = FALSE;
10880
10881     if (strcmp(cps->host, "localhost") == 0) {
10882         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10883     } else if (*appData.remoteShell == NULLCHAR) {
10884         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10885     } else {
10886         if (*appData.remoteUser == NULLCHAR) {
10887           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10888                     cps->program);
10889         } else {
10890           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10891                     cps->host, appData.remoteUser, cps->program);
10892         }
10893         err = StartChildProcess(buf, "", &cps->pr);
10894     }
10895
10896     if (err != 0) {
10897       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10898         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10899         if(cps != &first) return;
10900         appData.noChessProgram = TRUE;
10901         ThawUI();
10902         SetNCPMode();
10903 //      DisplayFatalError(buf, err, 1);
10904 //      cps->pr = NoProc;
10905 //      cps->isr = NULL;
10906         return;
10907     }
10908
10909     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10910     if (cps->protocolVersion > 1) {
10911       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10912       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10913         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10914         cps->comboCnt = 0;  //                and values of combo boxes
10915       }
10916       SendToProgram(buf, cps);
10917       if(cps->reload) ResendOptions(cps);
10918     } else {
10919       SendToProgram("xboard\n", cps);
10920     }
10921 }
10922
10923 void
10924 TwoMachinesEventIfReady P((void))
10925 {
10926   static int curMess = 0;
10927   if (first.lastPing != first.lastPong) {
10928     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10929     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10930     return;
10931   }
10932   if (second.lastPing != second.lastPong) {
10933     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10934     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10935     return;
10936   }
10937   DisplayMessage("", ""); curMess = 0;
10938   TwoMachinesEvent();
10939 }
10940
10941 char *
10942 MakeName (char *template)
10943 {
10944     time_t clock;
10945     struct tm *tm;
10946     static char buf[MSG_SIZ];
10947     char *p = buf;
10948     int i;
10949
10950     clock = time((time_t *)NULL);
10951     tm = localtime(&clock);
10952
10953     while(*p++ = *template++) if(p[-1] == '%') {
10954         switch(*template++) {
10955           case 0:   *p = 0; return buf;
10956           case 'Y': i = tm->tm_year+1900; break;
10957           case 'y': i = tm->tm_year-100; break;
10958           case 'M': i = tm->tm_mon+1; break;
10959           case 'd': i = tm->tm_mday; break;
10960           case 'h': i = tm->tm_hour; break;
10961           case 'm': i = tm->tm_min; break;
10962           case 's': i = tm->tm_sec; break;
10963           default:  i = 0;
10964         }
10965         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10966     }
10967     return buf;
10968 }
10969
10970 int
10971 CountPlayers (char *p)
10972 {
10973     int n = 0;
10974     while(p = strchr(p, '\n')) p++, n++; // count participants
10975     return n;
10976 }
10977
10978 FILE *
10979 WriteTourneyFile (char *results, FILE *f)
10980 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10981     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10982     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10983         // create a file with tournament description
10984         fprintf(f, "-participants {%s}\n", appData.participants);
10985         fprintf(f, "-seedBase %d\n", appData.seedBase);
10986         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10987         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10988         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10989         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10990         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10991         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10992         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10993         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10994         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10995         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10996         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10997         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10998         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10999         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11000         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11001         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11002         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11003         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11004         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11005         fprintf(f, "-smpCores %d\n", appData.smpCores);
11006         if(searchTime > 0)
11007                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11008         else {
11009                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11010                 fprintf(f, "-tc %s\n", appData.timeControl);
11011                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11012         }
11013         fprintf(f, "-results \"%s\"\n", results);
11014     }
11015     return f;
11016 }
11017
11018 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11019
11020 void
11021 Substitute (char *participants, int expunge)
11022 {
11023     int i, changed, changes=0, nPlayers=0;
11024     char *p, *q, *r, buf[MSG_SIZ];
11025     if(participants == NULL) return;
11026     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11027     r = p = participants; q = appData.participants;
11028     while(*p && *p == *q) {
11029         if(*p == '\n') r = p+1, nPlayers++;
11030         p++; q++;
11031     }
11032     if(*p) { // difference
11033         while(*p && *p++ != '\n');
11034         while(*q && *q++ != '\n');
11035       changed = nPlayers;
11036         changes = 1 + (strcmp(p, q) != 0);
11037     }
11038     if(changes == 1) { // a single engine mnemonic was changed
11039         q = r; while(*q) nPlayers += (*q++ == '\n');
11040         p = buf; while(*r && (*p = *r++) != '\n') p++;
11041         *p = NULLCHAR;
11042         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11043         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11044         if(mnemonic[i]) { // The substitute is valid
11045             FILE *f;
11046             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11047                 flock(fileno(f), LOCK_EX);
11048                 ParseArgsFromFile(f);
11049                 fseek(f, 0, SEEK_SET);
11050                 FREE(appData.participants); appData.participants = participants;
11051                 if(expunge) { // erase results of replaced engine
11052                     int len = strlen(appData.results), w, b, dummy;
11053                     for(i=0; i<len; i++) {
11054                         Pairing(i, nPlayers, &w, &b, &dummy);
11055                         if((w == changed || b == changed) && appData.results[i] == '*') {
11056                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11057                             fclose(f);
11058                             return;
11059                         }
11060                     }
11061                     for(i=0; i<len; i++) {
11062                         Pairing(i, nPlayers, &w, &b, &dummy);
11063                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11064                     }
11065                 }
11066                 WriteTourneyFile(appData.results, f);
11067                 fclose(f); // release lock
11068                 return;
11069             }
11070         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11071     }
11072     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11073     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11074     free(participants);
11075     return;
11076 }
11077
11078 int
11079 CheckPlayers (char *participants)
11080 {
11081         int i;
11082         char buf[MSG_SIZ], *p;
11083         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11084         while(p = strchr(participants, '\n')) {
11085             *p = NULLCHAR;
11086             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11087             if(!mnemonic[i]) {
11088                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11089                 *p = '\n';
11090                 DisplayError(buf, 0);
11091                 return 1;
11092             }
11093             *p = '\n';
11094             participants = p + 1;
11095         }
11096         return 0;
11097 }
11098
11099 int
11100 CreateTourney (char *name)
11101 {
11102         FILE *f;
11103         if(matchMode && strcmp(name, appData.tourneyFile)) {
11104              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11105         }
11106         if(name[0] == NULLCHAR) {
11107             if(appData.participants[0])
11108                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11109             return 0;
11110         }
11111         f = fopen(name, "r");
11112         if(f) { // file exists
11113             ASSIGN(appData.tourneyFile, name);
11114             ParseArgsFromFile(f); // parse it
11115         } else {
11116             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11117             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11118                 DisplayError(_("Not enough participants"), 0);
11119                 return 0;
11120             }
11121             if(CheckPlayers(appData.participants)) return 0;
11122             ASSIGN(appData.tourneyFile, name);
11123             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11124             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11125         }
11126         fclose(f);
11127         appData.noChessProgram = FALSE;
11128         appData.clockMode = TRUE;
11129         SetGNUMode();
11130         return 1;
11131 }
11132
11133 int
11134 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11135 {
11136     char buf[MSG_SIZ], *p, *q;
11137     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11138     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11139     skip = !all && group[0]; // if group requested, we start in skip mode
11140     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11141         p = names; q = buf; header = 0;
11142         while(*p && *p != '\n') *q++ = *p++;
11143         *q = 0;
11144         if(*p == '\n') p++;
11145         if(buf[0] == '#') {
11146             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11147             depth++; // we must be entering a new group
11148             if(all) continue; // suppress printing group headers when complete list requested
11149             header = 1;
11150             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11151         }
11152         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11153         if(engineList[i]) free(engineList[i]);
11154         engineList[i] = strdup(buf);
11155         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11156         if(engineMnemonic[i]) free(engineMnemonic[i]);
11157         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11158             strcat(buf, " (");
11159             sscanf(q + 8, "%s", buf + strlen(buf));
11160             strcat(buf, ")");
11161         }
11162         engineMnemonic[i] = strdup(buf);
11163         i++;
11164     }
11165     engineList[i] = engineMnemonic[i] = NULL;
11166     return i;
11167 }
11168
11169 // following implemented as macro to avoid type limitations
11170 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11171
11172 void
11173 SwapEngines (int n)
11174 {   // swap settings for first engine and other engine (so far only some selected options)
11175     int h;
11176     char *p;
11177     if(n == 0) return;
11178     SWAP(directory, p)
11179     SWAP(chessProgram, p)
11180     SWAP(isUCI, h)
11181     SWAP(hasOwnBookUCI, h)
11182     SWAP(protocolVersion, h)
11183     SWAP(reuse, h)
11184     SWAP(scoreIsAbsolute, h)
11185     SWAP(timeOdds, h)
11186     SWAP(logo, p)
11187     SWAP(pgnName, p)
11188     SWAP(pvSAN, h)
11189     SWAP(engOptions, p)
11190     SWAP(engInitString, p)
11191     SWAP(computerString, p)
11192     SWAP(features, p)
11193     SWAP(fenOverride, p)
11194     SWAP(NPS, h)
11195     SWAP(accumulateTC, h)
11196     SWAP(drawDepth, h)
11197     SWAP(host, p)
11198     SWAP(pseudo, h)
11199 }
11200
11201 int
11202 GetEngineLine (char *s, int n)
11203 {
11204     int i;
11205     char buf[MSG_SIZ];
11206     extern char *icsNames;
11207     if(!s || !*s) return 0;
11208     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11209     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11210     if(!mnemonic[i]) return 0;
11211     if(n == 11) return 1; // just testing if there was a match
11212     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11213     if(n == 1) SwapEngines(n);
11214     ParseArgsFromString(buf);
11215     if(n == 1) SwapEngines(n);
11216     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11217         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11218         ParseArgsFromString(buf);
11219     }
11220     return 1;
11221 }
11222
11223 int
11224 SetPlayer (int player, char *p)
11225 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11226     int i;
11227     char buf[MSG_SIZ], *engineName;
11228     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11229     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11230     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11231     if(mnemonic[i]) {
11232         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11233         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11234         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11235         ParseArgsFromString(buf);
11236     } else { // no engine with this nickname is installed!
11237         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11238         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11239         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11240         ModeHighlight();
11241         DisplayError(buf, 0);
11242         return 0;
11243     }
11244     free(engineName);
11245     return i;
11246 }
11247
11248 char *recentEngines;
11249
11250 void
11251 RecentEngineEvent (int nr)
11252 {
11253     int n;
11254 //    SwapEngines(1); // bump first to second
11255 //    ReplaceEngine(&second, 1); // and load it there
11256     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11257     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11258     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11259         ReplaceEngine(&first, 0);
11260         FloatToFront(&appData.recentEngineList, command[n]);
11261     }
11262 }
11263
11264 int
11265 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11266 {   // determine players from game number
11267     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11268
11269     if(appData.tourneyType == 0) {
11270         roundsPerCycle = (nPlayers - 1) | 1;
11271         pairingsPerRound = nPlayers / 2;
11272     } else if(appData.tourneyType > 0) {
11273         roundsPerCycle = nPlayers - appData.tourneyType;
11274         pairingsPerRound = appData.tourneyType;
11275     }
11276     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11277     gamesPerCycle = gamesPerRound * roundsPerCycle;
11278     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11279     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11280     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11281     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11282     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11283     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11284
11285     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11286     if(appData.roundSync) *syncInterval = gamesPerRound;
11287
11288     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11289
11290     if(appData.tourneyType == 0) {
11291         if(curPairing == (nPlayers-1)/2 ) {
11292             *whitePlayer = curRound;
11293             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11294         } else {
11295             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11296             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11297             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11298             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11299         }
11300     } else if(appData.tourneyType > 1) {
11301         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11302         *whitePlayer = curRound + appData.tourneyType;
11303     } else if(appData.tourneyType > 0) {
11304         *whitePlayer = curPairing;
11305         *blackPlayer = curRound + appData.tourneyType;
11306     }
11307
11308     // take care of white/black alternation per round.
11309     // For cycles and games this is already taken care of by default, derived from matchGame!
11310     return curRound & 1;
11311 }
11312
11313 int
11314 NextTourneyGame (int nr, int *swapColors)
11315 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11316     char *p, *q;
11317     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11318     FILE *tf;
11319     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11320     tf = fopen(appData.tourneyFile, "r");
11321     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11322     ParseArgsFromFile(tf); fclose(tf);
11323     InitTimeControls(); // TC might be altered from tourney file
11324
11325     nPlayers = CountPlayers(appData.participants); // count participants
11326     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11327     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11328
11329     if(syncInterval) {
11330         p = q = appData.results;
11331         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11332         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11333             DisplayMessage(_("Waiting for other game(s)"),"");
11334             waitingForGame = TRUE;
11335             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11336             return 0;
11337         }
11338         waitingForGame = FALSE;
11339     }
11340
11341     if(appData.tourneyType < 0) {
11342         if(nr>=0 && !pairingReceived) {
11343             char buf[1<<16];
11344             if(pairing.pr == NoProc) {
11345                 if(!appData.pairingEngine[0]) {
11346                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11347                     return 0;
11348                 }
11349                 StartChessProgram(&pairing); // starts the pairing engine
11350             }
11351             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11352             SendToProgram(buf, &pairing);
11353             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11354             SendToProgram(buf, &pairing);
11355             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11356         }
11357         pairingReceived = 0;                              // ... so we continue here
11358         *swapColors = 0;
11359         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11360         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11361         matchGame = 1; roundNr = nr / syncInterval + 1;
11362     }
11363
11364     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11365
11366     // redefine engines, engine dir, etc.
11367     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11368     if(first.pr == NoProc) {
11369       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11370       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11371     }
11372     if(second.pr == NoProc) {
11373       SwapEngines(1);
11374       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11375       SwapEngines(1);         // and make that valid for second engine by swapping
11376       InitEngine(&second, 1);
11377     }
11378     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11379     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11380     return OK;
11381 }
11382
11383 void
11384 NextMatchGame ()
11385 {   // performs game initialization that does not invoke engines, and then tries to start the game
11386     int res, firstWhite, swapColors = 0;
11387     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11388     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
11389         char buf[MSG_SIZ];
11390         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11391         if(strcmp(buf, currentDebugFile)) { // name has changed
11392             FILE *f = fopen(buf, "w");
11393             if(f) { // if opening the new file failed, just keep using the old one
11394                 ASSIGN(currentDebugFile, buf);
11395                 fclose(debugFP);
11396                 debugFP = f;
11397             }
11398             if(appData.serverFileName) {
11399                 if(serverFP) fclose(serverFP);
11400                 serverFP = fopen(appData.serverFileName, "w");
11401                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11402                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11403             }
11404         }
11405     }
11406     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11407     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11408     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11409     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11410     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11411     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11412     Reset(FALSE, first.pr != NoProc);
11413     res = LoadGameOrPosition(matchGame); // setup game
11414     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11415     if(!res) return; // abort when bad game/pos file
11416     TwoMachinesEvent();
11417 }
11418
11419 void
11420 UserAdjudicationEvent (int result)
11421 {
11422     ChessMove gameResult = GameIsDrawn;
11423
11424     if( result > 0 ) {
11425         gameResult = WhiteWins;
11426     }
11427     else if( result < 0 ) {
11428         gameResult = BlackWins;
11429     }
11430
11431     if( gameMode == TwoMachinesPlay ) {
11432         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11433     }
11434 }
11435
11436
11437 // [HGM] save: calculate checksum of game to make games easily identifiable
11438 int
11439 StringCheckSum (char *s)
11440 {
11441         int i = 0;
11442         if(s==NULL) return 0;
11443         while(*s) i = i*259 + *s++;
11444         return i;
11445 }
11446
11447 int
11448 GameCheckSum ()
11449 {
11450         int i, sum=0;
11451         for(i=backwardMostMove; i<forwardMostMove; i++) {
11452                 sum += pvInfoList[i].depth;
11453                 sum += StringCheckSum(parseList[i]);
11454                 sum += StringCheckSum(commentList[i]);
11455                 sum *= 261;
11456         }
11457         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11458         return sum + StringCheckSum(commentList[i]);
11459 } // end of save patch
11460
11461 void
11462 GameEnds (ChessMove result, char *resultDetails, int whosays)
11463 {
11464     GameMode nextGameMode;
11465     int isIcsGame;
11466     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11467
11468     if(endingGame) return; /* [HGM] crash: forbid recursion */
11469     endingGame = 1;
11470     if(twoBoards) { // [HGM] dual: switch back to one board
11471         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11472         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11473     }
11474     if (appData.debugMode) {
11475       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11476               result, resultDetails ? resultDetails : "(null)", whosays);
11477     }
11478
11479     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11480
11481     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11482
11483     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11484         /* If we are playing on ICS, the server decides when the
11485            game is over, but the engine can offer to draw, claim
11486            a draw, or resign.
11487          */
11488 #if ZIPPY
11489         if (appData.zippyPlay && first.initDone) {
11490             if (result == GameIsDrawn) {
11491                 /* In case draw still needs to be claimed */
11492                 SendToICS(ics_prefix);
11493                 SendToICS("draw\n");
11494             } else if (StrCaseStr(resultDetails, "resign")) {
11495                 SendToICS(ics_prefix);
11496                 SendToICS("resign\n");
11497             }
11498         }
11499 #endif
11500         endingGame = 0; /* [HGM] crash */
11501         return;
11502     }
11503
11504     /* If we're loading the game from a file, stop */
11505     if (whosays == GE_FILE) {
11506       (void) StopLoadGameTimer();
11507       gameFileFP = NULL;
11508     }
11509
11510     /* Cancel draw offers */
11511     first.offeredDraw = second.offeredDraw = 0;
11512
11513     /* If this is an ICS game, only ICS can really say it's done;
11514        if not, anyone can. */
11515     isIcsGame = (gameMode == IcsPlayingWhite ||
11516                  gameMode == IcsPlayingBlack ||
11517                  gameMode == IcsObserving    ||
11518                  gameMode == IcsExamining);
11519
11520     if (!isIcsGame || whosays == GE_ICS) {
11521         /* OK -- not an ICS game, or ICS said it was done */
11522         StopClocks();
11523         if (!isIcsGame && !appData.noChessProgram)
11524           SetUserThinkingEnables();
11525
11526         /* [HGM] if a machine claims the game end we verify this claim */
11527         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11528             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11529                 char claimer;
11530                 ChessMove trueResult = (ChessMove) -1;
11531
11532                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11533                                             first.twoMachinesColor[0] :
11534                                             second.twoMachinesColor[0] ;
11535
11536                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11537                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11538                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11539                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11540                 } else
11541                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11542                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11543                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11544                 } else
11545                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11546                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11547                 }
11548
11549                 // now verify win claims, but not in drop games, as we don't understand those yet
11550                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11551                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11552                     (result == WhiteWins && claimer == 'w' ||
11553                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11554                       if (appData.debugMode) {
11555                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11556                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11557                       }
11558                       if(result != trueResult) {
11559                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11560                               result = claimer == 'w' ? BlackWins : WhiteWins;
11561                               resultDetails = buf;
11562                       }
11563                 } else
11564                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11565                     && (forwardMostMove <= backwardMostMove ||
11566                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11567                         (claimer=='b')==(forwardMostMove&1))
11568                                                                                   ) {
11569                       /* [HGM] verify: draws that were not flagged are false claims */
11570                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11571                       result = claimer == 'w' ? BlackWins : WhiteWins;
11572                       resultDetails = buf;
11573                 }
11574                 /* (Claiming a loss is accepted no questions asked!) */
11575             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11576                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11577                 result = GameUnfinished;
11578                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11579             }
11580             /* [HGM] bare: don't allow bare King to win */
11581             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11582                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11583                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11584                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11585                && result != GameIsDrawn)
11586             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11587                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11588                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11589                         if(p >= 0 && p <= (int)WhiteKing) k++;
11590                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11591                 }
11592                 if (appData.debugMode) {
11593                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11594                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11595                 }
11596                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11597                         result = GameIsDrawn;
11598                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11599                         resultDetails = buf;
11600                 }
11601             }
11602         }
11603
11604
11605         if(serverMoves != NULL && !loadFlag) { char c = '=';
11606             if(result==WhiteWins) c = '+';
11607             if(result==BlackWins) c = '-';
11608             if(resultDetails != NULL)
11609                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11610         }
11611         if (resultDetails != NULL) {
11612             gameInfo.result = result;
11613             gameInfo.resultDetails = StrSave(resultDetails);
11614
11615             /* display last move only if game was not loaded from file */
11616             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11617                 DisplayMove(currentMove - 1);
11618
11619             if (forwardMostMove != 0) {
11620                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11621                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11622                                                                 ) {
11623                     if (*appData.saveGameFile != NULLCHAR) {
11624                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11625                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11626                         else
11627                         SaveGameToFile(appData.saveGameFile, TRUE);
11628                     } else if (appData.autoSaveGames) {
11629                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11630                     }
11631                     if (*appData.savePositionFile != NULLCHAR) {
11632                         SavePositionToFile(appData.savePositionFile);
11633                     }
11634                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11635                 }
11636             }
11637
11638             /* Tell program how game ended in case it is learning */
11639             /* [HGM] Moved this to after saving the PGN, just in case */
11640             /* engine died and we got here through time loss. In that */
11641             /* case we will get a fatal error writing the pipe, which */
11642             /* would otherwise lose us the PGN.                       */
11643             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11644             /* output during GameEnds should never be fatal anymore   */
11645             if (gameMode == MachinePlaysWhite ||
11646                 gameMode == MachinePlaysBlack ||
11647                 gameMode == TwoMachinesPlay ||
11648                 gameMode == IcsPlayingWhite ||
11649                 gameMode == IcsPlayingBlack ||
11650                 gameMode == BeginningOfGame) {
11651                 char buf[MSG_SIZ];
11652                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11653                         resultDetails);
11654                 if (first.pr != NoProc) {
11655                     SendToProgram(buf, &first);
11656                 }
11657                 if (second.pr != NoProc &&
11658                     gameMode == TwoMachinesPlay) {
11659                     SendToProgram(buf, &second);
11660                 }
11661             }
11662         }
11663
11664         if (appData.icsActive) {
11665             if (appData.quietPlay &&
11666                 (gameMode == IcsPlayingWhite ||
11667                  gameMode == IcsPlayingBlack)) {
11668                 SendToICS(ics_prefix);
11669                 SendToICS("set shout 1\n");
11670             }
11671             nextGameMode = IcsIdle;
11672             ics_user_moved = FALSE;
11673             /* clean up premove.  It's ugly when the game has ended and the
11674              * premove highlights are still on the board.
11675              */
11676             if (gotPremove) {
11677               gotPremove = FALSE;
11678               ClearPremoveHighlights();
11679               DrawPosition(FALSE, boards[currentMove]);
11680             }
11681             if (whosays == GE_ICS) {
11682                 switch (result) {
11683                 case WhiteWins:
11684                     if (gameMode == IcsPlayingWhite)
11685                         PlayIcsWinSound();
11686                     else if(gameMode == IcsPlayingBlack)
11687                         PlayIcsLossSound();
11688                     break;
11689                 case BlackWins:
11690                     if (gameMode == IcsPlayingBlack)
11691                         PlayIcsWinSound();
11692                     else if(gameMode == IcsPlayingWhite)
11693                         PlayIcsLossSound();
11694                     break;
11695                 case GameIsDrawn:
11696                     PlayIcsDrawSound();
11697                     break;
11698                 default:
11699                     PlayIcsUnfinishedSound();
11700                 }
11701             }
11702             if(appData.quitNext) { ExitEvent(0); return; }
11703         } else if (gameMode == EditGame ||
11704                    gameMode == PlayFromGameFile ||
11705                    gameMode == AnalyzeMode ||
11706                    gameMode == AnalyzeFile) {
11707             nextGameMode = gameMode;
11708         } else {
11709             nextGameMode = EndOfGame;
11710         }
11711         pausing = FALSE;
11712         ModeHighlight();
11713     } else {
11714         nextGameMode = gameMode;
11715     }
11716
11717     if (appData.noChessProgram) {
11718         gameMode = nextGameMode;
11719         ModeHighlight();
11720         endingGame = 0; /* [HGM] crash */
11721         return;
11722     }
11723
11724     if (first.reuse) {
11725         /* Put first chess program into idle state */
11726         if (first.pr != NoProc &&
11727             (gameMode == MachinePlaysWhite ||
11728              gameMode == MachinePlaysBlack ||
11729              gameMode == TwoMachinesPlay ||
11730              gameMode == IcsPlayingWhite ||
11731              gameMode == IcsPlayingBlack ||
11732              gameMode == BeginningOfGame)) {
11733             SendToProgram("force\n", &first);
11734             if (first.usePing) {
11735               char buf[MSG_SIZ];
11736               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11737               SendToProgram(buf, &first);
11738             }
11739         }
11740     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11741         /* Kill off first chess program */
11742         if (first.isr != NULL)
11743           RemoveInputSource(first.isr);
11744         first.isr = NULL;
11745
11746         if (first.pr != NoProc) {
11747             ExitAnalyzeMode();
11748             DoSleep( appData.delayBeforeQuit );
11749             SendToProgram("quit\n", &first);
11750             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11751             first.reload = TRUE;
11752         }
11753         first.pr = NoProc;
11754     }
11755     if (second.reuse) {
11756         /* Put second chess program into idle state */
11757         if (second.pr != NoProc &&
11758             gameMode == TwoMachinesPlay) {
11759             SendToProgram("force\n", &second);
11760             if (second.usePing) {
11761               char buf[MSG_SIZ];
11762               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11763               SendToProgram(buf, &second);
11764             }
11765         }
11766     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11767         /* Kill off second chess program */
11768         if (second.isr != NULL)
11769           RemoveInputSource(second.isr);
11770         second.isr = NULL;
11771
11772         if (second.pr != NoProc) {
11773             DoSleep( appData.delayBeforeQuit );
11774             SendToProgram("quit\n", &second);
11775             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11776             second.reload = TRUE;
11777         }
11778         second.pr = NoProc;
11779     }
11780
11781     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11782         char resChar = '=';
11783         switch (result) {
11784         case WhiteWins:
11785           resChar = '+';
11786           if (first.twoMachinesColor[0] == 'w') {
11787             first.matchWins++;
11788           } else {
11789             second.matchWins++;
11790           }
11791           break;
11792         case BlackWins:
11793           resChar = '-';
11794           if (first.twoMachinesColor[0] == 'b') {
11795             first.matchWins++;
11796           } else {
11797             second.matchWins++;
11798           }
11799           break;
11800         case GameUnfinished:
11801           resChar = ' ';
11802         default:
11803           break;
11804         }
11805
11806         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11807         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11808             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11809             ReserveGame(nextGame, resChar); // sets nextGame
11810             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11811             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11812         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11813
11814         if (nextGame <= appData.matchGames && !abortMatch) {
11815             gameMode = nextGameMode;
11816             matchGame = nextGame; // this will be overruled in tourney mode!
11817             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11818             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11819             endingGame = 0; /* [HGM] crash */
11820             return;
11821         } else {
11822             gameMode = nextGameMode;
11823             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11824                      first.tidy, second.tidy,
11825                      first.matchWins, second.matchWins,
11826                      appData.matchGames - (first.matchWins + second.matchWins));
11827             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11828             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11829             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11830             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11831                 first.twoMachinesColor = "black\n";
11832                 second.twoMachinesColor = "white\n";
11833             } else {
11834                 first.twoMachinesColor = "white\n";
11835                 second.twoMachinesColor = "black\n";
11836             }
11837         }
11838     }
11839     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11840         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11841       ExitAnalyzeMode();
11842     gameMode = nextGameMode;
11843     ModeHighlight();
11844     endingGame = 0;  /* [HGM] crash */
11845     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11846         if(matchMode == TRUE) { // match through command line: exit with or without popup
11847             if(ranking) {
11848                 ToNrEvent(forwardMostMove);
11849                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11850                 else ExitEvent(0);
11851             } else DisplayFatalError(buf, 0, 0);
11852         } else { // match through menu; just stop, with or without popup
11853             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11854             ModeHighlight();
11855             if(ranking){
11856                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11857             } else DisplayNote(buf);
11858       }
11859       if(ranking) free(ranking);
11860     }
11861 }
11862
11863 /* Assumes program was just initialized (initString sent).
11864    Leaves program in force mode. */
11865 void
11866 FeedMovesToProgram (ChessProgramState *cps, int upto)
11867 {
11868     int i;
11869
11870     if (appData.debugMode)
11871       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11872               startedFromSetupPosition ? "position and " : "",
11873               backwardMostMove, upto, cps->which);
11874     if(currentlyInitializedVariant != gameInfo.variant) {
11875       char buf[MSG_SIZ];
11876         // [HGM] variantswitch: make engine aware of new variant
11877         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11878                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11879                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11880         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11881         SendToProgram(buf, cps);
11882         currentlyInitializedVariant = gameInfo.variant;
11883     }
11884     SendToProgram("force\n", cps);
11885     if (startedFromSetupPosition) {
11886         SendBoard(cps, backwardMostMove);
11887     if (appData.debugMode) {
11888         fprintf(debugFP, "feedMoves\n");
11889     }
11890     }
11891     for (i = backwardMostMove; i < upto; i++) {
11892         SendMoveToProgram(i, cps);
11893     }
11894 }
11895
11896
11897 int
11898 ResurrectChessProgram ()
11899 {
11900      /* The chess program may have exited.
11901         If so, restart it and feed it all the moves made so far. */
11902     static int doInit = 0;
11903
11904     if (appData.noChessProgram) return 1;
11905
11906     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11907         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11908         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11909         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11910     } else {
11911         if (first.pr != NoProc) return 1;
11912         StartChessProgram(&first);
11913     }
11914     InitChessProgram(&first, FALSE);
11915     FeedMovesToProgram(&first, currentMove);
11916
11917     if (!first.sendTime) {
11918         /* can't tell gnuchess what its clock should read,
11919            so we bow to its notion. */
11920         ResetClocks();
11921         timeRemaining[0][currentMove] = whiteTimeRemaining;
11922         timeRemaining[1][currentMove] = blackTimeRemaining;
11923     }
11924
11925     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11926                 appData.icsEngineAnalyze) && first.analysisSupport) {
11927       SendToProgram("analyze\n", &first);
11928       first.analyzing = TRUE;
11929     }
11930     return 1;
11931 }
11932
11933 /*
11934  * Button procedures
11935  */
11936 void
11937 Reset (int redraw, int init)
11938 {
11939     int i;
11940
11941     if (appData.debugMode) {
11942         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11943                 redraw, init, gameMode);
11944     }
11945     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11946     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11947     CleanupTail(); // [HGM] vari: delete any stored variations
11948     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11949     pausing = pauseExamInvalid = FALSE;
11950     startedFromSetupPosition = blackPlaysFirst = FALSE;
11951     firstMove = TRUE;
11952     whiteFlag = blackFlag = FALSE;
11953     userOfferedDraw = FALSE;
11954     hintRequested = bookRequested = FALSE;
11955     first.maybeThinking = FALSE;
11956     second.maybeThinking = FALSE;
11957     first.bookSuspend = FALSE; // [HGM] book
11958     second.bookSuspend = FALSE;
11959     thinkOutput[0] = NULLCHAR;
11960     lastHint[0] = NULLCHAR;
11961     ClearGameInfo(&gameInfo);
11962     gameInfo.variant = StringToVariant(appData.variant);
11963     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11964     ics_user_moved = ics_clock_paused = FALSE;
11965     ics_getting_history = H_FALSE;
11966     ics_gamenum = -1;
11967     white_holding[0] = black_holding[0] = NULLCHAR;
11968     ClearProgramStats();
11969     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11970
11971     ResetFrontEnd();
11972     ClearHighlights();
11973     flipView = appData.flipView;
11974     ClearPremoveHighlights();
11975     gotPremove = FALSE;
11976     alarmSounded = FALSE;
11977     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11978
11979     GameEnds(EndOfFile, NULL, GE_PLAYER);
11980     if(appData.serverMovesName != NULL) {
11981         /* [HGM] prepare to make moves file for broadcasting */
11982         clock_t t = clock();
11983         if(serverMoves != NULL) fclose(serverMoves);
11984         serverMoves = fopen(appData.serverMovesName, "r");
11985         if(serverMoves != NULL) {
11986             fclose(serverMoves);
11987             /* delay 15 sec before overwriting, so all clients can see end */
11988             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11989         }
11990         serverMoves = fopen(appData.serverMovesName, "w");
11991     }
11992
11993     ExitAnalyzeMode();
11994     gameMode = BeginningOfGame;
11995     ModeHighlight();
11996     if(appData.icsActive) gameInfo.variant = VariantNormal;
11997     currentMove = forwardMostMove = backwardMostMove = 0;
11998     MarkTargetSquares(1);
11999     InitPosition(redraw);
12000     for (i = 0; i < MAX_MOVES; i++) {
12001         if (commentList[i] != NULL) {
12002             free(commentList[i]);
12003             commentList[i] = NULL;
12004         }
12005     }
12006     ResetClocks();
12007     timeRemaining[0][0] = whiteTimeRemaining;
12008     timeRemaining[1][0] = blackTimeRemaining;
12009
12010     if (first.pr == NoProc) {
12011         StartChessProgram(&first);
12012     }
12013     if (init) {
12014             InitChessProgram(&first, startedFromSetupPosition);
12015     }
12016     DisplayTitle("");
12017     DisplayMessage("", "");
12018     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12019     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12020     ClearMap();        // [HGM] exclude: invalidate map
12021 }
12022
12023 void
12024 AutoPlayGameLoop ()
12025 {
12026     for (;;) {
12027         if (!AutoPlayOneMove())
12028           return;
12029         if (matchMode || appData.timeDelay == 0)
12030           continue;
12031         if (appData.timeDelay < 0)
12032           return;
12033         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12034         break;
12035     }
12036 }
12037
12038 void
12039 AnalyzeNextGame()
12040 {
12041     ReloadGame(1); // next game
12042 }
12043
12044 int
12045 AutoPlayOneMove ()
12046 {
12047     int fromX, fromY, toX, toY;
12048
12049     if (appData.debugMode) {
12050       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12051     }
12052
12053     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12054       return FALSE;
12055
12056     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12057       pvInfoList[currentMove].depth = programStats.depth;
12058       pvInfoList[currentMove].score = programStats.score;
12059       pvInfoList[currentMove].time  = 0;
12060       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12061       else { // append analysis of final position as comment
12062         char buf[MSG_SIZ];
12063         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12064         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12065       }
12066       programStats.depth = 0;
12067     }
12068
12069     if (currentMove >= forwardMostMove) {
12070       if(gameMode == AnalyzeFile) {
12071           if(appData.loadGameIndex == -1) {
12072             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12073           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12074           } else {
12075           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12076         }
12077       }
12078 //      gameMode = EndOfGame;
12079 //      ModeHighlight();
12080
12081       /* [AS] Clear current move marker at the end of a game */
12082       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12083
12084       return FALSE;
12085     }
12086
12087     toX = moveList[currentMove][2] - AAA;
12088     toY = moveList[currentMove][3] - ONE;
12089
12090     if (moveList[currentMove][1] == '@') {
12091         if (appData.highlightLastMove) {
12092             SetHighlights(-1, -1, toX, toY);
12093         }
12094     } else {
12095         int viaX = moveList[currentMove][5] - AAA;
12096         int viaY = moveList[currentMove][6] - ONE;
12097         fromX = moveList[currentMove][0] - AAA;
12098         fromY = moveList[currentMove][1] - ONE;
12099
12100         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12101
12102         if(moveList[currentMove][4] == ';') { // multi-leg
12103             ChessSquare piece = boards[currentMove][viaY][viaX];
12104             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12105             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12106             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12107             boards[currentMove][viaY][viaX] = piece;
12108         } else
12109         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12110
12111         if (appData.highlightLastMove) {
12112             SetHighlights(fromX, fromY, toX, toY);
12113         }
12114     }
12115     DisplayMove(currentMove);
12116     SendMoveToProgram(currentMove++, &first);
12117     DisplayBothClocks();
12118     DrawPosition(FALSE, boards[currentMove]);
12119     // [HGM] PV info: always display, routine tests if empty
12120     DisplayComment(currentMove - 1, commentList[currentMove]);
12121     return TRUE;
12122 }
12123
12124
12125 int
12126 LoadGameOneMove (ChessMove readAhead)
12127 {
12128     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12129     char promoChar = NULLCHAR;
12130     ChessMove moveType;
12131     char move[MSG_SIZ];
12132     char *p, *q;
12133
12134     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12135         gameMode != AnalyzeMode && gameMode != Training) {
12136         gameFileFP = NULL;
12137         return FALSE;
12138     }
12139
12140     yyboardindex = forwardMostMove;
12141     if (readAhead != EndOfFile) {
12142       moveType = readAhead;
12143     } else {
12144       if (gameFileFP == NULL)
12145           return FALSE;
12146       moveType = (ChessMove) Myylex();
12147     }
12148
12149     done = FALSE;
12150     switch (moveType) {
12151       case Comment:
12152         if (appData.debugMode)
12153           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12154         p = yy_text;
12155
12156         /* append the comment but don't display it */
12157         AppendComment(currentMove, p, FALSE);
12158         return TRUE;
12159
12160       case WhiteCapturesEnPassant:
12161       case BlackCapturesEnPassant:
12162       case WhitePromotion:
12163       case BlackPromotion:
12164       case WhiteNonPromotion:
12165       case BlackNonPromotion:
12166       case NormalMove:
12167       case FirstLeg:
12168       case WhiteKingSideCastle:
12169       case WhiteQueenSideCastle:
12170       case BlackKingSideCastle:
12171       case BlackQueenSideCastle:
12172       case WhiteKingSideCastleWild:
12173       case WhiteQueenSideCastleWild:
12174       case BlackKingSideCastleWild:
12175       case BlackQueenSideCastleWild:
12176       /* PUSH Fabien */
12177       case WhiteHSideCastleFR:
12178       case WhiteASideCastleFR:
12179       case BlackHSideCastleFR:
12180       case BlackASideCastleFR:
12181       /* POP Fabien */
12182         if (appData.debugMode)
12183           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12184         fromX = currentMoveString[0] - AAA;
12185         fromY = currentMoveString[1] - ONE;
12186         toX = currentMoveString[2] - AAA;
12187         toY = currentMoveString[3] - ONE;
12188         promoChar = currentMoveString[4];
12189         if(promoChar == ';') promoChar = currentMoveString[7];
12190         break;
12191
12192       case WhiteDrop:
12193       case BlackDrop:
12194         if (appData.debugMode)
12195           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12196         fromX = moveType == WhiteDrop ?
12197           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12198         (int) CharToPiece(ToLower(currentMoveString[0]));
12199         fromY = DROP_RANK;
12200         toX = currentMoveString[2] - AAA;
12201         toY = currentMoveString[3] - ONE;
12202         break;
12203
12204       case WhiteWins:
12205       case BlackWins:
12206       case GameIsDrawn:
12207       case GameUnfinished:
12208         if (appData.debugMode)
12209           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12210         p = strchr(yy_text, '{');
12211         if (p == NULL) p = strchr(yy_text, '(');
12212         if (p == NULL) {
12213             p = yy_text;
12214             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12215         } else {
12216             q = strchr(p, *p == '{' ? '}' : ')');
12217             if (q != NULL) *q = NULLCHAR;
12218             p++;
12219         }
12220         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12221         GameEnds(moveType, p, GE_FILE);
12222         done = TRUE;
12223         if (cmailMsgLoaded) {
12224             ClearHighlights();
12225             flipView = WhiteOnMove(currentMove);
12226             if (moveType == GameUnfinished) flipView = !flipView;
12227             if (appData.debugMode)
12228               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12229         }
12230         break;
12231
12232       case EndOfFile:
12233         if (appData.debugMode)
12234           fprintf(debugFP, "Parser hit end of file\n");
12235         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12236           case MT_NONE:
12237           case MT_CHECK:
12238             break;
12239           case MT_CHECKMATE:
12240           case MT_STAINMATE:
12241             if (WhiteOnMove(currentMove)) {
12242                 GameEnds(BlackWins, "Black mates", GE_FILE);
12243             } else {
12244                 GameEnds(WhiteWins, "White mates", GE_FILE);
12245             }
12246             break;
12247           case MT_STALEMATE:
12248             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12249             break;
12250         }
12251         done = TRUE;
12252         break;
12253
12254       case MoveNumberOne:
12255         if (lastLoadGameStart == GNUChessGame) {
12256             /* GNUChessGames have numbers, but they aren't move numbers */
12257             if (appData.debugMode)
12258               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12259                       yy_text, (int) moveType);
12260             return LoadGameOneMove(EndOfFile); /* tail recursion */
12261         }
12262         /* else fall thru */
12263
12264       case XBoardGame:
12265       case GNUChessGame:
12266       case PGNTag:
12267         /* Reached start of next game in file */
12268         if (appData.debugMode)
12269           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12270         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12271           case MT_NONE:
12272           case MT_CHECK:
12273             break;
12274           case MT_CHECKMATE:
12275           case MT_STAINMATE:
12276             if (WhiteOnMove(currentMove)) {
12277                 GameEnds(BlackWins, "Black mates", GE_FILE);
12278             } else {
12279                 GameEnds(WhiteWins, "White mates", GE_FILE);
12280             }
12281             break;
12282           case MT_STALEMATE:
12283             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12284             break;
12285         }
12286         done = TRUE;
12287         break;
12288
12289       case PositionDiagram:     /* should not happen; ignore */
12290       case ElapsedTime:         /* ignore */
12291       case NAG:                 /* ignore */
12292         if (appData.debugMode)
12293           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12294                   yy_text, (int) moveType);
12295         return LoadGameOneMove(EndOfFile); /* tail recursion */
12296
12297       case IllegalMove:
12298         if (appData.testLegality) {
12299             if (appData.debugMode)
12300               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12301             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12302                     (forwardMostMove / 2) + 1,
12303                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12304             DisplayError(move, 0);
12305             done = TRUE;
12306         } else {
12307             if (appData.debugMode)
12308               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12309                       yy_text, currentMoveString);
12310             if(currentMoveString[1] == '@') {
12311                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12312                 fromY = DROP_RANK;
12313             } else {
12314                 fromX = currentMoveString[0] - AAA;
12315                 fromY = currentMoveString[1] - ONE;
12316             }
12317             toX = currentMoveString[2] - AAA;
12318             toY = currentMoveString[3] - ONE;
12319             promoChar = currentMoveString[4];
12320         }
12321         break;
12322
12323       case AmbiguousMove:
12324         if (appData.debugMode)
12325           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12326         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12327                 (forwardMostMove / 2) + 1,
12328                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12329         DisplayError(move, 0);
12330         done = TRUE;
12331         break;
12332
12333       default:
12334       case ImpossibleMove:
12335         if (appData.debugMode)
12336           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12337         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12338                 (forwardMostMove / 2) + 1,
12339                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12340         DisplayError(move, 0);
12341         done = TRUE;
12342         break;
12343     }
12344
12345     if (done) {
12346         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12347             DrawPosition(FALSE, boards[currentMove]);
12348             DisplayBothClocks();
12349             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12350               DisplayComment(currentMove - 1, commentList[currentMove]);
12351         }
12352         (void) StopLoadGameTimer();
12353         gameFileFP = NULL;
12354         cmailOldMove = forwardMostMove;
12355         return FALSE;
12356     } else {
12357         /* currentMoveString is set as a side-effect of yylex */
12358
12359         thinkOutput[0] = NULLCHAR;
12360         MakeMove(fromX, fromY, toX, toY, promoChar);
12361         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12362         currentMove = forwardMostMove;
12363         return TRUE;
12364     }
12365 }
12366
12367 /* Load the nth game from the given file */
12368 int
12369 LoadGameFromFile (char *filename, int n, char *title, int useList)
12370 {
12371     FILE *f;
12372     char buf[MSG_SIZ];
12373
12374     if (strcmp(filename, "-") == 0) {
12375         f = stdin;
12376         title = "stdin";
12377     } else {
12378         f = fopen(filename, "rb");
12379         if (f == NULL) {
12380           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12381             DisplayError(buf, errno);
12382             return FALSE;
12383         }
12384     }
12385     if (fseek(f, 0, 0) == -1) {
12386         /* f is not seekable; probably a pipe */
12387         useList = FALSE;
12388     }
12389     if (useList && n == 0) {
12390         int error = GameListBuild(f);
12391         if (error) {
12392             DisplayError(_("Cannot build game list"), error);
12393         } else if (!ListEmpty(&gameList) &&
12394                    ((ListGame *) gameList.tailPred)->number > 1) {
12395             GameListPopUp(f, title);
12396             return TRUE;
12397         }
12398         GameListDestroy();
12399         n = 1;
12400     }
12401     if (n == 0) n = 1;
12402     return LoadGame(f, n, title, FALSE);
12403 }
12404
12405
12406 void
12407 MakeRegisteredMove ()
12408 {
12409     int fromX, fromY, toX, toY;
12410     char promoChar;
12411     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12412         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12413           case CMAIL_MOVE:
12414           case CMAIL_DRAW:
12415             if (appData.debugMode)
12416               fprintf(debugFP, "Restoring %s for game %d\n",
12417                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12418
12419             thinkOutput[0] = NULLCHAR;
12420             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12421             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12422             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12423             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12424             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12425             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12426             MakeMove(fromX, fromY, toX, toY, promoChar);
12427             ShowMove(fromX, fromY, toX, toY);
12428
12429             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12430               case MT_NONE:
12431               case MT_CHECK:
12432                 break;
12433
12434               case MT_CHECKMATE:
12435               case MT_STAINMATE:
12436                 if (WhiteOnMove(currentMove)) {
12437                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12438                 } else {
12439                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12440                 }
12441                 break;
12442
12443               case MT_STALEMATE:
12444                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12445                 break;
12446             }
12447
12448             break;
12449
12450           case CMAIL_RESIGN:
12451             if (WhiteOnMove(currentMove)) {
12452                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12453             } else {
12454                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12455             }
12456             break;
12457
12458           case CMAIL_ACCEPT:
12459             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12460             break;
12461
12462           default:
12463             break;
12464         }
12465     }
12466
12467     return;
12468 }
12469
12470 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12471 int
12472 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12473 {
12474     int retVal;
12475
12476     if (gameNumber > nCmailGames) {
12477         DisplayError(_("No more games in this message"), 0);
12478         return FALSE;
12479     }
12480     if (f == lastLoadGameFP) {
12481         int offset = gameNumber - lastLoadGameNumber;
12482         if (offset == 0) {
12483             cmailMsg[0] = NULLCHAR;
12484             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12485                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12486                 nCmailMovesRegistered--;
12487             }
12488             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12489             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12490                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12491             }
12492         } else {
12493             if (! RegisterMove()) return FALSE;
12494         }
12495     }
12496
12497     retVal = LoadGame(f, gameNumber, title, useList);
12498
12499     /* Make move registered during previous look at this game, if any */
12500     MakeRegisteredMove();
12501
12502     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12503         commentList[currentMove]
12504           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12505         DisplayComment(currentMove - 1, commentList[currentMove]);
12506     }
12507
12508     return retVal;
12509 }
12510
12511 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12512 int
12513 ReloadGame (int offset)
12514 {
12515     int gameNumber = lastLoadGameNumber + offset;
12516     if (lastLoadGameFP == NULL) {
12517         DisplayError(_("No game has been loaded yet"), 0);
12518         return FALSE;
12519     }
12520     if (gameNumber <= 0) {
12521         DisplayError(_("Can't back up any further"), 0);
12522         return FALSE;
12523     }
12524     if (cmailMsgLoaded) {
12525         return CmailLoadGame(lastLoadGameFP, gameNumber,
12526                              lastLoadGameTitle, lastLoadGameUseList);
12527     } else {
12528         return LoadGame(lastLoadGameFP, gameNumber,
12529                         lastLoadGameTitle, lastLoadGameUseList);
12530     }
12531 }
12532
12533 int keys[EmptySquare+1];
12534
12535 int
12536 PositionMatches (Board b1, Board b2)
12537 {
12538     int r, f, sum=0;
12539     switch(appData.searchMode) {
12540         case 1: return CompareWithRights(b1, b2);
12541         case 2:
12542             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12543                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12544             }
12545             return TRUE;
12546         case 3:
12547             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12548               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12549                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12550             }
12551             return sum==0;
12552         case 4:
12553             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12554                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12555             }
12556             return sum==0;
12557     }
12558     return TRUE;
12559 }
12560
12561 #define Q_PROMO  4
12562 #define Q_EP     3
12563 #define Q_BCASTL 2
12564 #define Q_WCASTL 1
12565
12566 int pieceList[256], quickBoard[256];
12567 ChessSquare pieceType[256] = { EmptySquare };
12568 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12569 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12570 int soughtTotal, turn;
12571 Boolean epOK, flipSearch;
12572
12573 typedef struct {
12574     unsigned char piece, to;
12575 } Move;
12576
12577 #define DSIZE (250000)
12578
12579 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12580 Move *moveDatabase = initialSpace;
12581 unsigned int movePtr, dataSize = DSIZE;
12582
12583 int
12584 MakePieceList (Board board, int *counts)
12585 {
12586     int r, f, n=Q_PROMO, total=0;
12587     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12588     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12589         int sq = f + (r<<4);
12590         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12591             quickBoard[sq] = ++n;
12592             pieceList[n] = sq;
12593             pieceType[n] = board[r][f];
12594             counts[board[r][f]]++;
12595             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12596             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12597             total++;
12598         }
12599     }
12600     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12601     return total;
12602 }
12603
12604 void
12605 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12606 {
12607     int sq = fromX + (fromY<<4);
12608     int piece = quickBoard[sq], rook;
12609     quickBoard[sq] = 0;
12610     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12611     if(piece == pieceList[1] && fromY == toY) {
12612       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12613         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12614         moveDatabase[movePtr++].piece = Q_WCASTL;
12615         quickBoard[sq] = piece;
12616         piece = quickBoard[from]; quickBoard[from] = 0;
12617         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12618       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12619         quickBoard[sq] = 0; // remove Rook
12620         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12621         moveDatabase[movePtr++].piece = Q_WCASTL;
12622         quickBoard[sq] = pieceList[1]; // put King
12623         piece = rook;
12624         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12625       }
12626     } else
12627     if(piece == pieceList[2] && fromY == toY) {
12628       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12629         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12630         moveDatabase[movePtr++].piece = Q_BCASTL;
12631         quickBoard[sq] = piece;
12632         piece = quickBoard[from]; quickBoard[from] = 0;
12633         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12634       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12635         quickBoard[sq] = 0; // remove Rook
12636         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12637         moveDatabase[movePtr++].piece = Q_BCASTL;
12638         quickBoard[sq] = pieceList[2]; // put King
12639         piece = rook;
12640         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12641       }
12642     } else
12643     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12644         quickBoard[(fromY<<4)+toX] = 0;
12645         moveDatabase[movePtr].piece = Q_EP;
12646         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12647         moveDatabase[movePtr].to = sq;
12648     } else
12649     if(promoPiece != pieceType[piece]) {
12650         moveDatabase[movePtr++].piece = Q_PROMO;
12651         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12652     }
12653     moveDatabase[movePtr].piece = piece;
12654     quickBoard[sq] = piece;
12655     movePtr++;
12656 }
12657
12658 int
12659 PackGame (Board board)
12660 {
12661     Move *newSpace = NULL;
12662     moveDatabase[movePtr].piece = 0; // terminate previous game
12663     if(movePtr > dataSize) {
12664         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12665         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12666         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12667         if(newSpace) {
12668             int i;
12669             Move *p = moveDatabase, *q = newSpace;
12670             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12671             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12672             moveDatabase = newSpace;
12673         } else { // calloc failed, we must be out of memory. Too bad...
12674             dataSize = 0; // prevent calloc events for all subsequent games
12675             return 0;     // and signal this one isn't cached
12676         }
12677     }
12678     movePtr++;
12679     MakePieceList(board, counts);
12680     return movePtr;
12681 }
12682
12683 int
12684 QuickCompare (Board board, int *minCounts, int *maxCounts)
12685 {   // compare according to search mode
12686     int r, f;
12687     switch(appData.searchMode)
12688     {
12689       case 1: // exact position match
12690         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12691         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12692             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12693         }
12694         break;
12695       case 2: // can have extra material on empty squares
12696         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12697             if(board[r][f] == EmptySquare) continue;
12698             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12699         }
12700         break;
12701       case 3: // material with exact Pawn structure
12702         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12703             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12704             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12705         } // fall through to material comparison
12706       case 4: // exact material
12707         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12708         break;
12709       case 6: // material range with given imbalance
12710         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12711         // fall through to range comparison
12712       case 5: // material range
12713         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12714     }
12715     return TRUE;
12716 }
12717
12718 int
12719 QuickScan (Board board, Move *move)
12720 {   // reconstruct game,and compare all positions in it
12721     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12722     do {
12723         int piece = move->piece;
12724         int to = move->to, from = pieceList[piece];
12725         if(found < 0) { // if already found just scan to game end for final piece count
12726           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12727            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12728            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12729                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12730             ) {
12731             static int lastCounts[EmptySquare+1];
12732             int i;
12733             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12734             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12735           } else stretch = 0;
12736           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12737           if(found >= 0 && !appData.minPieces) return found;
12738         }
12739         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12740           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12741           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12742             piece = (++move)->piece;
12743             from = pieceList[piece];
12744             counts[pieceType[piece]]--;
12745             pieceType[piece] = (ChessSquare) move->to;
12746             counts[move->to]++;
12747           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12748             counts[pieceType[quickBoard[to]]]--;
12749             quickBoard[to] = 0; total--;
12750             move++;
12751             continue;
12752           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12753             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12754             from  = pieceList[piece]; // so this must be King
12755             quickBoard[from] = 0;
12756             pieceList[piece] = to;
12757             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12758             quickBoard[from] = 0; // rook
12759             quickBoard[to] = piece;
12760             to = move->to; piece = move->piece;
12761             goto aftercastle;
12762           }
12763         }
12764         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12765         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12766         quickBoard[from] = 0;
12767       aftercastle:
12768         quickBoard[to] = piece;
12769         pieceList[piece] = to;
12770         cnt++; turn ^= 3;
12771         move++;
12772     } while(1);
12773 }
12774
12775 void
12776 InitSearch ()
12777 {
12778     int r, f;
12779     flipSearch = FALSE;
12780     CopyBoard(soughtBoard, boards[currentMove]);
12781     soughtTotal = MakePieceList(soughtBoard, maxSought);
12782     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12783     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12784     CopyBoard(reverseBoard, boards[currentMove]);
12785     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12786         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12787         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12788         reverseBoard[r][f] = piece;
12789     }
12790     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12791     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12792     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12793                  || (boards[currentMove][CASTLING][2] == NoRights ||
12794                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12795                  && (boards[currentMove][CASTLING][5] == NoRights ||
12796                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12797       ) {
12798         flipSearch = TRUE;
12799         CopyBoard(flipBoard, soughtBoard);
12800         CopyBoard(rotateBoard, reverseBoard);
12801         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12802             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12803             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12804         }
12805     }
12806     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12807     if(appData.searchMode >= 5) {
12808         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12809         MakePieceList(soughtBoard, minSought);
12810         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12811     }
12812     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12813         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12814 }
12815
12816 GameInfo dummyInfo;
12817 static int creatingBook;
12818
12819 int
12820 GameContainsPosition (FILE *f, ListGame *lg)
12821 {
12822     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12823     int fromX, fromY, toX, toY;
12824     char promoChar;
12825     static int initDone=FALSE;
12826
12827     // weed out games based on numerical tag comparison
12828     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12829     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12830     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12831     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12832     if(!initDone) {
12833         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12834         initDone = TRUE;
12835     }
12836     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12837     else CopyBoard(boards[scratch], initialPosition); // default start position
12838     if(lg->moves) {
12839         turn = btm + 1;
12840         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12841         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12842     }
12843     if(btm) plyNr++;
12844     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12845     fseek(f, lg->offset, 0);
12846     yynewfile(f);
12847     while(1) {
12848         yyboardindex = scratch;
12849         quickFlag = plyNr+1;
12850         next = Myylex();
12851         quickFlag = 0;
12852         switch(next) {
12853             case PGNTag:
12854                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12855             default:
12856                 continue;
12857
12858             case XBoardGame:
12859             case GNUChessGame:
12860                 if(plyNr) return -1; // after we have seen moves, this is for new game
12861               continue;
12862
12863             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12864             case ImpossibleMove:
12865             case WhiteWins: // game ends here with these four
12866             case BlackWins:
12867             case GameIsDrawn:
12868             case GameUnfinished:
12869                 return -1;
12870
12871             case IllegalMove:
12872                 if(appData.testLegality) return -1;
12873             case WhiteCapturesEnPassant:
12874             case BlackCapturesEnPassant:
12875             case WhitePromotion:
12876             case BlackPromotion:
12877             case WhiteNonPromotion:
12878             case BlackNonPromotion:
12879             case NormalMove:
12880             case FirstLeg:
12881             case WhiteKingSideCastle:
12882             case WhiteQueenSideCastle:
12883             case BlackKingSideCastle:
12884             case BlackQueenSideCastle:
12885             case WhiteKingSideCastleWild:
12886             case WhiteQueenSideCastleWild:
12887             case BlackKingSideCastleWild:
12888             case BlackQueenSideCastleWild:
12889             case WhiteHSideCastleFR:
12890             case WhiteASideCastleFR:
12891             case BlackHSideCastleFR:
12892             case BlackASideCastleFR:
12893                 fromX = currentMoveString[0] - AAA;
12894                 fromY = currentMoveString[1] - ONE;
12895                 toX = currentMoveString[2] - AAA;
12896                 toY = currentMoveString[3] - ONE;
12897                 promoChar = currentMoveString[4];
12898                 break;
12899             case WhiteDrop:
12900             case BlackDrop:
12901                 fromX = next == WhiteDrop ?
12902                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12903                   (int) CharToPiece(ToLower(currentMoveString[0]));
12904                 fromY = DROP_RANK;
12905                 toX = currentMoveString[2] - AAA;
12906                 toY = currentMoveString[3] - ONE;
12907                 promoChar = 0;
12908                 break;
12909         }
12910         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12911         plyNr++;
12912         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12913         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12914         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12915         if(appData.findMirror) {
12916             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12917             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12918         }
12919     }
12920 }
12921
12922 /* Load the nth game from open file f */
12923 int
12924 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12925 {
12926     ChessMove cm;
12927     char buf[MSG_SIZ];
12928     int gn = gameNumber;
12929     ListGame *lg = NULL;
12930     int numPGNTags = 0;
12931     int err, pos = -1;
12932     GameMode oldGameMode;
12933     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12934     char oldName[MSG_SIZ];
12935
12936     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12937
12938     if (appData.debugMode)
12939         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12940
12941     if (gameMode == Training )
12942         SetTrainingModeOff();
12943
12944     oldGameMode = gameMode;
12945     if (gameMode != BeginningOfGame) {
12946       Reset(FALSE, TRUE);
12947     }
12948     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12949
12950     gameFileFP = f;
12951     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12952         fclose(lastLoadGameFP);
12953     }
12954
12955     if (useList) {
12956         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12957
12958         if (lg) {
12959             fseek(f, lg->offset, 0);
12960             GameListHighlight(gameNumber);
12961             pos = lg->position;
12962             gn = 1;
12963         }
12964         else {
12965             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12966               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12967             else
12968             DisplayError(_("Game number out of range"), 0);
12969             return FALSE;
12970         }
12971     } else {
12972         GameListDestroy();
12973         if (fseek(f, 0, 0) == -1) {
12974             if (f == lastLoadGameFP ?
12975                 gameNumber == lastLoadGameNumber + 1 :
12976                 gameNumber == 1) {
12977                 gn = 1;
12978             } else {
12979                 DisplayError(_("Can't seek on game file"), 0);
12980                 return FALSE;
12981             }
12982         }
12983     }
12984     lastLoadGameFP = f;
12985     lastLoadGameNumber = gameNumber;
12986     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12987     lastLoadGameUseList = useList;
12988
12989     yynewfile(f);
12990
12991     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12992       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12993                 lg->gameInfo.black);
12994             DisplayTitle(buf);
12995     } else if (*title != NULLCHAR) {
12996         if (gameNumber > 1) {
12997           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12998             DisplayTitle(buf);
12999         } else {
13000             DisplayTitle(title);
13001         }
13002     }
13003
13004     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13005         gameMode = PlayFromGameFile;
13006         ModeHighlight();
13007     }
13008
13009     currentMove = forwardMostMove = backwardMostMove = 0;
13010     CopyBoard(boards[0], initialPosition);
13011     StopClocks();
13012
13013     /*
13014      * Skip the first gn-1 games in the file.
13015      * Also skip over anything that precedes an identifiable
13016      * start of game marker, to avoid being confused by
13017      * garbage at the start of the file.  Currently
13018      * recognized start of game markers are the move number "1",
13019      * the pattern "gnuchess .* game", the pattern
13020      * "^[#;%] [^ ]* game file", and a PGN tag block.
13021      * A game that starts with one of the latter two patterns
13022      * will also have a move number 1, possibly
13023      * following a position diagram.
13024      * 5-4-02: Let's try being more lenient and allowing a game to
13025      * start with an unnumbered move.  Does that break anything?
13026      */
13027     cm = lastLoadGameStart = EndOfFile;
13028     while (gn > 0) {
13029         yyboardindex = forwardMostMove;
13030         cm = (ChessMove) Myylex();
13031         switch (cm) {
13032           case EndOfFile:
13033             if (cmailMsgLoaded) {
13034                 nCmailGames = CMAIL_MAX_GAMES - gn;
13035             } else {
13036                 Reset(TRUE, TRUE);
13037                 DisplayError(_("Game not found in file"), 0);
13038             }
13039             return FALSE;
13040
13041           case GNUChessGame:
13042           case XBoardGame:
13043             gn--;
13044             lastLoadGameStart = cm;
13045             break;
13046
13047           case MoveNumberOne:
13048             switch (lastLoadGameStart) {
13049               case GNUChessGame:
13050               case XBoardGame:
13051               case PGNTag:
13052                 break;
13053               case MoveNumberOne:
13054               case EndOfFile:
13055                 gn--;           /* count this game */
13056                 lastLoadGameStart = cm;
13057                 break;
13058               default:
13059                 /* impossible */
13060                 break;
13061             }
13062             break;
13063
13064           case PGNTag:
13065             switch (lastLoadGameStart) {
13066               case GNUChessGame:
13067               case PGNTag:
13068               case MoveNumberOne:
13069               case EndOfFile:
13070                 gn--;           /* count this game */
13071                 lastLoadGameStart = cm;
13072                 break;
13073               case XBoardGame:
13074                 lastLoadGameStart = cm; /* game counted already */
13075                 break;
13076               default:
13077                 /* impossible */
13078                 break;
13079             }
13080             if (gn > 0) {
13081                 do {
13082                     yyboardindex = forwardMostMove;
13083                     cm = (ChessMove) Myylex();
13084                 } while (cm == PGNTag || cm == Comment);
13085             }
13086             break;
13087
13088           case WhiteWins:
13089           case BlackWins:
13090           case GameIsDrawn:
13091             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13092                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13093                     != CMAIL_OLD_RESULT) {
13094                     nCmailResults ++ ;
13095                     cmailResult[  CMAIL_MAX_GAMES
13096                                 - gn - 1] = CMAIL_OLD_RESULT;
13097                 }
13098             }
13099             break;
13100
13101           case NormalMove:
13102           case FirstLeg:
13103             /* Only a NormalMove can be at the start of a game
13104              * without a position diagram. */
13105             if (lastLoadGameStart == EndOfFile ) {
13106               gn--;
13107               lastLoadGameStart = MoveNumberOne;
13108             }
13109             break;
13110
13111           default:
13112             break;
13113         }
13114     }
13115
13116     if (appData.debugMode)
13117       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13118
13119     if (cm == XBoardGame) {
13120         /* Skip any header junk before position diagram and/or move 1 */
13121         for (;;) {
13122             yyboardindex = forwardMostMove;
13123             cm = (ChessMove) Myylex();
13124
13125             if (cm == EndOfFile ||
13126                 cm == GNUChessGame || cm == XBoardGame) {
13127                 /* Empty game; pretend end-of-file and handle later */
13128                 cm = EndOfFile;
13129                 break;
13130             }
13131
13132             if (cm == MoveNumberOne || cm == PositionDiagram ||
13133                 cm == PGNTag || cm == Comment)
13134               break;
13135         }
13136     } else if (cm == GNUChessGame) {
13137         if (gameInfo.event != NULL) {
13138             free(gameInfo.event);
13139         }
13140         gameInfo.event = StrSave(yy_text);
13141     }
13142
13143     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13144     while (cm == PGNTag) {
13145         if (appData.debugMode)
13146           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13147         err = ParsePGNTag(yy_text, &gameInfo);
13148         if (!err) numPGNTags++;
13149
13150         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13151         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13152             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13153             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13154             InitPosition(TRUE);
13155             oldVariant = gameInfo.variant;
13156             if (appData.debugMode)
13157               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13158         }
13159
13160
13161         if (gameInfo.fen != NULL) {
13162           Board initial_position;
13163           startedFromSetupPosition = TRUE;
13164           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13165             Reset(TRUE, TRUE);
13166             DisplayError(_("Bad FEN position in file"), 0);
13167             return FALSE;
13168           }
13169           CopyBoard(boards[0], initial_position);
13170           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13171             CopyBoard(initialPosition, initial_position);
13172           if (blackPlaysFirst) {
13173             currentMove = forwardMostMove = backwardMostMove = 1;
13174             CopyBoard(boards[1], initial_position);
13175             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13176             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13177             timeRemaining[0][1] = whiteTimeRemaining;
13178             timeRemaining[1][1] = blackTimeRemaining;
13179             if (commentList[0] != NULL) {
13180               commentList[1] = commentList[0];
13181               commentList[0] = NULL;
13182             }
13183           } else {
13184             currentMove = forwardMostMove = backwardMostMove = 0;
13185           }
13186           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13187           {   int i;
13188               initialRulePlies = FENrulePlies;
13189               for( i=0; i< nrCastlingRights; i++ )
13190                   initialRights[i] = initial_position[CASTLING][i];
13191           }
13192           yyboardindex = forwardMostMove;
13193           free(gameInfo.fen);
13194           gameInfo.fen = NULL;
13195         }
13196
13197         yyboardindex = forwardMostMove;
13198         cm = (ChessMove) Myylex();
13199
13200         /* Handle comments interspersed among the tags */
13201         while (cm == Comment) {
13202             char *p;
13203             if (appData.debugMode)
13204               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13205             p = yy_text;
13206             AppendComment(currentMove, p, FALSE);
13207             yyboardindex = forwardMostMove;
13208             cm = (ChessMove) Myylex();
13209         }
13210     }
13211
13212     /* don't rely on existence of Event tag since if game was
13213      * pasted from clipboard the Event tag may not exist
13214      */
13215     if (numPGNTags > 0){
13216         char *tags;
13217         if (gameInfo.variant == VariantNormal) {
13218           VariantClass v = StringToVariant(gameInfo.event);
13219           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13220           if(v < VariantShogi) gameInfo.variant = v;
13221         }
13222         if (!matchMode) {
13223           if( appData.autoDisplayTags ) {
13224             tags = PGNTags(&gameInfo);
13225             TagsPopUp(tags, CmailMsg());
13226             free(tags);
13227           }
13228         }
13229     } else {
13230         /* Make something up, but don't display it now */
13231         SetGameInfo();
13232         TagsPopDown();
13233     }
13234
13235     if (cm == PositionDiagram) {
13236         int i, j;
13237         char *p;
13238         Board initial_position;
13239
13240         if (appData.debugMode)
13241           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13242
13243         if (!startedFromSetupPosition) {
13244             p = yy_text;
13245             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13246               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13247                 switch (*p) {
13248                   case '{':
13249                   case '[':
13250                   case '-':
13251                   case ' ':
13252                   case '\t':
13253                   case '\n':
13254                   case '\r':
13255                     break;
13256                   default:
13257                     initial_position[i][j++] = CharToPiece(*p);
13258                     break;
13259                 }
13260             while (*p == ' ' || *p == '\t' ||
13261                    *p == '\n' || *p == '\r') p++;
13262
13263             if (strncmp(p, "black", strlen("black"))==0)
13264               blackPlaysFirst = TRUE;
13265             else
13266               blackPlaysFirst = FALSE;
13267             startedFromSetupPosition = TRUE;
13268
13269             CopyBoard(boards[0], initial_position);
13270             if (blackPlaysFirst) {
13271                 currentMove = forwardMostMove = backwardMostMove = 1;
13272                 CopyBoard(boards[1], initial_position);
13273                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13274                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13275                 timeRemaining[0][1] = whiteTimeRemaining;
13276                 timeRemaining[1][1] = blackTimeRemaining;
13277                 if (commentList[0] != NULL) {
13278                     commentList[1] = commentList[0];
13279                     commentList[0] = NULL;
13280                 }
13281             } else {
13282                 currentMove = forwardMostMove = backwardMostMove = 0;
13283             }
13284         }
13285         yyboardindex = forwardMostMove;
13286         cm = (ChessMove) Myylex();
13287     }
13288
13289   if(!creatingBook) {
13290     if (first.pr == NoProc) {
13291         StartChessProgram(&first);
13292     }
13293     InitChessProgram(&first, FALSE);
13294     if(gameInfo.variant == VariantUnknown && *oldName) {
13295         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13296         gameInfo.variant = v;
13297     }
13298     SendToProgram("force\n", &first);
13299     if (startedFromSetupPosition) {
13300         SendBoard(&first, forwardMostMove);
13301     if (appData.debugMode) {
13302         fprintf(debugFP, "Load Game\n");
13303     }
13304         DisplayBothClocks();
13305     }
13306   }
13307
13308     /* [HGM] server: flag to write setup moves in broadcast file as one */
13309     loadFlag = appData.suppressLoadMoves;
13310
13311     while (cm == Comment) {
13312         char *p;
13313         if (appData.debugMode)
13314           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13315         p = yy_text;
13316         AppendComment(currentMove, p, FALSE);
13317         yyboardindex = forwardMostMove;
13318         cm = (ChessMove) Myylex();
13319     }
13320
13321     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13322         cm == WhiteWins || cm == BlackWins ||
13323         cm == GameIsDrawn || cm == GameUnfinished) {
13324         DisplayMessage("", _("No moves in game"));
13325         if (cmailMsgLoaded) {
13326             if (appData.debugMode)
13327               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13328             ClearHighlights();
13329             flipView = FALSE;
13330         }
13331         DrawPosition(FALSE, boards[currentMove]);
13332         DisplayBothClocks();
13333         gameMode = EditGame;
13334         ModeHighlight();
13335         gameFileFP = NULL;
13336         cmailOldMove = 0;
13337         return TRUE;
13338     }
13339
13340     // [HGM] PV info: routine tests if comment empty
13341     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13342         DisplayComment(currentMove - 1, commentList[currentMove]);
13343     }
13344     if (!matchMode && appData.timeDelay != 0)
13345       DrawPosition(FALSE, boards[currentMove]);
13346
13347     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13348       programStats.ok_to_send = 1;
13349     }
13350
13351     /* if the first token after the PGN tags is a move
13352      * and not move number 1, retrieve it from the parser
13353      */
13354     if (cm != MoveNumberOne)
13355         LoadGameOneMove(cm);
13356
13357     /* load the remaining moves from the file */
13358     while (LoadGameOneMove(EndOfFile)) {
13359       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13360       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13361     }
13362
13363     /* rewind to the start of the game */
13364     currentMove = backwardMostMove;
13365
13366     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13367
13368     if (oldGameMode == AnalyzeFile) {
13369       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13370       AnalyzeFileEvent();
13371     } else
13372     if (oldGameMode == AnalyzeMode) {
13373       AnalyzeFileEvent();
13374     }
13375
13376     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13377         long int w, b; // [HGM] adjourn: restore saved clock times
13378         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13379         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13380             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13381             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13382         }
13383     }
13384
13385     if(creatingBook) return TRUE;
13386     if (!matchMode && pos > 0) {
13387         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13388     } else
13389     if (matchMode || appData.timeDelay == 0) {
13390       ToEndEvent();
13391     } else if (appData.timeDelay > 0) {
13392       AutoPlayGameLoop();
13393     }
13394
13395     if (appData.debugMode)
13396         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13397
13398     loadFlag = 0; /* [HGM] true game starts */
13399     return TRUE;
13400 }
13401
13402 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13403 int
13404 ReloadPosition (int offset)
13405 {
13406     int positionNumber = lastLoadPositionNumber + offset;
13407     if (lastLoadPositionFP == NULL) {
13408         DisplayError(_("No position has been loaded yet"), 0);
13409         return FALSE;
13410     }
13411     if (positionNumber <= 0) {
13412         DisplayError(_("Can't back up any further"), 0);
13413         return FALSE;
13414     }
13415     return LoadPosition(lastLoadPositionFP, positionNumber,
13416                         lastLoadPositionTitle);
13417 }
13418
13419 /* Load the nth position from the given file */
13420 int
13421 LoadPositionFromFile (char *filename, int n, char *title)
13422 {
13423     FILE *f;
13424     char buf[MSG_SIZ];
13425
13426     if (strcmp(filename, "-") == 0) {
13427         return LoadPosition(stdin, n, "stdin");
13428     } else {
13429         f = fopen(filename, "rb");
13430         if (f == NULL) {
13431             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13432             DisplayError(buf, errno);
13433             return FALSE;
13434         } else {
13435             return LoadPosition(f, n, title);
13436         }
13437     }
13438 }
13439
13440 /* Load the nth position from the given open file, and close it */
13441 int
13442 LoadPosition (FILE *f, int positionNumber, char *title)
13443 {
13444     char *p, line[MSG_SIZ];
13445     Board initial_position;
13446     int i, j, fenMode, pn;
13447
13448     if (gameMode == Training )
13449         SetTrainingModeOff();
13450
13451     if (gameMode != BeginningOfGame) {
13452         Reset(FALSE, TRUE);
13453     }
13454     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13455         fclose(lastLoadPositionFP);
13456     }
13457     if (positionNumber == 0) positionNumber = 1;
13458     lastLoadPositionFP = f;
13459     lastLoadPositionNumber = positionNumber;
13460     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13461     if (first.pr == NoProc && !appData.noChessProgram) {
13462       StartChessProgram(&first);
13463       InitChessProgram(&first, FALSE);
13464     }
13465     pn = positionNumber;
13466     if (positionNumber < 0) {
13467         /* Negative position number means to seek to that byte offset */
13468         if (fseek(f, -positionNumber, 0) == -1) {
13469             DisplayError(_("Can't seek on position file"), 0);
13470             return FALSE;
13471         };
13472         pn = 1;
13473     } else {
13474         if (fseek(f, 0, 0) == -1) {
13475             if (f == lastLoadPositionFP ?
13476                 positionNumber == lastLoadPositionNumber + 1 :
13477                 positionNumber == 1) {
13478                 pn = 1;
13479             } else {
13480                 DisplayError(_("Can't seek on position file"), 0);
13481                 return FALSE;
13482             }
13483         }
13484     }
13485     /* See if this file is FEN or old-style xboard */
13486     if (fgets(line, MSG_SIZ, f) == NULL) {
13487         DisplayError(_("Position not found in file"), 0);
13488         return FALSE;
13489     }
13490     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13491     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13492
13493     if (pn >= 2) {
13494         if (fenMode || line[0] == '#') pn--;
13495         while (pn > 0) {
13496             /* skip positions before number pn */
13497             if (fgets(line, MSG_SIZ, f) == NULL) {
13498                 Reset(TRUE, TRUE);
13499                 DisplayError(_("Position not found in file"), 0);
13500                 return FALSE;
13501             }
13502             if (fenMode || line[0] == '#') pn--;
13503         }
13504     }
13505
13506     if (fenMode) {
13507         char *p;
13508         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13509             DisplayError(_("Bad FEN position in file"), 0);
13510             return FALSE;
13511         }
13512         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13513             sscanf(p+3, "%s", bestMove);
13514         } else *bestMove = NULLCHAR;
13515     } else {
13516         (void) fgets(line, MSG_SIZ, f);
13517         (void) fgets(line, MSG_SIZ, f);
13518
13519         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13520             (void) fgets(line, MSG_SIZ, f);
13521             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13522                 if (*p == ' ')
13523                   continue;
13524                 initial_position[i][j++] = CharToPiece(*p);
13525             }
13526         }
13527
13528         blackPlaysFirst = FALSE;
13529         if (!feof(f)) {
13530             (void) fgets(line, MSG_SIZ, f);
13531             if (strncmp(line, "black", strlen("black"))==0)
13532               blackPlaysFirst = TRUE;
13533         }
13534     }
13535     startedFromSetupPosition = TRUE;
13536
13537     CopyBoard(boards[0], initial_position);
13538     if (blackPlaysFirst) {
13539         currentMove = forwardMostMove = backwardMostMove = 1;
13540         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13541         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13542         CopyBoard(boards[1], initial_position);
13543         DisplayMessage("", _("Black to play"));
13544     } else {
13545         currentMove = forwardMostMove = backwardMostMove = 0;
13546         DisplayMessage("", _("White to play"));
13547     }
13548     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13549     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13550         SendToProgram("force\n", &first);
13551         SendBoard(&first, forwardMostMove);
13552     }
13553     if (appData.debugMode) {
13554 int i, j;
13555   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13556   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13557         fprintf(debugFP, "Load Position\n");
13558     }
13559
13560     if (positionNumber > 1) {
13561       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13562         DisplayTitle(line);
13563     } else {
13564         DisplayTitle(title);
13565     }
13566     gameMode = EditGame;
13567     ModeHighlight();
13568     ResetClocks();
13569     timeRemaining[0][1] = whiteTimeRemaining;
13570     timeRemaining[1][1] = blackTimeRemaining;
13571     DrawPosition(FALSE, boards[currentMove]);
13572
13573     return TRUE;
13574 }
13575
13576
13577 void
13578 CopyPlayerNameIntoFileName (char **dest, char *src)
13579 {
13580     while (*src != NULLCHAR && *src != ',') {
13581         if (*src == ' ') {
13582             *(*dest)++ = '_';
13583             src++;
13584         } else {
13585             *(*dest)++ = *src++;
13586         }
13587     }
13588 }
13589
13590 char *
13591 DefaultFileName (char *ext)
13592 {
13593     static char def[MSG_SIZ];
13594     char *p;
13595
13596     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13597         p = def;
13598         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13599         *p++ = '-';
13600         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13601         *p++ = '.';
13602         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13603     } else {
13604         def[0] = NULLCHAR;
13605     }
13606     return def;
13607 }
13608
13609 /* Save the current game to the given file */
13610 int
13611 SaveGameToFile (char *filename, int append)
13612 {
13613     FILE *f;
13614     char buf[MSG_SIZ];
13615     int result, i, t,tot=0;
13616
13617     if (strcmp(filename, "-") == 0) {
13618         return SaveGame(stdout, 0, NULL);
13619     } else {
13620         for(i=0; i<10; i++) { // upto 10 tries
13621              f = fopen(filename, append ? "a" : "w");
13622              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13623              if(f || errno != 13) break;
13624              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13625              tot += t;
13626         }
13627         if (f == NULL) {
13628             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13629             DisplayError(buf, errno);
13630             return FALSE;
13631         } else {
13632             safeStrCpy(buf, lastMsg, MSG_SIZ);
13633             DisplayMessage(_("Waiting for access to save file"), "");
13634             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13635             DisplayMessage(_("Saving game"), "");
13636             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13637             result = SaveGame(f, 0, NULL);
13638             DisplayMessage(buf, "");
13639             return result;
13640         }
13641     }
13642 }
13643
13644 char *
13645 SavePart (char *str)
13646 {
13647     static char buf[MSG_SIZ];
13648     char *p;
13649
13650     p = strchr(str, ' ');
13651     if (p == NULL) return str;
13652     strncpy(buf, str, p - str);
13653     buf[p - str] = NULLCHAR;
13654     return buf;
13655 }
13656
13657 #define PGN_MAX_LINE 75
13658
13659 #define PGN_SIDE_WHITE  0
13660 #define PGN_SIDE_BLACK  1
13661
13662 static int
13663 FindFirstMoveOutOfBook (int side)
13664 {
13665     int result = -1;
13666
13667     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13668         int index = backwardMostMove;
13669         int has_book_hit = 0;
13670
13671         if( (index % 2) != side ) {
13672             index++;
13673         }
13674
13675         while( index < forwardMostMove ) {
13676             /* Check to see if engine is in book */
13677             int depth = pvInfoList[index].depth;
13678             int score = pvInfoList[index].score;
13679             int in_book = 0;
13680
13681             if( depth <= 2 ) {
13682                 in_book = 1;
13683             }
13684             else if( score == 0 && depth == 63 ) {
13685                 in_book = 1; /* Zappa */
13686             }
13687             else if( score == 2 && depth == 99 ) {
13688                 in_book = 1; /* Abrok */
13689             }
13690
13691             has_book_hit += in_book;
13692
13693             if( ! in_book ) {
13694                 result = index;
13695
13696                 break;
13697             }
13698
13699             index += 2;
13700         }
13701     }
13702
13703     return result;
13704 }
13705
13706 void
13707 GetOutOfBookInfo (char * buf)
13708 {
13709     int oob[2];
13710     int i;
13711     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13712
13713     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13714     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13715
13716     *buf = '\0';
13717
13718     if( oob[0] >= 0 || oob[1] >= 0 ) {
13719         for( i=0; i<2; i++ ) {
13720             int idx = oob[i];
13721
13722             if( idx >= 0 ) {
13723                 if( i > 0 && oob[0] >= 0 ) {
13724                     strcat( buf, "   " );
13725                 }
13726
13727                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13728                 sprintf( buf+strlen(buf), "%s%.2f",
13729                     pvInfoList[idx].score >= 0 ? "+" : "",
13730                     pvInfoList[idx].score / 100.0 );
13731             }
13732         }
13733     }
13734 }
13735
13736 /* Save game in PGN style */
13737 static void
13738 SaveGamePGN2 (FILE *f)
13739 {
13740     int i, offset, linelen, newblock;
13741 //    char *movetext;
13742     char numtext[32];
13743     int movelen, numlen, blank;
13744     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13745
13746     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13747
13748     PrintPGNTags(f, &gameInfo);
13749
13750     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13751
13752     if (backwardMostMove > 0 || startedFromSetupPosition) {
13753         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13754         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13755         fprintf(f, "\n{--------------\n");
13756         PrintPosition(f, backwardMostMove);
13757         fprintf(f, "--------------}\n");
13758         free(fen);
13759     }
13760     else {
13761         /* [AS] Out of book annotation */
13762         if( appData.saveOutOfBookInfo ) {
13763             char buf[64];
13764
13765             GetOutOfBookInfo( buf );
13766
13767             if( buf[0] != '\0' ) {
13768                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13769             }
13770         }
13771
13772         fprintf(f, "\n");
13773     }
13774
13775     i = backwardMostMove;
13776     linelen = 0;
13777     newblock = TRUE;
13778
13779     while (i < forwardMostMove) {
13780         /* Print comments preceding this move */
13781         if (commentList[i] != NULL) {
13782             if (linelen > 0) fprintf(f, "\n");
13783             fprintf(f, "%s", commentList[i]);
13784             linelen = 0;
13785             newblock = TRUE;
13786         }
13787
13788         /* Format move number */
13789         if ((i % 2) == 0)
13790           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13791         else
13792           if (newblock)
13793             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13794           else
13795             numtext[0] = NULLCHAR;
13796
13797         numlen = strlen(numtext);
13798         newblock = FALSE;
13799
13800         /* Print move number */
13801         blank = linelen > 0 && numlen > 0;
13802         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13803             fprintf(f, "\n");
13804             linelen = 0;
13805             blank = 0;
13806         }
13807         if (blank) {
13808             fprintf(f, " ");
13809             linelen++;
13810         }
13811         fprintf(f, "%s", numtext);
13812         linelen += numlen;
13813
13814         /* Get move */
13815         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13816         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13817
13818         /* Print move */
13819         blank = linelen > 0 && movelen > 0;
13820         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13821             fprintf(f, "\n");
13822             linelen = 0;
13823             blank = 0;
13824         }
13825         if (blank) {
13826             fprintf(f, " ");
13827             linelen++;
13828         }
13829         fprintf(f, "%s", move_buffer);
13830         linelen += movelen;
13831
13832         /* [AS] Add PV info if present */
13833         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13834             /* [HGM] add time */
13835             char buf[MSG_SIZ]; int seconds;
13836
13837             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13838
13839             if( seconds <= 0)
13840               buf[0] = 0;
13841             else
13842               if( seconds < 30 )
13843                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13844               else
13845                 {
13846                   seconds = (seconds + 4)/10; // round to full seconds
13847                   if( seconds < 60 )
13848                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13849                   else
13850                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13851                 }
13852
13853             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13854                       pvInfoList[i].score >= 0 ? "+" : "",
13855                       pvInfoList[i].score / 100.0,
13856                       pvInfoList[i].depth,
13857                       buf );
13858
13859             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13860
13861             /* Print score/depth */
13862             blank = linelen > 0 && movelen > 0;
13863             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13864                 fprintf(f, "\n");
13865                 linelen = 0;
13866                 blank = 0;
13867             }
13868             if (blank) {
13869                 fprintf(f, " ");
13870                 linelen++;
13871             }
13872             fprintf(f, "%s", move_buffer);
13873             linelen += movelen;
13874         }
13875
13876         i++;
13877     }
13878
13879     /* Start a new line */
13880     if (linelen > 0) fprintf(f, "\n");
13881
13882     /* Print comments after last move */
13883     if (commentList[i] != NULL) {
13884         fprintf(f, "%s\n", commentList[i]);
13885     }
13886
13887     /* Print result */
13888     if (gameInfo.resultDetails != NULL &&
13889         gameInfo.resultDetails[0] != NULLCHAR) {
13890         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13891         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13892            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13893             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13894         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13895     } else {
13896         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13897     }
13898 }
13899
13900 /* Save game in PGN style and close the file */
13901 int
13902 SaveGamePGN (FILE *f)
13903 {
13904     SaveGamePGN2(f);
13905     fclose(f);
13906     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13907     return TRUE;
13908 }
13909
13910 /* Save game in old style and close the file */
13911 int
13912 SaveGameOldStyle (FILE *f)
13913 {
13914     int i, offset;
13915     time_t tm;
13916
13917     tm = time((time_t *) NULL);
13918
13919     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13920     PrintOpponents(f);
13921
13922     if (backwardMostMove > 0 || startedFromSetupPosition) {
13923         fprintf(f, "\n[--------------\n");
13924         PrintPosition(f, backwardMostMove);
13925         fprintf(f, "--------------]\n");
13926     } else {
13927         fprintf(f, "\n");
13928     }
13929
13930     i = backwardMostMove;
13931     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13932
13933     while (i < forwardMostMove) {
13934         if (commentList[i] != NULL) {
13935             fprintf(f, "[%s]\n", commentList[i]);
13936         }
13937
13938         if ((i % 2) == 1) {
13939             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13940             i++;
13941         } else {
13942             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13943             i++;
13944             if (commentList[i] != NULL) {
13945                 fprintf(f, "\n");
13946                 continue;
13947             }
13948             if (i >= forwardMostMove) {
13949                 fprintf(f, "\n");
13950                 break;
13951             }
13952             fprintf(f, "%s\n", parseList[i]);
13953             i++;
13954         }
13955     }
13956
13957     if (commentList[i] != NULL) {
13958         fprintf(f, "[%s]\n", commentList[i]);
13959     }
13960
13961     /* This isn't really the old style, but it's close enough */
13962     if (gameInfo.resultDetails != NULL &&
13963         gameInfo.resultDetails[0] != NULLCHAR) {
13964         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13965                 gameInfo.resultDetails);
13966     } else {
13967         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13968     }
13969
13970     fclose(f);
13971     return TRUE;
13972 }
13973
13974 /* Save the current game to open file f and close the file */
13975 int
13976 SaveGame (FILE *f, int dummy, char *dummy2)
13977 {
13978     if (gameMode == EditPosition) EditPositionDone(TRUE);
13979     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13980     if (appData.oldSaveStyle)
13981       return SaveGameOldStyle(f);
13982     else
13983       return SaveGamePGN(f);
13984 }
13985
13986 /* Save the current position to the given file */
13987 int
13988 SavePositionToFile (char *filename)
13989 {
13990     FILE *f;
13991     char buf[MSG_SIZ];
13992
13993     if (strcmp(filename, "-") == 0) {
13994         return SavePosition(stdout, 0, NULL);
13995     } else {
13996         f = fopen(filename, "a");
13997         if (f == NULL) {
13998             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13999             DisplayError(buf, errno);
14000             return FALSE;
14001         } else {
14002             safeStrCpy(buf, lastMsg, MSG_SIZ);
14003             DisplayMessage(_("Waiting for access to save file"), "");
14004             flock(fileno(f), LOCK_EX); // [HGM] lock
14005             DisplayMessage(_("Saving position"), "");
14006             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14007             SavePosition(f, 0, NULL);
14008             DisplayMessage(buf, "");
14009             return TRUE;
14010         }
14011     }
14012 }
14013
14014 /* Save the current position to the given open file and close the file */
14015 int
14016 SavePosition (FILE *f, int dummy, char *dummy2)
14017 {
14018     time_t tm;
14019     char *fen;
14020
14021     if (gameMode == EditPosition) EditPositionDone(TRUE);
14022     if (appData.oldSaveStyle) {
14023         tm = time((time_t *) NULL);
14024
14025         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14026         PrintOpponents(f);
14027         fprintf(f, "[--------------\n");
14028         PrintPosition(f, currentMove);
14029         fprintf(f, "--------------]\n");
14030     } else {
14031         fen = PositionToFEN(currentMove, NULL, 1);
14032         fprintf(f, "%s\n", fen);
14033         free(fen);
14034     }
14035     fclose(f);
14036     return TRUE;
14037 }
14038
14039 void
14040 ReloadCmailMsgEvent (int unregister)
14041 {
14042 #if !WIN32
14043     static char *inFilename = NULL;
14044     static char *outFilename;
14045     int i;
14046     struct stat inbuf, outbuf;
14047     int status;
14048
14049     /* Any registered moves are unregistered if unregister is set, */
14050     /* i.e. invoked by the signal handler */
14051     if (unregister) {
14052         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14053             cmailMoveRegistered[i] = FALSE;
14054             if (cmailCommentList[i] != NULL) {
14055                 free(cmailCommentList[i]);
14056                 cmailCommentList[i] = NULL;
14057             }
14058         }
14059         nCmailMovesRegistered = 0;
14060     }
14061
14062     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14063         cmailResult[i] = CMAIL_NOT_RESULT;
14064     }
14065     nCmailResults = 0;
14066
14067     if (inFilename == NULL) {
14068         /* Because the filenames are static they only get malloced once  */
14069         /* and they never get freed                                      */
14070         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14071         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14072
14073         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14074         sprintf(outFilename, "%s.out", appData.cmailGameName);
14075     }
14076
14077     status = stat(outFilename, &outbuf);
14078     if (status < 0) {
14079         cmailMailedMove = FALSE;
14080     } else {
14081         status = stat(inFilename, &inbuf);
14082         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14083     }
14084
14085     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14086        counts the games, notes how each one terminated, etc.
14087
14088        It would be nice to remove this kludge and instead gather all
14089        the information while building the game list.  (And to keep it
14090        in the game list nodes instead of having a bunch of fixed-size
14091        parallel arrays.)  Note this will require getting each game's
14092        termination from the PGN tags, as the game list builder does
14093        not process the game moves.  --mann
14094        */
14095     cmailMsgLoaded = TRUE;
14096     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14097
14098     /* Load first game in the file or popup game menu */
14099     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14100
14101 #endif /* !WIN32 */
14102     return;
14103 }
14104
14105 int
14106 RegisterMove ()
14107 {
14108     FILE *f;
14109     char string[MSG_SIZ];
14110
14111     if (   cmailMailedMove
14112         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14113         return TRUE;            /* Allow free viewing  */
14114     }
14115
14116     /* Unregister move to ensure that we don't leave RegisterMove        */
14117     /* with the move registered when the conditions for registering no   */
14118     /* longer hold                                                       */
14119     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14120         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14121         nCmailMovesRegistered --;
14122
14123         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14124           {
14125               free(cmailCommentList[lastLoadGameNumber - 1]);
14126               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14127           }
14128     }
14129
14130     if (cmailOldMove == -1) {
14131         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14132         return FALSE;
14133     }
14134
14135     if (currentMove > cmailOldMove + 1) {
14136         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14137         return FALSE;
14138     }
14139
14140     if (currentMove < cmailOldMove) {
14141         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14142         return FALSE;
14143     }
14144
14145     if (forwardMostMove > currentMove) {
14146         /* Silently truncate extra moves */
14147         TruncateGame();
14148     }
14149
14150     if (   (currentMove == cmailOldMove + 1)
14151         || (   (currentMove == cmailOldMove)
14152             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14153                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14154         if (gameInfo.result != GameUnfinished) {
14155             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14156         }
14157
14158         if (commentList[currentMove] != NULL) {
14159             cmailCommentList[lastLoadGameNumber - 1]
14160               = StrSave(commentList[currentMove]);
14161         }
14162         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14163
14164         if (appData.debugMode)
14165           fprintf(debugFP, "Saving %s for game %d\n",
14166                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14167
14168         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14169
14170         f = fopen(string, "w");
14171         if (appData.oldSaveStyle) {
14172             SaveGameOldStyle(f); /* also closes the file */
14173
14174             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14175             f = fopen(string, "w");
14176             SavePosition(f, 0, NULL); /* also closes the file */
14177         } else {
14178             fprintf(f, "{--------------\n");
14179             PrintPosition(f, currentMove);
14180             fprintf(f, "--------------}\n\n");
14181
14182             SaveGame(f, 0, NULL); /* also closes the file*/
14183         }
14184
14185         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14186         nCmailMovesRegistered ++;
14187     } else if (nCmailGames == 1) {
14188         DisplayError(_("You have not made a move yet"), 0);
14189         return FALSE;
14190     }
14191
14192     return TRUE;
14193 }
14194
14195 void
14196 MailMoveEvent ()
14197 {
14198 #if !WIN32
14199     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14200     FILE *commandOutput;
14201     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14202     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14203     int nBuffers;
14204     int i;
14205     int archived;
14206     char *arcDir;
14207
14208     if (! cmailMsgLoaded) {
14209         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14210         return;
14211     }
14212
14213     if (nCmailGames == nCmailResults) {
14214         DisplayError(_("No unfinished games"), 0);
14215         return;
14216     }
14217
14218 #if CMAIL_PROHIBIT_REMAIL
14219     if (cmailMailedMove) {
14220       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);
14221         DisplayError(msg, 0);
14222         return;
14223     }
14224 #endif
14225
14226     if (! (cmailMailedMove || RegisterMove())) return;
14227
14228     if (   cmailMailedMove
14229         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14230       snprintf(string, MSG_SIZ, partCommandString,
14231                appData.debugMode ? " -v" : "", appData.cmailGameName);
14232         commandOutput = popen(string, "r");
14233
14234         if (commandOutput == NULL) {
14235             DisplayError(_("Failed to invoke cmail"), 0);
14236         } else {
14237             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14238                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14239             }
14240             if (nBuffers > 1) {
14241                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14242                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14243                 nBytes = MSG_SIZ - 1;
14244             } else {
14245                 (void) memcpy(msg, buffer, nBytes);
14246             }
14247             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14248
14249             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14250                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14251
14252                 archived = TRUE;
14253                 for (i = 0; i < nCmailGames; i ++) {
14254                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14255                         archived = FALSE;
14256                     }
14257                 }
14258                 if (   archived
14259                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14260                         != NULL)) {
14261                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14262                            arcDir,
14263                            appData.cmailGameName,
14264                            gameInfo.date);
14265                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14266                     cmailMsgLoaded = FALSE;
14267                 }
14268             }
14269
14270             DisplayInformation(msg);
14271             pclose(commandOutput);
14272         }
14273     } else {
14274         if ((*cmailMsg) != '\0') {
14275             DisplayInformation(cmailMsg);
14276         }
14277     }
14278
14279     return;
14280 #endif /* !WIN32 */
14281 }
14282
14283 char *
14284 CmailMsg ()
14285 {
14286 #if WIN32
14287     return NULL;
14288 #else
14289     int  prependComma = 0;
14290     char number[5];
14291     char string[MSG_SIZ];       /* Space for game-list */
14292     int  i;
14293
14294     if (!cmailMsgLoaded) return "";
14295
14296     if (cmailMailedMove) {
14297       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14298     } else {
14299         /* Create a list of games left */
14300       snprintf(string, MSG_SIZ, "[");
14301         for (i = 0; i < nCmailGames; i ++) {
14302             if (! (   cmailMoveRegistered[i]
14303                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14304                 if (prependComma) {
14305                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14306                 } else {
14307                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14308                     prependComma = 1;
14309                 }
14310
14311                 strcat(string, number);
14312             }
14313         }
14314         strcat(string, "]");
14315
14316         if (nCmailMovesRegistered + nCmailResults == 0) {
14317             switch (nCmailGames) {
14318               case 1:
14319                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14320                 break;
14321
14322               case 2:
14323                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14324                 break;
14325
14326               default:
14327                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14328                          nCmailGames);
14329                 break;
14330             }
14331         } else {
14332             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14333               case 1:
14334                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14335                          string);
14336                 break;
14337
14338               case 0:
14339                 if (nCmailResults == nCmailGames) {
14340                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14341                 } else {
14342                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14343                 }
14344                 break;
14345
14346               default:
14347                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14348                          string);
14349             }
14350         }
14351     }
14352     return cmailMsg;
14353 #endif /* WIN32 */
14354 }
14355
14356 void
14357 ResetGameEvent ()
14358 {
14359     if (gameMode == Training)
14360       SetTrainingModeOff();
14361
14362     Reset(TRUE, TRUE);
14363     cmailMsgLoaded = FALSE;
14364     if (appData.icsActive) {
14365       SendToICS(ics_prefix);
14366       SendToICS("refresh\n");
14367     }
14368 }
14369
14370 void
14371 ExitEvent (int status)
14372 {
14373     exiting++;
14374     if (exiting > 2) {
14375       /* Give up on clean exit */
14376       exit(status);
14377     }
14378     if (exiting > 1) {
14379       /* Keep trying for clean exit */
14380       return;
14381     }
14382
14383     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14384     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14385
14386     if (telnetISR != NULL) {
14387       RemoveInputSource(telnetISR);
14388     }
14389     if (icsPR != NoProc) {
14390       DestroyChildProcess(icsPR, TRUE);
14391     }
14392
14393     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14394     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14395
14396     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14397     /* make sure this other one finishes before killing it!                  */
14398     if(endingGame) { int count = 0;
14399         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14400         while(endingGame && count++ < 10) DoSleep(1);
14401         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14402     }
14403
14404     /* Kill off chess programs */
14405     if (first.pr != NoProc) {
14406         ExitAnalyzeMode();
14407
14408         DoSleep( appData.delayBeforeQuit );
14409         SendToProgram("quit\n", &first);
14410         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14411     }
14412     if (second.pr != NoProc) {
14413         DoSleep( appData.delayBeforeQuit );
14414         SendToProgram("quit\n", &second);
14415         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14416     }
14417     if (first.isr != NULL) {
14418         RemoveInputSource(first.isr);
14419     }
14420     if (second.isr != NULL) {
14421         RemoveInputSource(second.isr);
14422     }
14423
14424     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14425     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14426
14427     ShutDownFrontEnd();
14428     exit(status);
14429 }
14430
14431 void
14432 PauseEngine (ChessProgramState *cps)
14433 {
14434     SendToProgram("pause\n", cps);
14435     cps->pause = 2;
14436 }
14437
14438 void
14439 UnPauseEngine (ChessProgramState *cps)
14440 {
14441     SendToProgram("resume\n", cps);
14442     cps->pause = 1;
14443 }
14444
14445 void
14446 PauseEvent ()
14447 {
14448     if (appData.debugMode)
14449         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14450     if (pausing) {
14451         pausing = FALSE;
14452         ModeHighlight();
14453         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14454             StartClocks();
14455             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14456                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14457                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14458             }
14459             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14460             HandleMachineMove(stashedInputMove, stalledEngine);
14461             stalledEngine = NULL;
14462             return;
14463         }
14464         if (gameMode == MachinePlaysWhite ||
14465             gameMode == TwoMachinesPlay   ||
14466             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14467             if(first.pause)  UnPauseEngine(&first);
14468             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14469             if(second.pause) UnPauseEngine(&second);
14470             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14471             StartClocks();
14472         } else {
14473             DisplayBothClocks();
14474         }
14475         if (gameMode == PlayFromGameFile) {
14476             if (appData.timeDelay >= 0)
14477                 AutoPlayGameLoop();
14478         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14479             Reset(FALSE, TRUE);
14480             SendToICS(ics_prefix);
14481             SendToICS("refresh\n");
14482         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14483             ForwardInner(forwardMostMove);
14484         }
14485         pauseExamInvalid = FALSE;
14486     } else {
14487         switch (gameMode) {
14488           default:
14489             return;
14490           case IcsExamining:
14491             pauseExamForwardMostMove = forwardMostMove;
14492             pauseExamInvalid = FALSE;
14493             /* fall through */
14494           case IcsObserving:
14495           case IcsPlayingWhite:
14496           case IcsPlayingBlack:
14497             pausing = TRUE;
14498             ModeHighlight();
14499             return;
14500           case PlayFromGameFile:
14501             (void) StopLoadGameTimer();
14502             pausing = TRUE;
14503             ModeHighlight();
14504             break;
14505           case BeginningOfGame:
14506             if (appData.icsActive) return;
14507             /* else fall through */
14508           case MachinePlaysWhite:
14509           case MachinePlaysBlack:
14510           case TwoMachinesPlay:
14511             if (forwardMostMove == 0)
14512               return;           /* don't pause if no one has moved */
14513             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14514                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14515                 if(onMove->pause) {           // thinking engine can be paused
14516                     PauseEngine(onMove);      // do it
14517                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14518                         PauseEngine(onMove->other);
14519                     else
14520                         SendToProgram("easy\n", onMove->other);
14521                     StopClocks();
14522                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14523             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14524                 if(first.pause) {
14525                     PauseEngine(&first);
14526                     StopClocks();
14527                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14528             } else { // human on move, pause pondering by either method
14529                 if(first.pause)
14530                     PauseEngine(&first);
14531                 else if(appData.ponderNextMove)
14532                     SendToProgram("easy\n", &first);
14533                 StopClocks();
14534             }
14535             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14536           case AnalyzeMode:
14537             pausing = TRUE;
14538             ModeHighlight();
14539             break;
14540         }
14541     }
14542 }
14543
14544 void
14545 EditCommentEvent ()
14546 {
14547     char title[MSG_SIZ];
14548
14549     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14550       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14551     } else {
14552       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14553                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14554                parseList[currentMove - 1]);
14555     }
14556
14557     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14558 }
14559
14560
14561 void
14562 EditTagsEvent ()
14563 {
14564     char *tags = PGNTags(&gameInfo);
14565     bookUp = FALSE;
14566     EditTagsPopUp(tags, NULL);
14567     free(tags);
14568 }
14569
14570 void
14571 ToggleSecond ()
14572 {
14573   if(second.analyzing) {
14574     SendToProgram("exit\n", &second);
14575     second.analyzing = FALSE;
14576   } else {
14577     if (second.pr == NoProc) StartChessProgram(&second);
14578     InitChessProgram(&second, FALSE);
14579     FeedMovesToProgram(&second, currentMove);
14580
14581     SendToProgram("analyze\n", &second);
14582     second.analyzing = TRUE;
14583   }
14584 }
14585
14586 /* Toggle ShowThinking */
14587 void
14588 ToggleShowThinking()
14589 {
14590   appData.showThinking = !appData.showThinking;
14591   ShowThinkingEvent();
14592 }
14593
14594 int
14595 AnalyzeModeEvent ()
14596 {
14597     char buf[MSG_SIZ];
14598
14599     if (!first.analysisSupport) {
14600       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14601       DisplayError(buf, 0);
14602       return 0;
14603     }
14604     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14605     if (appData.icsActive) {
14606         if (gameMode != IcsObserving) {
14607           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14608             DisplayError(buf, 0);
14609             /* secure check */
14610             if (appData.icsEngineAnalyze) {
14611                 if (appData.debugMode)
14612                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14613                 ExitAnalyzeMode();
14614                 ModeHighlight();
14615             }
14616             return 0;
14617         }
14618         /* if enable, user wants to disable icsEngineAnalyze */
14619         if (appData.icsEngineAnalyze) {
14620                 ExitAnalyzeMode();
14621                 ModeHighlight();
14622                 return 0;
14623         }
14624         appData.icsEngineAnalyze = TRUE;
14625         if (appData.debugMode)
14626             fprintf(debugFP, "ICS engine analyze starting... \n");
14627     }
14628
14629     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14630     if (appData.noChessProgram || gameMode == AnalyzeMode)
14631       return 0;
14632
14633     if (gameMode != AnalyzeFile) {
14634         if (!appData.icsEngineAnalyze) {
14635                EditGameEvent();
14636                if (gameMode != EditGame) return 0;
14637         }
14638         if (!appData.showThinking) ToggleShowThinking();
14639         ResurrectChessProgram();
14640         SendToProgram("analyze\n", &first);
14641         first.analyzing = TRUE;
14642         /*first.maybeThinking = TRUE;*/
14643         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14644         EngineOutputPopUp();
14645     }
14646     if (!appData.icsEngineAnalyze) {
14647         gameMode = AnalyzeMode;
14648         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14649     }
14650     pausing = FALSE;
14651     ModeHighlight();
14652     SetGameInfo();
14653
14654     StartAnalysisClock();
14655     GetTimeMark(&lastNodeCountTime);
14656     lastNodeCount = 0;
14657     return 1;
14658 }
14659
14660 void
14661 AnalyzeFileEvent ()
14662 {
14663     if (appData.noChessProgram || gameMode == AnalyzeFile)
14664       return;
14665
14666     if (!first.analysisSupport) {
14667       char buf[MSG_SIZ];
14668       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14669       DisplayError(buf, 0);
14670       return;
14671     }
14672
14673     if (gameMode != AnalyzeMode) {
14674         keepInfo = 1; // mere annotating should not alter PGN tags
14675         EditGameEvent();
14676         keepInfo = 0;
14677         if (gameMode != EditGame) return;
14678         if (!appData.showThinking) ToggleShowThinking();
14679         ResurrectChessProgram();
14680         SendToProgram("analyze\n", &first);
14681         first.analyzing = TRUE;
14682         /*first.maybeThinking = TRUE;*/
14683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14684         EngineOutputPopUp();
14685     }
14686     gameMode = AnalyzeFile;
14687     pausing = FALSE;
14688     ModeHighlight();
14689
14690     StartAnalysisClock();
14691     GetTimeMark(&lastNodeCountTime);
14692     lastNodeCount = 0;
14693     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14694     AnalysisPeriodicEvent(1);
14695 }
14696
14697 void
14698 MachineWhiteEvent ()
14699 {
14700     char buf[MSG_SIZ];
14701     char *bookHit = NULL;
14702
14703     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14704       return;
14705
14706
14707     if (gameMode == PlayFromGameFile ||
14708         gameMode == TwoMachinesPlay  ||
14709         gameMode == Training         ||
14710         gameMode == AnalyzeMode      ||
14711         gameMode == EndOfGame)
14712         EditGameEvent();
14713
14714     if (gameMode == EditPosition)
14715         EditPositionDone(TRUE);
14716
14717     if (!WhiteOnMove(currentMove)) {
14718         DisplayError(_("It is not White's turn"), 0);
14719         return;
14720     }
14721
14722     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14723       ExitAnalyzeMode();
14724
14725     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14726         gameMode == AnalyzeFile)
14727         TruncateGame();
14728
14729     ResurrectChessProgram();    /* in case it isn't running */
14730     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14731         gameMode = MachinePlaysWhite;
14732         ResetClocks();
14733     } else
14734     gameMode = MachinePlaysWhite;
14735     pausing = FALSE;
14736     ModeHighlight();
14737     SetGameInfo();
14738     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14739     DisplayTitle(buf);
14740     if (first.sendName) {
14741       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14742       SendToProgram(buf, &first);
14743     }
14744     if (first.sendTime) {
14745       if (first.useColors) {
14746         SendToProgram("black\n", &first); /*gnu kludge*/
14747       }
14748       SendTimeRemaining(&first, TRUE);
14749     }
14750     if (first.useColors) {
14751       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14752     }
14753     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14754     SetMachineThinkingEnables();
14755     first.maybeThinking = TRUE;
14756     StartClocks();
14757     firstMove = FALSE;
14758
14759     if (appData.autoFlipView && !flipView) {
14760       flipView = !flipView;
14761       DrawPosition(FALSE, NULL);
14762       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14763     }
14764
14765     if(bookHit) { // [HGM] book: simulate book reply
14766         static char bookMove[MSG_SIZ]; // a bit generous?
14767
14768         programStats.nodes = programStats.depth = programStats.time =
14769         programStats.score = programStats.got_only_move = 0;
14770         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14771
14772         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14773         strcat(bookMove, bookHit);
14774         HandleMachineMove(bookMove, &first);
14775     }
14776 }
14777
14778 void
14779 MachineBlackEvent ()
14780 {
14781   char buf[MSG_SIZ];
14782   char *bookHit = NULL;
14783
14784     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14785         return;
14786
14787
14788     if (gameMode == PlayFromGameFile ||
14789         gameMode == TwoMachinesPlay  ||
14790         gameMode == Training         ||
14791         gameMode == AnalyzeMode      ||
14792         gameMode == EndOfGame)
14793         EditGameEvent();
14794
14795     if (gameMode == EditPosition)
14796         EditPositionDone(TRUE);
14797
14798     if (WhiteOnMove(currentMove)) {
14799         DisplayError(_("It is not Black's turn"), 0);
14800         return;
14801     }
14802
14803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14804       ExitAnalyzeMode();
14805
14806     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14807         gameMode == AnalyzeFile)
14808         TruncateGame();
14809
14810     ResurrectChessProgram();    /* in case it isn't running */
14811     gameMode = MachinePlaysBlack;
14812     pausing = FALSE;
14813     ModeHighlight();
14814     SetGameInfo();
14815     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14816     DisplayTitle(buf);
14817     if (first.sendName) {
14818       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14819       SendToProgram(buf, &first);
14820     }
14821     if (first.sendTime) {
14822       if (first.useColors) {
14823         SendToProgram("white\n", &first); /*gnu kludge*/
14824       }
14825       SendTimeRemaining(&first, FALSE);
14826     }
14827     if (first.useColors) {
14828       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14829     }
14830     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14831     SetMachineThinkingEnables();
14832     first.maybeThinking = TRUE;
14833     StartClocks();
14834
14835     if (appData.autoFlipView && flipView) {
14836       flipView = !flipView;
14837       DrawPosition(FALSE, NULL);
14838       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14839     }
14840     if(bookHit) { // [HGM] book: simulate book reply
14841         static char bookMove[MSG_SIZ]; // a bit generous?
14842
14843         programStats.nodes = programStats.depth = programStats.time =
14844         programStats.score = programStats.got_only_move = 0;
14845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14846
14847         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14848         strcat(bookMove, bookHit);
14849         HandleMachineMove(bookMove, &first);
14850     }
14851 }
14852
14853
14854 void
14855 DisplayTwoMachinesTitle ()
14856 {
14857     char buf[MSG_SIZ];
14858     if (appData.matchGames > 0) {
14859         if(appData.tourneyFile[0]) {
14860           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14861                    gameInfo.white, _("vs."), gameInfo.black,
14862                    nextGame+1, appData.matchGames+1,
14863                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14864         } else
14865         if (first.twoMachinesColor[0] == 'w') {
14866           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14867                    gameInfo.white, _("vs."),  gameInfo.black,
14868                    first.matchWins, second.matchWins,
14869                    matchGame - 1 - (first.matchWins + second.matchWins));
14870         } else {
14871           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14872                    gameInfo.white, _("vs."), gameInfo.black,
14873                    second.matchWins, first.matchWins,
14874                    matchGame - 1 - (first.matchWins + second.matchWins));
14875         }
14876     } else {
14877       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14878     }
14879     DisplayTitle(buf);
14880 }
14881
14882 void
14883 SettingsMenuIfReady ()
14884 {
14885   if (second.lastPing != second.lastPong) {
14886     DisplayMessage("", _("Waiting for second chess program"));
14887     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14888     return;
14889   }
14890   ThawUI();
14891   DisplayMessage("", "");
14892   SettingsPopUp(&second);
14893 }
14894
14895 int
14896 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14897 {
14898     char buf[MSG_SIZ];
14899     if (cps->pr == NoProc) {
14900         StartChessProgram(cps);
14901         if (cps->protocolVersion == 1) {
14902           retry();
14903           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14904         } else {
14905           /* kludge: allow timeout for initial "feature" command */
14906           if(retry != TwoMachinesEventIfReady) FreezeUI();
14907           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14908           DisplayMessage("", buf);
14909           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14910         }
14911         return 1;
14912     }
14913     return 0;
14914 }
14915
14916 void
14917 TwoMachinesEvent P((void))
14918 {
14919     int i;
14920     char buf[MSG_SIZ];
14921     ChessProgramState *onmove;
14922     char *bookHit = NULL;
14923     static int stalling = 0;
14924     TimeMark now;
14925     long wait;
14926
14927     if (appData.noChessProgram) return;
14928
14929     switch (gameMode) {
14930       case TwoMachinesPlay:
14931         return;
14932       case MachinePlaysWhite:
14933       case MachinePlaysBlack:
14934         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14935             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14936             return;
14937         }
14938         /* fall through */
14939       case BeginningOfGame:
14940       case PlayFromGameFile:
14941       case EndOfGame:
14942         EditGameEvent();
14943         if (gameMode != EditGame) return;
14944         break;
14945       case EditPosition:
14946         EditPositionDone(TRUE);
14947         break;
14948       case AnalyzeMode:
14949       case AnalyzeFile:
14950         ExitAnalyzeMode();
14951         break;
14952       case EditGame:
14953       default:
14954         break;
14955     }
14956
14957 //    forwardMostMove = currentMove;
14958     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14959     startingEngine = TRUE;
14960
14961     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14962
14963     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14964     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14965       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14966       return;
14967     }
14968     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14969
14970     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14971                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14972         startingEngine = matchMode = FALSE;
14973         DisplayError("second engine does not play this", 0);
14974         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14975         EditGameEvent(); // switch back to EditGame mode
14976         return;
14977     }
14978
14979     if(!stalling) {
14980       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14981       SendToProgram("force\n", &second);
14982       stalling = 1;
14983       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14984       return;
14985     }
14986     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14987     if(appData.matchPause>10000 || appData.matchPause<10)
14988                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14989     wait = SubtractTimeMarks(&now, &pauseStart);
14990     if(wait < appData.matchPause) {
14991         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14992         return;
14993     }
14994     // we are now committed to starting the game
14995     stalling = 0;
14996     DisplayMessage("", "");
14997     if (startedFromSetupPosition) {
14998         SendBoard(&second, backwardMostMove);
14999     if (appData.debugMode) {
15000         fprintf(debugFP, "Two Machines\n");
15001     }
15002     }
15003     for (i = backwardMostMove; i < forwardMostMove; i++) {
15004         SendMoveToProgram(i, &second);
15005     }
15006
15007     gameMode = TwoMachinesPlay;
15008     pausing = startingEngine = FALSE;
15009     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15010     SetGameInfo();
15011     DisplayTwoMachinesTitle();
15012     firstMove = TRUE;
15013     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15014         onmove = &first;
15015     } else {
15016         onmove = &second;
15017     }
15018     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15019     SendToProgram(first.computerString, &first);
15020     if (first.sendName) {
15021       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15022       SendToProgram(buf, &first);
15023     }
15024     SendToProgram(second.computerString, &second);
15025     if (second.sendName) {
15026       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15027       SendToProgram(buf, &second);
15028     }
15029
15030     ResetClocks();
15031     if (!first.sendTime || !second.sendTime) {
15032         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15033         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15034     }
15035     if (onmove->sendTime) {
15036       if (onmove->useColors) {
15037         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15038       }
15039       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15040     }
15041     if (onmove->useColors) {
15042       SendToProgram(onmove->twoMachinesColor, onmove);
15043     }
15044     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15045 //    SendToProgram("go\n", onmove);
15046     onmove->maybeThinking = TRUE;
15047     SetMachineThinkingEnables();
15048
15049     StartClocks();
15050
15051     if(bookHit) { // [HGM] book: simulate book reply
15052         static char bookMove[MSG_SIZ]; // a bit generous?
15053
15054         programStats.nodes = programStats.depth = programStats.time =
15055         programStats.score = programStats.got_only_move = 0;
15056         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15057
15058         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15059         strcat(bookMove, bookHit);
15060         savedMessage = bookMove; // args for deferred call
15061         savedState = onmove;
15062         ScheduleDelayedEvent(DeferredBookMove, 1);
15063     }
15064 }
15065
15066 void
15067 TrainingEvent ()
15068 {
15069     if (gameMode == Training) {
15070       SetTrainingModeOff();
15071       gameMode = PlayFromGameFile;
15072       DisplayMessage("", _("Training mode off"));
15073     } else {
15074       gameMode = Training;
15075       animateTraining = appData.animate;
15076
15077       /* make sure we are not already at the end of the game */
15078       if (currentMove < forwardMostMove) {
15079         SetTrainingModeOn();
15080         DisplayMessage("", _("Training mode on"));
15081       } else {
15082         gameMode = PlayFromGameFile;
15083         DisplayError(_("Already at end of game"), 0);
15084       }
15085     }
15086     ModeHighlight();
15087 }
15088
15089 void
15090 IcsClientEvent ()
15091 {
15092     if (!appData.icsActive) return;
15093     switch (gameMode) {
15094       case IcsPlayingWhite:
15095       case IcsPlayingBlack:
15096       case IcsObserving:
15097       case IcsIdle:
15098       case BeginningOfGame:
15099       case IcsExamining:
15100         return;
15101
15102       case EditGame:
15103         break;
15104
15105       case EditPosition:
15106         EditPositionDone(TRUE);
15107         break;
15108
15109       case AnalyzeMode:
15110       case AnalyzeFile:
15111         ExitAnalyzeMode();
15112         break;
15113
15114       default:
15115         EditGameEvent();
15116         break;
15117     }
15118
15119     gameMode = IcsIdle;
15120     ModeHighlight();
15121     return;
15122 }
15123
15124 void
15125 EditGameEvent ()
15126 {
15127     int i;
15128
15129     switch (gameMode) {
15130       case Training:
15131         SetTrainingModeOff();
15132         break;
15133       case MachinePlaysWhite:
15134       case MachinePlaysBlack:
15135       case BeginningOfGame:
15136         SendToProgram("force\n", &first);
15137         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15138             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15139                 char buf[MSG_SIZ];
15140                 abortEngineThink = TRUE;
15141                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15142                 SendToProgram(buf, &first);
15143                 DisplayMessage("Aborting engine think", "");
15144                 FreezeUI();
15145             }
15146         }
15147         SetUserThinkingEnables();
15148         break;
15149       case PlayFromGameFile:
15150         (void) StopLoadGameTimer();
15151         if (gameFileFP != NULL) {
15152             gameFileFP = NULL;
15153         }
15154         break;
15155       case EditPosition:
15156         EditPositionDone(TRUE);
15157         break;
15158       case AnalyzeMode:
15159       case AnalyzeFile:
15160         ExitAnalyzeMode();
15161         SendToProgram("force\n", &first);
15162         break;
15163       case TwoMachinesPlay:
15164         GameEnds(EndOfFile, NULL, GE_PLAYER);
15165         ResurrectChessProgram();
15166         SetUserThinkingEnables();
15167         break;
15168       case EndOfGame:
15169         ResurrectChessProgram();
15170         break;
15171       case IcsPlayingBlack:
15172       case IcsPlayingWhite:
15173         DisplayError(_("Warning: You are still playing a game"), 0);
15174         break;
15175       case IcsObserving:
15176         DisplayError(_("Warning: You are still observing a game"), 0);
15177         break;
15178       case IcsExamining:
15179         DisplayError(_("Warning: You are still examining a game"), 0);
15180         break;
15181       case IcsIdle:
15182         break;
15183       case EditGame:
15184       default:
15185         return;
15186     }
15187
15188     pausing = FALSE;
15189     StopClocks();
15190     first.offeredDraw = second.offeredDraw = 0;
15191
15192     if (gameMode == PlayFromGameFile) {
15193         whiteTimeRemaining = timeRemaining[0][currentMove];
15194         blackTimeRemaining = timeRemaining[1][currentMove];
15195         DisplayTitle("");
15196     }
15197
15198     if (gameMode == MachinePlaysWhite ||
15199         gameMode == MachinePlaysBlack ||
15200         gameMode == TwoMachinesPlay ||
15201         gameMode == EndOfGame) {
15202         i = forwardMostMove;
15203         while (i > currentMove) {
15204             SendToProgram("undo\n", &first);
15205             i--;
15206         }
15207         if(!adjustedClock) {
15208         whiteTimeRemaining = timeRemaining[0][currentMove];
15209         blackTimeRemaining = timeRemaining[1][currentMove];
15210         DisplayBothClocks();
15211         }
15212         if (whiteFlag || blackFlag) {
15213             whiteFlag = blackFlag = 0;
15214         }
15215         DisplayTitle("");
15216     }
15217
15218     gameMode = EditGame;
15219     ModeHighlight();
15220     SetGameInfo();
15221 }
15222
15223
15224 void
15225 EditPositionEvent ()
15226 {
15227     if (gameMode == EditPosition) {
15228         EditGameEvent();
15229         return;
15230     }
15231
15232     EditGameEvent();
15233     if (gameMode != EditGame) return;
15234
15235     gameMode = EditPosition;
15236     ModeHighlight();
15237     SetGameInfo();
15238     if (currentMove > 0)
15239       CopyBoard(boards[0], boards[currentMove]);
15240
15241     blackPlaysFirst = !WhiteOnMove(currentMove);
15242     ResetClocks();
15243     currentMove = forwardMostMove = backwardMostMove = 0;
15244     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15245     DisplayMove(-1);
15246     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15247 }
15248
15249 void
15250 ExitAnalyzeMode ()
15251 {
15252     /* [DM] icsEngineAnalyze - possible call from other functions */
15253     if (appData.icsEngineAnalyze) {
15254         appData.icsEngineAnalyze = FALSE;
15255
15256         DisplayMessage("",_("Close ICS engine analyze..."));
15257     }
15258     if (first.analysisSupport && first.analyzing) {
15259       SendToBoth("exit\n");
15260       first.analyzing = second.analyzing = FALSE;
15261     }
15262     thinkOutput[0] = NULLCHAR;
15263 }
15264
15265 void
15266 EditPositionDone (Boolean fakeRights)
15267 {
15268     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15269
15270     startedFromSetupPosition = TRUE;
15271     InitChessProgram(&first, FALSE);
15272     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15273       boards[0][EP_STATUS] = EP_NONE;
15274       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15275       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15276         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15277         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15278       } else boards[0][CASTLING][2] = NoRights;
15279       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15280         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15281         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15282       } else boards[0][CASTLING][5] = NoRights;
15283       if(gameInfo.variant == VariantSChess) {
15284         int i;
15285         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15286           boards[0][VIRGIN][i] = 0;
15287           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15288           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15289         }
15290       }
15291     }
15292     SendToProgram("force\n", &first);
15293     if (blackPlaysFirst) {
15294         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15295         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15296         currentMove = forwardMostMove = backwardMostMove = 1;
15297         CopyBoard(boards[1], boards[0]);
15298     } else {
15299         currentMove = forwardMostMove = backwardMostMove = 0;
15300     }
15301     SendBoard(&first, forwardMostMove);
15302     if (appData.debugMode) {
15303         fprintf(debugFP, "EditPosDone\n");
15304     }
15305     DisplayTitle("");
15306     DisplayMessage("", "");
15307     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15308     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15309     gameMode = EditGame;
15310     ModeHighlight();
15311     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15312     ClearHighlights(); /* [AS] */
15313 }
15314
15315 /* Pause for `ms' milliseconds */
15316 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15317 void
15318 TimeDelay (long ms)
15319 {
15320     TimeMark m1, m2;
15321
15322     GetTimeMark(&m1);
15323     do {
15324         GetTimeMark(&m2);
15325     } while (SubtractTimeMarks(&m2, &m1) < ms);
15326 }
15327
15328 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15329 void
15330 SendMultiLineToICS (char *buf)
15331 {
15332     char temp[MSG_SIZ+1], *p;
15333     int len;
15334
15335     len = strlen(buf);
15336     if (len > MSG_SIZ)
15337       len = MSG_SIZ;
15338
15339     strncpy(temp, buf, len);
15340     temp[len] = 0;
15341
15342     p = temp;
15343     while (*p) {
15344         if (*p == '\n' || *p == '\r')
15345           *p = ' ';
15346         ++p;
15347     }
15348
15349     strcat(temp, "\n");
15350     SendToICS(temp);
15351     SendToPlayer(temp, strlen(temp));
15352 }
15353
15354 void
15355 SetWhiteToPlayEvent ()
15356 {
15357     if (gameMode == EditPosition) {
15358         blackPlaysFirst = FALSE;
15359         DisplayBothClocks();    /* works because currentMove is 0 */
15360     } else if (gameMode == IcsExamining) {
15361         SendToICS(ics_prefix);
15362         SendToICS("tomove white\n");
15363     }
15364 }
15365
15366 void
15367 SetBlackToPlayEvent ()
15368 {
15369     if (gameMode == EditPosition) {
15370         blackPlaysFirst = TRUE;
15371         currentMove = 1;        /* kludge */
15372         DisplayBothClocks();
15373         currentMove = 0;
15374     } else if (gameMode == IcsExamining) {
15375         SendToICS(ics_prefix);
15376         SendToICS("tomove black\n");
15377     }
15378 }
15379
15380 void
15381 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15382 {
15383     char buf[MSG_SIZ];
15384     ChessSquare piece = boards[0][y][x];
15385     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15386     static int lastVariant;
15387
15388     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15389
15390     switch (selection) {
15391       case ClearBoard:
15392         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15393         MarkTargetSquares(1);
15394         CopyBoard(currentBoard, boards[0]);
15395         CopyBoard(menuBoard, initialPosition);
15396         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15397             SendToICS(ics_prefix);
15398             SendToICS("bsetup clear\n");
15399         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15400             SendToICS(ics_prefix);
15401             SendToICS("clearboard\n");
15402         } else {
15403             int nonEmpty = 0;
15404             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15405                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15406                 for (y = 0; y < BOARD_HEIGHT; y++) {
15407                     if (gameMode == IcsExamining) {
15408                         if (boards[currentMove][y][x] != EmptySquare) {
15409                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15410                                     AAA + x, ONE + y);
15411                             SendToICS(buf);
15412                         }
15413                     } else if(boards[0][y][x] != DarkSquare) {
15414                         if(boards[0][y][x] != p) nonEmpty++;
15415                         boards[0][y][x] = p;
15416                     }
15417                 }
15418             }
15419             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15420                 int r;
15421                 for(r = 0; r < BOARD_HEIGHT; r++) {
15422                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15423                     ChessSquare p = menuBoard[r][x];
15424                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15425                   }
15426                 }
15427                 DisplayMessage("Clicking clock again restores position", "");
15428                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15429                 if(!nonEmpty) { // asked to clear an empty board
15430                     CopyBoard(boards[0], menuBoard);
15431                 } else
15432                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15433                     CopyBoard(boards[0], initialPosition);
15434                 } else
15435                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15436                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15437                     CopyBoard(boards[0], erasedBoard);
15438                 } else
15439                     CopyBoard(erasedBoard, currentBoard);
15440
15441             }
15442         }
15443         if (gameMode == EditPosition) {
15444             DrawPosition(FALSE, boards[0]);
15445         }
15446         break;
15447
15448       case WhitePlay:
15449         SetWhiteToPlayEvent();
15450         break;
15451
15452       case BlackPlay:
15453         SetBlackToPlayEvent();
15454         break;
15455
15456       case EmptySquare:
15457         if (gameMode == IcsExamining) {
15458             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15459             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15460             SendToICS(buf);
15461         } else {
15462             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15463                 if(x == BOARD_LEFT-2) {
15464                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15465                     boards[0][y][1] = 0;
15466                 } else
15467                 if(x == BOARD_RGHT+1) {
15468                     if(y >= gameInfo.holdingsSize) break;
15469                     boards[0][y][BOARD_WIDTH-2] = 0;
15470                 } else break;
15471             }
15472             boards[0][y][x] = EmptySquare;
15473             DrawPosition(FALSE, boards[0]);
15474         }
15475         break;
15476
15477       case PromotePiece:
15478         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15479            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15480             selection = (ChessSquare) (PROMOTED(piece));
15481         } else if(piece == EmptySquare) selection = WhiteSilver;
15482         else selection = (ChessSquare)((int)piece - 1);
15483         goto defaultlabel;
15484
15485       case DemotePiece:
15486         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15487            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15488             selection = (ChessSquare) (DEMOTED(piece));
15489         } else if(piece == EmptySquare) selection = BlackSilver;
15490         else selection = (ChessSquare)((int)piece + 1);
15491         goto defaultlabel;
15492
15493       case WhiteQueen:
15494       case BlackQueen:
15495         if(gameInfo.variant == VariantShatranj ||
15496            gameInfo.variant == VariantXiangqi  ||
15497            gameInfo.variant == VariantCourier  ||
15498            gameInfo.variant == VariantASEAN    ||
15499            gameInfo.variant == VariantMakruk     )
15500             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15501         goto defaultlabel;
15502
15503       case WhiteKing:
15504       case BlackKing:
15505         if(gameInfo.variant == VariantXiangqi)
15506             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15507         if(gameInfo.variant == VariantKnightmate)
15508             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15509       default:
15510         defaultlabel:
15511         if (gameMode == IcsExamining) {
15512             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15513             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15514                      PieceToChar(selection), AAA + x, ONE + y);
15515             SendToICS(buf);
15516         } else {
15517             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15518                 int n;
15519                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15520                     n = PieceToNumber(selection - BlackPawn);
15521                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15522                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15523                     boards[0][BOARD_HEIGHT-1-n][1]++;
15524                 } else
15525                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15526                     n = PieceToNumber(selection);
15527                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15528                     boards[0][n][BOARD_WIDTH-1] = selection;
15529                     boards[0][n][BOARD_WIDTH-2]++;
15530                 }
15531             } else
15532             boards[0][y][x] = selection;
15533             DrawPosition(TRUE, boards[0]);
15534             ClearHighlights();
15535             fromX = fromY = -1;
15536         }
15537         break;
15538     }
15539 }
15540
15541
15542 void
15543 DropMenuEvent (ChessSquare selection, int x, int y)
15544 {
15545     ChessMove moveType;
15546
15547     switch (gameMode) {
15548       case IcsPlayingWhite:
15549       case MachinePlaysBlack:
15550         if (!WhiteOnMove(currentMove)) {
15551             DisplayMoveError(_("It is Black's turn"));
15552             return;
15553         }
15554         moveType = WhiteDrop;
15555         break;
15556       case IcsPlayingBlack:
15557       case MachinePlaysWhite:
15558         if (WhiteOnMove(currentMove)) {
15559             DisplayMoveError(_("It is White's turn"));
15560             return;
15561         }
15562         moveType = BlackDrop;
15563         break;
15564       case EditGame:
15565         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15566         break;
15567       default:
15568         return;
15569     }
15570
15571     if (moveType == BlackDrop && selection < BlackPawn) {
15572       selection = (ChessSquare) ((int) selection
15573                                  + (int) BlackPawn - (int) WhitePawn);
15574     }
15575     if (boards[currentMove][y][x] != EmptySquare) {
15576         DisplayMoveError(_("That square is occupied"));
15577         return;
15578     }
15579
15580     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15581 }
15582
15583 void
15584 AcceptEvent ()
15585 {
15586     /* Accept a pending offer of any kind from opponent */
15587
15588     if (appData.icsActive) {
15589         SendToICS(ics_prefix);
15590         SendToICS("accept\n");
15591     } else if (cmailMsgLoaded) {
15592         if (currentMove == cmailOldMove &&
15593             commentList[cmailOldMove] != NULL &&
15594             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15595                    "Black offers a draw" : "White offers a draw")) {
15596             TruncateGame();
15597             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15598             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15599         } else {
15600             DisplayError(_("There is no pending offer on this move"), 0);
15601             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15602         }
15603     } else {
15604         /* Not used for offers from chess program */
15605     }
15606 }
15607
15608 void
15609 DeclineEvent ()
15610 {
15611     /* Decline a pending offer of any kind from opponent */
15612
15613     if (appData.icsActive) {
15614         SendToICS(ics_prefix);
15615         SendToICS("decline\n");
15616     } else if (cmailMsgLoaded) {
15617         if (currentMove == cmailOldMove &&
15618             commentList[cmailOldMove] != NULL &&
15619             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15620                    "Black offers a draw" : "White offers a draw")) {
15621 #ifdef NOTDEF
15622             AppendComment(cmailOldMove, "Draw declined", TRUE);
15623             DisplayComment(cmailOldMove - 1, "Draw declined");
15624 #endif /*NOTDEF*/
15625         } else {
15626             DisplayError(_("There is no pending offer on this move"), 0);
15627         }
15628     } else {
15629         /* Not used for offers from chess program */
15630     }
15631 }
15632
15633 void
15634 RematchEvent ()
15635 {
15636     /* Issue ICS rematch command */
15637     if (appData.icsActive) {
15638         SendToICS(ics_prefix);
15639         SendToICS("rematch\n");
15640     }
15641 }
15642
15643 void
15644 CallFlagEvent ()
15645 {
15646     /* Call your opponent's flag (claim a win on time) */
15647     if (appData.icsActive) {
15648         SendToICS(ics_prefix);
15649         SendToICS("flag\n");
15650     } else {
15651         switch (gameMode) {
15652           default:
15653             return;
15654           case MachinePlaysWhite:
15655             if (whiteFlag) {
15656                 if (blackFlag)
15657                   GameEnds(GameIsDrawn, "Both players ran out of time",
15658                            GE_PLAYER);
15659                 else
15660                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15661             } else {
15662                 DisplayError(_("Your opponent is not out of time"), 0);
15663             }
15664             break;
15665           case MachinePlaysBlack:
15666             if (blackFlag) {
15667                 if (whiteFlag)
15668                   GameEnds(GameIsDrawn, "Both players ran out of time",
15669                            GE_PLAYER);
15670                 else
15671                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15672             } else {
15673                 DisplayError(_("Your opponent is not out of time"), 0);
15674             }
15675             break;
15676         }
15677     }
15678 }
15679
15680 void
15681 ClockClick (int which)
15682 {       // [HGM] code moved to back-end from winboard.c
15683         if(which) { // black clock
15684           if (gameMode == EditPosition || gameMode == IcsExamining) {
15685             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15686             SetBlackToPlayEvent();
15687           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15688                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15689           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15690           } else if (shiftKey) {
15691             AdjustClock(which, -1);
15692           } else if (gameMode == IcsPlayingWhite ||
15693                      gameMode == MachinePlaysBlack) {
15694             CallFlagEvent();
15695           }
15696         } else { // white clock
15697           if (gameMode == EditPosition || gameMode == IcsExamining) {
15698             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15699             SetWhiteToPlayEvent();
15700           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15701                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15702           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15703           } else if (shiftKey) {
15704             AdjustClock(which, -1);
15705           } else if (gameMode == IcsPlayingBlack ||
15706                    gameMode == MachinePlaysWhite) {
15707             CallFlagEvent();
15708           }
15709         }
15710 }
15711
15712 void
15713 DrawEvent ()
15714 {
15715     /* Offer draw or accept pending draw offer from opponent */
15716
15717     if (appData.icsActive) {
15718         /* Note: tournament rules require draw offers to be
15719            made after you make your move but before you punch
15720            your clock.  Currently ICS doesn't let you do that;
15721            instead, you immediately punch your clock after making
15722            a move, but you can offer a draw at any time. */
15723
15724         SendToICS(ics_prefix);
15725         SendToICS("draw\n");
15726         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15727     } else if (cmailMsgLoaded) {
15728         if (currentMove == cmailOldMove &&
15729             commentList[cmailOldMove] != NULL &&
15730             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15731                    "Black offers a draw" : "White offers a draw")) {
15732             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15733             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15734         } else if (currentMove == cmailOldMove + 1) {
15735             char *offer = WhiteOnMove(cmailOldMove) ?
15736               "White offers a draw" : "Black offers a draw";
15737             AppendComment(currentMove, offer, TRUE);
15738             DisplayComment(currentMove - 1, offer);
15739             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15740         } else {
15741             DisplayError(_("You must make your move before offering a draw"), 0);
15742             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15743         }
15744     } else if (first.offeredDraw) {
15745         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15746     } else {
15747         if (first.sendDrawOffers) {
15748             SendToProgram("draw\n", &first);
15749             userOfferedDraw = TRUE;
15750         }
15751     }
15752 }
15753
15754 void
15755 AdjournEvent ()
15756 {
15757     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15758
15759     if (appData.icsActive) {
15760         SendToICS(ics_prefix);
15761         SendToICS("adjourn\n");
15762     } else {
15763         /* Currently GNU Chess doesn't offer or accept Adjourns */
15764     }
15765 }
15766
15767
15768 void
15769 AbortEvent ()
15770 {
15771     /* Offer Abort or accept pending Abort offer from opponent */
15772
15773     if (appData.icsActive) {
15774         SendToICS(ics_prefix);
15775         SendToICS("abort\n");
15776     } else {
15777         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15778     }
15779 }
15780
15781 void
15782 ResignEvent ()
15783 {
15784     /* Resign.  You can do this even if it's not your turn. */
15785
15786     if (appData.icsActive) {
15787         SendToICS(ics_prefix);
15788         SendToICS("resign\n");
15789     } else {
15790         switch (gameMode) {
15791           case MachinePlaysWhite:
15792             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15793             break;
15794           case MachinePlaysBlack:
15795             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15796             break;
15797           case EditGame:
15798             if (cmailMsgLoaded) {
15799                 TruncateGame();
15800                 if (WhiteOnMove(cmailOldMove)) {
15801                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15802                 } else {
15803                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15804                 }
15805                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15806             }
15807             break;
15808           default:
15809             break;
15810         }
15811     }
15812 }
15813
15814
15815 void
15816 StopObservingEvent ()
15817 {
15818     /* Stop observing current games */
15819     SendToICS(ics_prefix);
15820     SendToICS("unobserve\n");
15821 }
15822
15823 void
15824 StopExaminingEvent ()
15825 {
15826     /* Stop observing current game */
15827     SendToICS(ics_prefix);
15828     SendToICS("unexamine\n");
15829 }
15830
15831 void
15832 ForwardInner (int target)
15833 {
15834     int limit; int oldSeekGraphUp = seekGraphUp;
15835
15836     if (appData.debugMode)
15837         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15838                 target, currentMove, forwardMostMove);
15839
15840     if (gameMode == EditPosition)
15841       return;
15842
15843     seekGraphUp = FALSE;
15844     MarkTargetSquares(1);
15845     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15846
15847     if (gameMode == PlayFromGameFile && !pausing)
15848       PauseEvent();
15849
15850     if (gameMode == IcsExamining && pausing)
15851       limit = pauseExamForwardMostMove;
15852     else
15853       limit = forwardMostMove;
15854
15855     if (target > limit) target = limit;
15856
15857     if (target > 0 && moveList[target - 1][0]) {
15858         int fromX, fromY, toX, toY;
15859         toX = moveList[target - 1][2] - AAA;
15860         toY = moveList[target - 1][3] - ONE;
15861         if (moveList[target - 1][1] == '@') {
15862             if (appData.highlightLastMove) {
15863                 SetHighlights(-1, -1, toX, toY);
15864             }
15865         } else {
15866             int viaX = moveList[target - 1][5] - AAA;
15867             int viaY = moveList[target - 1][6] - ONE;
15868             fromX = moveList[target - 1][0] - AAA;
15869             fromY = moveList[target - 1][1] - ONE;
15870             if (target == currentMove + 1) {
15871                 if(moveList[target - 1][4] == ';') { // multi-leg
15872                     ChessSquare piece = boards[currentMove][viaY][viaX];
15873                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15874                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15875                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15876                     boards[currentMove][viaY][viaX] = piece;
15877                 } else
15878                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15879             }
15880             if (appData.highlightLastMove) {
15881                 SetHighlights(fromX, fromY, toX, toY);
15882             }
15883         }
15884     }
15885     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15886         gameMode == Training || gameMode == PlayFromGameFile ||
15887         gameMode == AnalyzeFile) {
15888         while (currentMove < target) {
15889             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15890             SendMoveToProgram(currentMove++, &first);
15891         }
15892     } else {
15893         currentMove = target;
15894     }
15895
15896     if (gameMode == EditGame || gameMode == EndOfGame) {
15897         whiteTimeRemaining = timeRemaining[0][currentMove];
15898         blackTimeRemaining = timeRemaining[1][currentMove];
15899     }
15900     DisplayBothClocks();
15901     DisplayMove(currentMove - 1);
15902     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15903     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15904     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15905         DisplayComment(currentMove - 1, commentList[currentMove]);
15906     }
15907     ClearMap(); // [HGM] exclude: invalidate map
15908 }
15909
15910
15911 void
15912 ForwardEvent ()
15913 {
15914     if (gameMode == IcsExamining && !pausing) {
15915         SendToICS(ics_prefix);
15916         SendToICS("forward\n");
15917     } else {
15918         ForwardInner(currentMove + 1);
15919     }
15920 }
15921
15922 void
15923 ToEndEvent ()
15924 {
15925     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15926         /* to optimze, we temporarily turn off analysis mode while we feed
15927          * the remaining moves to the engine. Otherwise we get analysis output
15928          * after each move.
15929          */
15930         if (first.analysisSupport) {
15931           SendToProgram("exit\nforce\n", &first);
15932           first.analyzing = FALSE;
15933         }
15934     }
15935
15936     if (gameMode == IcsExamining && !pausing) {
15937         SendToICS(ics_prefix);
15938         SendToICS("forward 999999\n");
15939     } else {
15940         ForwardInner(forwardMostMove);
15941     }
15942
15943     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15944         /* we have fed all the moves, so reactivate analysis mode */
15945         SendToProgram("analyze\n", &first);
15946         first.analyzing = TRUE;
15947         /*first.maybeThinking = TRUE;*/
15948         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15949     }
15950 }
15951
15952 void
15953 BackwardInner (int target)
15954 {
15955     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15956
15957     if (appData.debugMode)
15958         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15959                 target, currentMove, forwardMostMove);
15960
15961     if (gameMode == EditPosition) return;
15962     seekGraphUp = FALSE;
15963     MarkTargetSquares(1);
15964     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15965     if (currentMove <= backwardMostMove) {
15966         ClearHighlights();
15967         DrawPosition(full_redraw, boards[currentMove]);
15968         return;
15969     }
15970     if (gameMode == PlayFromGameFile && !pausing)
15971       PauseEvent();
15972
15973     if (moveList[target][0]) {
15974         int fromX, fromY, toX, toY;
15975         toX = moveList[target][2] - AAA;
15976         toY = moveList[target][3] - ONE;
15977         if (moveList[target][1] == '@') {
15978             if (appData.highlightLastMove) {
15979                 SetHighlights(-1, -1, toX, toY);
15980             }
15981         } else {
15982             fromX = moveList[target][0] - AAA;
15983             fromY = moveList[target][1] - ONE;
15984             if (target == currentMove - 1) {
15985                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15986             }
15987             if (appData.highlightLastMove) {
15988                 SetHighlights(fromX, fromY, toX, toY);
15989             }
15990         }
15991     }
15992     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15993         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15994         while (currentMove > target) {
15995             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15996                 // null move cannot be undone. Reload program with move history before it.
15997                 int i;
15998                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15999                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16000                 }
16001                 SendBoard(&first, i);
16002               if(second.analyzing) SendBoard(&second, i);
16003                 for(currentMove=i; currentMove<target; currentMove++) {
16004                     SendMoveToProgram(currentMove, &first);
16005                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16006                 }
16007                 break;
16008             }
16009             SendToBoth("undo\n");
16010             currentMove--;
16011         }
16012     } else {
16013         currentMove = target;
16014     }
16015
16016     if (gameMode == EditGame || gameMode == EndOfGame) {
16017         whiteTimeRemaining = timeRemaining[0][currentMove];
16018         blackTimeRemaining = timeRemaining[1][currentMove];
16019     }
16020     DisplayBothClocks();
16021     DisplayMove(currentMove - 1);
16022     DrawPosition(full_redraw, boards[currentMove]);
16023     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16024     // [HGM] PV info: routine tests if comment empty
16025     DisplayComment(currentMove - 1, commentList[currentMove]);
16026     ClearMap(); // [HGM] exclude: invalidate map
16027 }
16028
16029 void
16030 BackwardEvent ()
16031 {
16032     if (gameMode == IcsExamining && !pausing) {
16033         SendToICS(ics_prefix);
16034         SendToICS("backward\n");
16035     } else {
16036         BackwardInner(currentMove - 1);
16037     }
16038 }
16039
16040 void
16041 ToStartEvent ()
16042 {
16043     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16044         /* to optimize, we temporarily turn off analysis mode while we undo
16045          * all the moves. Otherwise we get analysis output after each undo.
16046          */
16047         if (first.analysisSupport) {
16048           SendToProgram("exit\nforce\n", &first);
16049           first.analyzing = FALSE;
16050         }
16051     }
16052
16053     if (gameMode == IcsExamining && !pausing) {
16054         SendToICS(ics_prefix);
16055         SendToICS("backward 999999\n");
16056     } else {
16057         BackwardInner(backwardMostMove);
16058     }
16059
16060     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16061         /* we have fed all the moves, so reactivate analysis mode */
16062         SendToProgram("analyze\n", &first);
16063         first.analyzing = TRUE;
16064         /*first.maybeThinking = TRUE;*/
16065         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16066     }
16067 }
16068
16069 void
16070 ToNrEvent (int to)
16071 {
16072   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16073   if (to >= forwardMostMove) to = forwardMostMove;
16074   if (to <= backwardMostMove) to = backwardMostMove;
16075   if (to < currentMove) {
16076     BackwardInner(to);
16077   } else {
16078     ForwardInner(to);
16079   }
16080 }
16081
16082 void
16083 RevertEvent (Boolean annotate)
16084 {
16085     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16086         return;
16087     }
16088     if (gameMode != IcsExamining) {
16089         DisplayError(_("You are not examining a game"), 0);
16090         return;
16091     }
16092     if (pausing) {
16093         DisplayError(_("You can't revert while pausing"), 0);
16094         return;
16095     }
16096     SendToICS(ics_prefix);
16097     SendToICS("revert\n");
16098 }
16099
16100 void
16101 RetractMoveEvent ()
16102 {
16103     switch (gameMode) {
16104       case MachinePlaysWhite:
16105       case MachinePlaysBlack:
16106         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16107             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16108             return;
16109         }
16110         if (forwardMostMove < 2) return;
16111         currentMove = forwardMostMove = forwardMostMove - 2;
16112         whiteTimeRemaining = timeRemaining[0][currentMove];
16113         blackTimeRemaining = timeRemaining[1][currentMove];
16114         DisplayBothClocks();
16115         DisplayMove(currentMove - 1);
16116         ClearHighlights();/*!! could figure this out*/
16117         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16118         SendToProgram("remove\n", &first);
16119         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16120         break;
16121
16122       case BeginningOfGame:
16123       default:
16124         break;
16125
16126       case IcsPlayingWhite:
16127       case IcsPlayingBlack:
16128         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16129             SendToICS(ics_prefix);
16130             SendToICS("takeback 2\n");
16131         } else {
16132             SendToICS(ics_prefix);
16133             SendToICS("takeback 1\n");
16134         }
16135         break;
16136     }
16137 }
16138
16139 void
16140 MoveNowEvent ()
16141 {
16142     ChessProgramState *cps;
16143
16144     switch (gameMode) {
16145       case MachinePlaysWhite:
16146         if (!WhiteOnMove(forwardMostMove)) {
16147             DisplayError(_("It is your turn"), 0);
16148             return;
16149         }
16150         cps = &first;
16151         break;
16152       case MachinePlaysBlack:
16153         if (WhiteOnMove(forwardMostMove)) {
16154             DisplayError(_("It is your turn"), 0);
16155             return;
16156         }
16157         cps = &first;
16158         break;
16159       case TwoMachinesPlay:
16160         if (WhiteOnMove(forwardMostMove) ==
16161             (first.twoMachinesColor[0] == 'w')) {
16162             cps = &first;
16163         } else {
16164             cps = &second;
16165         }
16166         break;
16167       case BeginningOfGame:
16168       default:
16169         return;
16170     }
16171     SendToProgram("?\n", cps);
16172 }
16173
16174 void
16175 TruncateGameEvent ()
16176 {
16177     EditGameEvent();
16178     if (gameMode != EditGame) return;
16179     TruncateGame();
16180 }
16181
16182 void
16183 TruncateGame ()
16184 {
16185     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16186     if (forwardMostMove > currentMove) {
16187         if (gameInfo.resultDetails != NULL) {
16188             free(gameInfo.resultDetails);
16189             gameInfo.resultDetails = NULL;
16190             gameInfo.result = GameUnfinished;
16191         }
16192         forwardMostMove = currentMove;
16193         HistorySet(parseList, backwardMostMove, forwardMostMove,
16194                    currentMove-1);
16195     }
16196 }
16197
16198 void
16199 HintEvent ()
16200 {
16201     if (appData.noChessProgram) return;
16202     switch (gameMode) {
16203       case MachinePlaysWhite:
16204         if (WhiteOnMove(forwardMostMove)) {
16205             DisplayError(_("Wait until your turn."), 0);
16206             return;
16207         }
16208         break;
16209       case BeginningOfGame:
16210       case MachinePlaysBlack:
16211         if (!WhiteOnMove(forwardMostMove)) {
16212             DisplayError(_("Wait until your turn."), 0);
16213             return;
16214         }
16215         break;
16216       default:
16217         DisplayError(_("No hint available"), 0);
16218         return;
16219     }
16220     SendToProgram("hint\n", &first);
16221     hintRequested = TRUE;
16222 }
16223
16224 int
16225 SaveSelected (FILE *g, int dummy, char *dummy2)
16226 {
16227     ListGame * lg = (ListGame *) gameList.head;
16228     int nItem, cnt=0;
16229     FILE *f;
16230
16231     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16232         DisplayError(_("Game list not loaded or empty"), 0);
16233         return 0;
16234     }
16235
16236     creatingBook = TRUE; // suppresses stuff during load game
16237
16238     /* Get list size */
16239     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16240         if(lg->position >= 0) { // selected?
16241             LoadGame(f, nItem, "", TRUE);
16242             SaveGamePGN2(g); // leaves g open
16243             cnt++; DoEvents();
16244         }
16245         lg = (ListGame *) lg->node.succ;
16246     }
16247
16248     fclose(g);
16249     creatingBook = FALSE;
16250
16251     return cnt;
16252 }
16253
16254 void
16255 CreateBookEvent ()
16256 {
16257     ListGame * lg = (ListGame *) gameList.head;
16258     FILE *f, *g;
16259     int nItem;
16260     static int secondTime = FALSE;
16261
16262     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16263         DisplayError(_("Game list not loaded or empty"), 0);
16264         return;
16265     }
16266
16267     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16268         fclose(g);
16269         secondTime++;
16270         DisplayNote(_("Book file exists! Try again for overwrite."));
16271         return;
16272     }
16273
16274     creatingBook = TRUE;
16275     secondTime = FALSE;
16276
16277     /* Get list size */
16278     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16279         if(lg->position >= 0) {
16280             LoadGame(f, nItem, "", TRUE);
16281             AddGameToBook(TRUE);
16282             DoEvents();
16283         }
16284         lg = (ListGame *) lg->node.succ;
16285     }
16286
16287     creatingBook = FALSE;
16288     FlushBook();
16289 }
16290
16291 void
16292 BookEvent ()
16293 {
16294     if (appData.noChessProgram) return;
16295     switch (gameMode) {
16296       case MachinePlaysWhite:
16297         if (WhiteOnMove(forwardMostMove)) {
16298             DisplayError(_("Wait until your turn."), 0);
16299             return;
16300         }
16301         break;
16302       case BeginningOfGame:
16303       case MachinePlaysBlack:
16304         if (!WhiteOnMove(forwardMostMove)) {
16305             DisplayError(_("Wait until your turn."), 0);
16306             return;
16307         }
16308         break;
16309       case EditPosition:
16310         EditPositionDone(TRUE);
16311         break;
16312       case TwoMachinesPlay:
16313         return;
16314       default:
16315         break;
16316     }
16317     SendToProgram("bk\n", &first);
16318     bookOutput[0] = NULLCHAR;
16319     bookRequested = TRUE;
16320 }
16321
16322 void
16323 AboutGameEvent ()
16324 {
16325     char *tags = PGNTags(&gameInfo);
16326     TagsPopUp(tags, CmailMsg());
16327     free(tags);
16328 }
16329
16330 /* end button procedures */
16331
16332 void
16333 PrintPosition (FILE *fp, int move)
16334 {
16335     int i, j;
16336
16337     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16338         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16339             char c = PieceToChar(boards[move][i][j]);
16340             fputc(c == '?' ? '.' : c, fp);
16341             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16342         }
16343     }
16344     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16345       fprintf(fp, "white to play\n");
16346     else
16347       fprintf(fp, "black to play\n");
16348 }
16349
16350 void
16351 PrintOpponents (FILE *fp)
16352 {
16353     if (gameInfo.white != NULL) {
16354         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16355     } else {
16356         fprintf(fp, "\n");
16357     }
16358 }
16359
16360 /* Find last component of program's own name, using some heuristics */
16361 void
16362 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16363 {
16364     char *p, *q, c;
16365     int local = (strcmp(host, "localhost") == 0);
16366     while (!local && (p = strchr(prog, ';')) != NULL) {
16367         p++;
16368         while (*p == ' ') p++;
16369         prog = p;
16370     }
16371     if (*prog == '"' || *prog == '\'') {
16372         q = strchr(prog + 1, *prog);
16373     } else {
16374         q = strchr(prog, ' ');
16375     }
16376     if (q == NULL) q = prog + strlen(prog);
16377     p = q;
16378     while (p >= prog && *p != '/' && *p != '\\') p--;
16379     p++;
16380     if(p == prog && *p == '"') p++;
16381     c = *q; *q = 0;
16382     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16383     memcpy(buf, p, q - p);
16384     buf[q - p] = NULLCHAR;
16385     if (!local) {
16386         strcat(buf, "@");
16387         strcat(buf, host);
16388     }
16389 }
16390
16391 char *
16392 TimeControlTagValue ()
16393 {
16394     char buf[MSG_SIZ];
16395     if (!appData.clockMode) {
16396       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16397     } else if (movesPerSession > 0) {
16398       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16399     } else if (timeIncrement == 0) {
16400       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16401     } else {
16402       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16403     }
16404     return StrSave(buf);
16405 }
16406
16407 void
16408 SetGameInfo ()
16409 {
16410     /* This routine is used only for certain modes */
16411     VariantClass v = gameInfo.variant;
16412     ChessMove r = GameUnfinished;
16413     char *p = NULL;
16414
16415     if(keepInfo) return;
16416
16417     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16418         r = gameInfo.result;
16419         p = gameInfo.resultDetails;
16420         gameInfo.resultDetails = NULL;
16421     }
16422     ClearGameInfo(&gameInfo);
16423     gameInfo.variant = v;
16424
16425     switch (gameMode) {
16426       case MachinePlaysWhite:
16427         gameInfo.event = StrSave( appData.pgnEventHeader );
16428         gameInfo.site = StrSave(HostName());
16429         gameInfo.date = PGNDate();
16430         gameInfo.round = StrSave("-");
16431         gameInfo.white = StrSave(first.tidy);
16432         gameInfo.black = StrSave(UserName());
16433         gameInfo.timeControl = TimeControlTagValue();
16434         break;
16435
16436       case MachinePlaysBlack:
16437         gameInfo.event = StrSave( appData.pgnEventHeader );
16438         gameInfo.site = StrSave(HostName());
16439         gameInfo.date = PGNDate();
16440         gameInfo.round = StrSave("-");
16441         gameInfo.white = StrSave(UserName());
16442         gameInfo.black = StrSave(first.tidy);
16443         gameInfo.timeControl = TimeControlTagValue();
16444         break;
16445
16446       case TwoMachinesPlay:
16447         gameInfo.event = StrSave( appData.pgnEventHeader );
16448         gameInfo.site = StrSave(HostName());
16449         gameInfo.date = PGNDate();
16450         if (roundNr > 0) {
16451             char buf[MSG_SIZ];
16452             snprintf(buf, MSG_SIZ, "%d", roundNr);
16453             gameInfo.round = StrSave(buf);
16454         } else {
16455             gameInfo.round = StrSave("-");
16456         }
16457         if (first.twoMachinesColor[0] == 'w') {
16458             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16459             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16460         } else {
16461             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16462             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16463         }
16464         gameInfo.timeControl = TimeControlTagValue();
16465         break;
16466
16467       case EditGame:
16468         gameInfo.event = StrSave("Edited game");
16469         gameInfo.site = StrSave(HostName());
16470         gameInfo.date = PGNDate();
16471         gameInfo.round = StrSave("-");
16472         gameInfo.white = StrSave("-");
16473         gameInfo.black = StrSave("-");
16474         gameInfo.result = r;
16475         gameInfo.resultDetails = p;
16476         break;
16477
16478       case EditPosition:
16479         gameInfo.event = StrSave("Edited position");
16480         gameInfo.site = StrSave(HostName());
16481         gameInfo.date = PGNDate();
16482         gameInfo.round = StrSave("-");
16483         gameInfo.white = StrSave("-");
16484         gameInfo.black = StrSave("-");
16485         break;
16486
16487       case IcsPlayingWhite:
16488       case IcsPlayingBlack:
16489       case IcsObserving:
16490       case IcsExamining:
16491         break;
16492
16493       case PlayFromGameFile:
16494         gameInfo.event = StrSave("Game from non-PGN file");
16495         gameInfo.site = StrSave(HostName());
16496         gameInfo.date = PGNDate();
16497         gameInfo.round = StrSave("-");
16498         gameInfo.white = StrSave("?");
16499         gameInfo.black = StrSave("?");
16500         break;
16501
16502       default:
16503         break;
16504     }
16505 }
16506
16507 void
16508 ReplaceComment (int index, char *text)
16509 {
16510     int len;
16511     char *p;
16512     float score;
16513
16514     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16515        pvInfoList[index-1].depth == len &&
16516        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16517        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16518     while (*text == '\n') text++;
16519     len = strlen(text);
16520     while (len > 0 && text[len - 1] == '\n') len--;
16521
16522     if (commentList[index] != NULL)
16523       free(commentList[index]);
16524
16525     if (len == 0) {
16526         commentList[index] = NULL;
16527         return;
16528     }
16529   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16530       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16531       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16532     commentList[index] = (char *) malloc(len + 2);
16533     strncpy(commentList[index], text, len);
16534     commentList[index][len] = '\n';
16535     commentList[index][len + 1] = NULLCHAR;
16536   } else {
16537     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16538     char *p;
16539     commentList[index] = (char *) malloc(len + 7);
16540     safeStrCpy(commentList[index], "{\n", 3);
16541     safeStrCpy(commentList[index]+2, text, len+1);
16542     commentList[index][len+2] = NULLCHAR;
16543     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16544     strcat(commentList[index], "\n}\n");
16545   }
16546 }
16547
16548 void
16549 CrushCRs (char *text)
16550 {
16551   char *p = text;
16552   char *q = text;
16553   char ch;
16554
16555   do {
16556     ch = *p++;
16557     if (ch == '\r') continue;
16558     *q++ = ch;
16559   } while (ch != '\0');
16560 }
16561
16562 void
16563 AppendComment (int index, char *text, Boolean addBraces)
16564 /* addBraces  tells if we should add {} */
16565 {
16566     int oldlen, len;
16567     char *old;
16568
16569 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16570     if(addBraces == 3) addBraces = 0; else // force appending literally
16571     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16572
16573     CrushCRs(text);
16574     while (*text == '\n') text++;
16575     len = strlen(text);
16576     while (len > 0 && text[len - 1] == '\n') len--;
16577     text[len] = NULLCHAR;
16578
16579     if (len == 0) return;
16580
16581     if (commentList[index] != NULL) {
16582       Boolean addClosingBrace = addBraces;
16583         old = commentList[index];
16584         oldlen = strlen(old);
16585         while(commentList[index][oldlen-1] ==  '\n')
16586           commentList[index][--oldlen] = NULLCHAR;
16587         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16588         safeStrCpy(commentList[index], old, oldlen + len + 6);
16589         free(old);
16590         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16591         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16592           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16593           while (*text == '\n') { text++; len--; }
16594           commentList[index][--oldlen] = NULLCHAR;
16595       }
16596         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16597         else          strcat(commentList[index], "\n");
16598         strcat(commentList[index], text);
16599         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16600         else          strcat(commentList[index], "\n");
16601     } else {
16602         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16603         if(addBraces)
16604           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16605         else commentList[index][0] = NULLCHAR;
16606         strcat(commentList[index], text);
16607         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16608         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16609     }
16610 }
16611
16612 static char *
16613 FindStr (char * text, char * sub_text)
16614 {
16615     char * result = strstr( text, sub_text );
16616
16617     if( result != NULL ) {
16618         result += strlen( sub_text );
16619     }
16620
16621     return result;
16622 }
16623
16624 /* [AS] Try to extract PV info from PGN comment */
16625 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16626 char *
16627 GetInfoFromComment (int index, char * text)
16628 {
16629     char * sep = text, *p;
16630
16631     if( text != NULL && index > 0 ) {
16632         int score = 0;
16633         int depth = 0;
16634         int time = -1, sec = 0, deci;
16635         char * s_eval = FindStr( text, "[%eval " );
16636         char * s_emt = FindStr( text, "[%emt " );
16637 #if 0
16638         if( s_eval != NULL || s_emt != NULL ) {
16639 #else
16640         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16641 #endif
16642             /* New style */
16643             char delim;
16644
16645             if( s_eval != NULL ) {
16646                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16647                     return text;
16648                 }
16649
16650                 if( delim != ']' ) {
16651                     return text;
16652                 }
16653             }
16654
16655             if( s_emt != NULL ) {
16656             }
16657                 return text;
16658         }
16659         else {
16660             /* We expect something like: [+|-]nnn.nn/dd */
16661             int score_lo = 0;
16662
16663             if(*text != '{') return text; // [HGM] braces: must be normal comment
16664
16665             sep = strchr( text, '/' );
16666             if( sep == NULL || sep < (text+4) ) {
16667                 return text;
16668             }
16669
16670             p = text;
16671             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16672             if(p[1] == '(') { // comment starts with PV
16673                p = strchr(p, ')'); // locate end of PV
16674                if(p == NULL || sep < p+5) return text;
16675                // at this point we have something like "{(.*) +0.23/6 ..."
16676                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16677                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16678                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16679             }
16680             time = -1; sec = -1; deci = -1;
16681             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16682                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16683                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16684                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16685                 return text;
16686             }
16687
16688             if( score_lo < 0 || score_lo >= 100 ) {
16689                 return text;
16690             }
16691
16692             if(sec >= 0) time = 600*time + 10*sec; else
16693             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16694
16695             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16696
16697             /* [HGM] PV time: now locate end of PV info */
16698             while( *++sep >= '0' && *sep <= '9'); // strip depth
16699             if(time >= 0)
16700             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16701             if(sec >= 0)
16702             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16703             if(deci >= 0)
16704             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16705             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16706         }
16707
16708         if( depth <= 0 ) {
16709             return text;
16710         }
16711
16712         if( time < 0 ) {
16713             time = -1;
16714         }
16715
16716         pvInfoList[index-1].depth = depth;
16717         pvInfoList[index-1].score = score;
16718         pvInfoList[index-1].time  = 10*time; // centi-sec
16719         if(*sep == '}') *sep = 0; else *--sep = '{';
16720         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16721     }
16722     return sep;
16723 }
16724
16725 void
16726 SendToProgram (char *message, ChessProgramState *cps)
16727 {
16728     int count, outCount, error;
16729     char buf[MSG_SIZ];
16730
16731     if (cps->pr == NoProc) return;
16732     Attention(cps);
16733
16734     if (appData.debugMode) {
16735         TimeMark now;
16736         GetTimeMark(&now);
16737         fprintf(debugFP, "%ld >%-6s: %s",
16738                 SubtractTimeMarks(&now, &programStartTime),
16739                 cps->which, message);
16740         if(serverFP)
16741             fprintf(serverFP, "%ld >%-6s: %s",
16742                 SubtractTimeMarks(&now, &programStartTime),
16743                 cps->which, message), fflush(serverFP);
16744     }
16745
16746     count = strlen(message);
16747     outCount = OutputToProcess(cps->pr, message, count, &error);
16748     if (outCount < count && !exiting
16749                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16750       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16751       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16752         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16753             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16754                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16755                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16756                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16757             } else {
16758                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16759                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16760                 gameInfo.result = res;
16761             }
16762             gameInfo.resultDetails = StrSave(buf);
16763         }
16764         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16765         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16766     }
16767 }
16768
16769 void
16770 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16771 {
16772     char *end_str;
16773     char buf[MSG_SIZ];
16774     ChessProgramState *cps = (ChessProgramState *)closure;
16775
16776     if (isr != cps->isr) return; /* Killed intentionally */
16777     if (count <= 0) {
16778         if (count == 0) {
16779             RemoveInputSource(cps->isr);
16780             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16781                     _(cps->which), cps->program);
16782             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16783             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16784                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16785                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16786                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16787                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16788                 } else {
16789                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16790                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16791                     gameInfo.result = res;
16792                 }
16793                 gameInfo.resultDetails = StrSave(buf);
16794             }
16795             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16796             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16797         } else {
16798             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16799                     _(cps->which), cps->program);
16800             RemoveInputSource(cps->isr);
16801
16802             /* [AS] Program is misbehaving badly... kill it */
16803             if( count == -2 ) {
16804                 DestroyChildProcess( cps->pr, 9 );
16805                 cps->pr = NoProc;
16806             }
16807
16808             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16809         }
16810         return;
16811     }
16812
16813     if ((end_str = strchr(message, '\r')) != NULL)
16814       *end_str = NULLCHAR;
16815     if ((end_str = strchr(message, '\n')) != NULL)
16816       *end_str = NULLCHAR;
16817
16818     if (appData.debugMode) {
16819         TimeMark now; int print = 1;
16820         char *quote = ""; char c; int i;
16821
16822         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16823                 char start = message[0];
16824                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16825                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16826                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16827                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16828                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16829                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16830                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16831                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16832                    sscanf(message, "hint: %c", &c)!=1 &&
16833                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16834                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16835                     print = (appData.engineComments >= 2);
16836                 }
16837                 message[0] = start; // restore original message
16838         }
16839         if(print) {
16840                 GetTimeMark(&now);
16841                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16842                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16843                         quote,
16844                         message);
16845                 if(serverFP)
16846                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16847                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16848                         quote,
16849                         message), fflush(serverFP);
16850         }
16851     }
16852
16853     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16854     if (appData.icsEngineAnalyze) {
16855         if (strstr(message, "whisper") != NULL ||
16856              strstr(message, "kibitz") != NULL ||
16857             strstr(message, "tellics") != NULL) return;
16858     }
16859
16860     HandleMachineMove(message, cps);
16861 }
16862
16863
16864 void
16865 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16866 {
16867     char buf[MSG_SIZ];
16868     int seconds;
16869
16870     if( timeControl_2 > 0 ) {
16871         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16872             tc = timeControl_2;
16873         }
16874     }
16875     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16876     inc /= cps->timeOdds;
16877     st  /= cps->timeOdds;
16878
16879     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16880
16881     if (st > 0) {
16882       /* Set exact time per move, normally using st command */
16883       if (cps->stKludge) {
16884         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16885         seconds = st % 60;
16886         if (seconds == 0) {
16887           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16888         } else {
16889           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16890         }
16891       } else {
16892         snprintf(buf, MSG_SIZ, "st %d\n", st);
16893       }
16894     } else {
16895       /* Set conventional or incremental time control, using level command */
16896       if (seconds == 0) {
16897         /* Note old gnuchess bug -- minutes:seconds used to not work.
16898            Fixed in later versions, but still avoid :seconds
16899            when seconds is 0. */
16900         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16901       } else {
16902         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16903                  seconds, inc/1000.);
16904       }
16905     }
16906     SendToProgram(buf, cps);
16907
16908     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16909     /* Orthogonally, limit search to given depth */
16910     if (sd > 0) {
16911       if (cps->sdKludge) {
16912         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16913       } else {
16914         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16915       }
16916       SendToProgram(buf, cps);
16917     }
16918
16919     if(cps->nps >= 0) { /* [HGM] nps */
16920         if(cps->supportsNPS == FALSE)
16921           cps->nps = -1; // don't use if engine explicitly says not supported!
16922         else {
16923           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16924           SendToProgram(buf, cps);
16925         }
16926     }
16927 }
16928
16929 ChessProgramState *
16930 WhitePlayer ()
16931 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16932 {
16933     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16934        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16935         return &second;
16936     return &first;
16937 }
16938
16939 void
16940 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16941 {
16942     char message[MSG_SIZ];
16943     long time, otime;
16944
16945     /* Note: this routine must be called when the clocks are stopped
16946        or when they have *just* been set or switched; otherwise
16947        it will be off by the time since the current tick started.
16948     */
16949     if (machineWhite) {
16950         time = whiteTimeRemaining / 10;
16951         otime = blackTimeRemaining / 10;
16952     } else {
16953         time = blackTimeRemaining / 10;
16954         otime = whiteTimeRemaining / 10;
16955     }
16956     /* [HGM] translate opponent's time by time-odds factor */
16957     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16958
16959     if (time <= 0) time = 1;
16960     if (otime <= 0) otime = 1;
16961
16962     snprintf(message, MSG_SIZ, "time %ld\n", time);
16963     SendToProgram(message, cps);
16964
16965     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16966     SendToProgram(message, cps);
16967 }
16968
16969 char *
16970 EngineDefinedVariant (ChessProgramState *cps, int n)
16971 {   // return name of n-th unknown variant that engine supports
16972     static char buf[MSG_SIZ];
16973     char *p, *s = cps->variants;
16974     if(!s) return NULL;
16975     do { // parse string from variants feature
16976       VariantClass v;
16977         p = strchr(s, ',');
16978         if(p) *p = NULLCHAR;
16979       v = StringToVariant(s);
16980       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16981         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16982             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16983                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16984                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16985                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16986             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16987         }
16988         if(p) *p++ = ',';
16989         if(n < 0) return buf;
16990     } while(s = p);
16991     return NULL;
16992 }
16993
16994 int
16995 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16996 {
16997   char buf[MSG_SIZ];
16998   int len = strlen(name);
16999   int val;
17000
17001   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17002     (*p) += len + 1;
17003     sscanf(*p, "%d", &val);
17004     *loc = (val != 0);
17005     while (**p && **p != ' ')
17006       (*p)++;
17007     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17008     SendToProgram(buf, cps);
17009     return TRUE;
17010   }
17011   return FALSE;
17012 }
17013
17014 int
17015 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17016 {
17017   char buf[MSG_SIZ];
17018   int len = strlen(name);
17019   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17020     (*p) += len + 1;
17021     sscanf(*p, "%d", loc);
17022     while (**p && **p != ' ') (*p)++;
17023     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17024     SendToProgram(buf, cps);
17025     return TRUE;
17026   }
17027   return FALSE;
17028 }
17029
17030 int
17031 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17032 {
17033   char buf[MSG_SIZ];
17034   int len = strlen(name);
17035   if (strncmp((*p), name, len) == 0
17036       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17037     (*p) += len + 2;
17038     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17039     sscanf(*p, "%[^\"]", *loc);
17040     while (**p && **p != '\"') (*p)++;
17041     if (**p == '\"') (*p)++;
17042     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17043     SendToProgram(buf, cps);
17044     return TRUE;
17045   }
17046   return FALSE;
17047 }
17048
17049 int
17050 ParseOption (Option *opt, ChessProgramState *cps)
17051 // [HGM] options: process the string that defines an engine option, and determine
17052 // name, type, default value, and allowed value range
17053 {
17054         char *p, *q, buf[MSG_SIZ];
17055         int n, min = (-1)<<31, max = 1<<31, def;
17056
17057         opt->target = &opt->value;   // OK for spin/slider and checkbox
17058         if(p = strstr(opt->name, " -spin ")) {
17059             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17060             if(max < min) max = min; // enforce consistency
17061             if(def < min) def = min;
17062             if(def > max) def = max;
17063             opt->value = def;
17064             opt->min = min;
17065             opt->max = max;
17066             opt->type = Spin;
17067         } else if((p = strstr(opt->name, " -slider "))) {
17068             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17069             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17070             if(max < min) max = min; // enforce consistency
17071             if(def < min) def = min;
17072             if(def > max) def = max;
17073             opt->value = def;
17074             opt->min = min;
17075             opt->max = max;
17076             opt->type = Spin; // Slider;
17077         } else if((p = strstr(opt->name, " -string "))) {
17078             opt->textValue = p+9;
17079             opt->type = TextBox;
17080             opt->target = &opt->textValue;
17081         } else if((p = strstr(opt->name, " -file "))) {
17082             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17083             opt->target = opt->textValue = p+7;
17084             opt->type = FileName; // FileName;
17085             opt->target = &opt->textValue;
17086         } else if((p = strstr(opt->name, " -path "))) {
17087             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17088             opt->target = opt->textValue = p+7;
17089             opt->type = PathName; // PathName;
17090             opt->target = &opt->textValue;
17091         } else if(p = strstr(opt->name, " -check ")) {
17092             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17093             opt->value = (def != 0);
17094             opt->type = CheckBox;
17095         } else if(p = strstr(opt->name, " -combo ")) {
17096             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17097             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17098             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17099             opt->value = n = 0;
17100             while(q = StrStr(q, " /// ")) {
17101                 n++; *q = 0;    // count choices, and null-terminate each of them
17102                 q += 5;
17103                 if(*q == '*') { // remember default, which is marked with * prefix
17104                     q++;
17105                     opt->value = n;
17106                 }
17107                 cps->comboList[cps->comboCnt++] = q;
17108             }
17109             cps->comboList[cps->comboCnt++] = NULL;
17110             opt->max = n + 1;
17111             opt->type = ComboBox;
17112         } else if(p = strstr(opt->name, " -button")) {
17113             opt->type = Button;
17114         } else if(p = strstr(opt->name, " -save")) {
17115             opt->type = SaveButton;
17116         } else return FALSE;
17117         *p = 0; // terminate option name
17118         // now look if the command-line options define a setting for this engine option.
17119         if(cps->optionSettings && cps->optionSettings[0])
17120             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17121         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17122           snprintf(buf, MSG_SIZ, "option %s", p);
17123                 if(p = strstr(buf, ",")) *p = 0;
17124                 if(q = strchr(buf, '=')) switch(opt->type) {
17125                     case ComboBox:
17126                         for(n=0; n<opt->max; n++)
17127                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17128                         break;
17129                     case TextBox:
17130                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17131                         break;
17132                     case Spin:
17133                     case CheckBox:
17134                         opt->value = atoi(q+1);
17135                     default:
17136                         break;
17137                 }
17138                 strcat(buf, "\n");
17139                 SendToProgram(buf, cps);
17140         }
17141         return TRUE;
17142 }
17143
17144 void
17145 FeatureDone (ChessProgramState *cps, int val)
17146 {
17147   DelayedEventCallback cb = GetDelayedEvent();
17148   if ((cb == InitBackEnd3 && cps == &first) ||
17149       (cb == SettingsMenuIfReady && cps == &second) ||
17150       (cb == LoadEngine) ||
17151       (cb == TwoMachinesEventIfReady)) {
17152     CancelDelayedEvent();
17153     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17154   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17155   cps->initDone = val;
17156   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17157 }
17158
17159 /* Parse feature command from engine */
17160 void
17161 ParseFeatures (char *args, ChessProgramState *cps)
17162 {
17163   char *p = args;
17164   char *q = NULL;
17165   int val;
17166   char buf[MSG_SIZ];
17167
17168   for (;;) {
17169     while (*p == ' ') p++;
17170     if (*p == NULLCHAR) return;
17171
17172     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17173     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17174     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17175     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17176     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17177     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17178     if (BoolFeature(&p, "reuse", &val, cps)) {
17179       /* Engine can disable reuse, but can't enable it if user said no */
17180       if (!val) cps->reuse = FALSE;
17181       continue;
17182     }
17183     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17184     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17185       if (gameMode == TwoMachinesPlay) {
17186         DisplayTwoMachinesTitle();
17187       } else {
17188         DisplayTitle("");
17189       }
17190       continue;
17191     }
17192     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17193     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17194     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17195     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17196     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17197     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17198     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17199     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17200     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17201     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17202     if (IntFeature(&p, "done", &val, cps)) {
17203       FeatureDone(cps, val);
17204       continue;
17205     }
17206     /* Added by Tord: */
17207     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17208     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17209     /* End of additions by Tord */
17210
17211     /* [HGM] added features: */
17212     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17213     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17214     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17215     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17216     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17217     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17218     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17219     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17220         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17221         FREE(cps->option[cps->nrOptions].name);
17222         cps->option[cps->nrOptions].name = q; q = NULL;
17223         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17224           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17225             SendToProgram(buf, cps);
17226             continue;
17227         }
17228         if(cps->nrOptions >= MAX_OPTIONS) {
17229             cps->nrOptions--;
17230             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17231             DisplayError(buf, 0);
17232         }
17233         continue;
17234     }
17235     /* End of additions by HGM */
17236
17237     /* unknown feature: complain and skip */
17238     q = p;
17239     while (*q && *q != '=') q++;
17240     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17241     SendToProgram(buf, cps);
17242     p = q;
17243     if (*p == '=') {
17244       p++;
17245       if (*p == '\"') {
17246         p++;
17247         while (*p && *p != '\"') p++;
17248         if (*p == '\"') p++;
17249       } else {
17250         while (*p && *p != ' ') p++;
17251       }
17252     }
17253   }
17254
17255 }
17256
17257 void
17258 PeriodicUpdatesEvent (int newState)
17259 {
17260     if (newState == appData.periodicUpdates)
17261       return;
17262
17263     appData.periodicUpdates=newState;
17264
17265     /* Display type changes, so update it now */
17266 //    DisplayAnalysis();
17267
17268     /* Get the ball rolling again... */
17269     if (newState) {
17270         AnalysisPeriodicEvent(1);
17271         StartAnalysisClock();
17272     }
17273 }
17274
17275 void
17276 PonderNextMoveEvent (int newState)
17277 {
17278     if (newState == appData.ponderNextMove) return;
17279     if (gameMode == EditPosition) EditPositionDone(TRUE);
17280     if (newState) {
17281         SendToProgram("hard\n", &first);
17282         if (gameMode == TwoMachinesPlay) {
17283             SendToProgram("hard\n", &second);
17284         }
17285     } else {
17286         SendToProgram("easy\n", &first);
17287         thinkOutput[0] = NULLCHAR;
17288         if (gameMode == TwoMachinesPlay) {
17289             SendToProgram("easy\n", &second);
17290         }
17291     }
17292     appData.ponderNextMove = newState;
17293 }
17294
17295 void
17296 NewSettingEvent (int option, int *feature, char *command, int value)
17297 {
17298     char buf[MSG_SIZ];
17299
17300     if (gameMode == EditPosition) EditPositionDone(TRUE);
17301     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17302     if(feature == NULL || *feature) SendToProgram(buf, &first);
17303     if (gameMode == TwoMachinesPlay) {
17304         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17305     }
17306 }
17307
17308 void
17309 ShowThinkingEvent ()
17310 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17311 {
17312     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17313     int newState = appData.showThinking
17314         // [HGM] thinking: other features now need thinking output as well
17315         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17316
17317     if (oldState == newState) return;
17318     oldState = newState;
17319     if (gameMode == EditPosition) EditPositionDone(TRUE);
17320     if (oldState) {
17321         SendToProgram("post\n", &first);
17322         if (gameMode == TwoMachinesPlay) {
17323             SendToProgram("post\n", &second);
17324         }
17325     } else {
17326         SendToProgram("nopost\n", &first);
17327         thinkOutput[0] = NULLCHAR;
17328         if (gameMode == TwoMachinesPlay) {
17329             SendToProgram("nopost\n", &second);
17330         }
17331     }
17332 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17333 }
17334
17335 void
17336 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17337 {
17338   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17339   if (pr == NoProc) return;
17340   AskQuestion(title, question, replyPrefix, pr);
17341 }
17342
17343 void
17344 TypeInEvent (char firstChar)
17345 {
17346     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17347         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17348         gameMode == AnalyzeMode || gameMode == EditGame ||
17349         gameMode == EditPosition || gameMode == IcsExamining ||
17350         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17351         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17352                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17353                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17354         gameMode == Training) PopUpMoveDialog(firstChar);
17355 }
17356
17357 void
17358 TypeInDoneEvent (char *move)
17359 {
17360         Board board;
17361         int n, fromX, fromY, toX, toY;
17362         char promoChar;
17363         ChessMove moveType;
17364
17365         // [HGM] FENedit
17366         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17367                 EditPositionPasteFEN(move);
17368                 return;
17369         }
17370         // [HGM] movenum: allow move number to be typed in any mode
17371         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17372           ToNrEvent(2*n-1);
17373           return;
17374         }
17375         // undocumented kludge: allow command-line option to be typed in!
17376         // (potentially fatal, and does not implement the effect of the option.)
17377         // should only be used for options that are values on which future decisions will be made,
17378         // and definitely not on options that would be used during initialization.
17379         if(strstr(move, "!!! -") == move) {
17380             ParseArgsFromString(move+4);
17381             return;
17382         }
17383
17384       if (gameMode != EditGame && currentMove != forwardMostMove &&
17385         gameMode != Training) {
17386         DisplayMoveError(_("Displayed move is not current"));
17387       } else {
17388         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17389           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17390         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17391         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17392           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17393           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17394         } else {
17395           DisplayMoveError(_("Could not parse move"));
17396         }
17397       }
17398 }
17399
17400 void
17401 DisplayMove (int moveNumber)
17402 {
17403     char message[MSG_SIZ];
17404     char res[MSG_SIZ];
17405     char cpThinkOutput[MSG_SIZ];
17406
17407     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17408
17409     if (moveNumber == forwardMostMove - 1 ||
17410         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17411
17412         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17413
17414         if (strchr(cpThinkOutput, '\n')) {
17415             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17416         }
17417     } else {
17418         *cpThinkOutput = NULLCHAR;
17419     }
17420
17421     /* [AS] Hide thinking from human user */
17422     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17423         *cpThinkOutput = NULLCHAR;
17424         if( thinkOutput[0] != NULLCHAR ) {
17425             int i;
17426
17427             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17428                 cpThinkOutput[i] = '.';
17429             }
17430             cpThinkOutput[i] = NULLCHAR;
17431             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17432         }
17433     }
17434
17435     if (moveNumber == forwardMostMove - 1 &&
17436         gameInfo.resultDetails != NULL) {
17437         if (gameInfo.resultDetails[0] == NULLCHAR) {
17438           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17439         } else {
17440           snprintf(res, MSG_SIZ, " {%s} %s",
17441                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17442         }
17443     } else {
17444         res[0] = NULLCHAR;
17445     }
17446
17447     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17448         DisplayMessage(res, cpThinkOutput);
17449     } else {
17450       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17451                 WhiteOnMove(moveNumber) ? " " : ".. ",
17452                 parseList[moveNumber], res);
17453         DisplayMessage(message, cpThinkOutput);
17454     }
17455 }
17456
17457 void
17458 DisplayComment (int moveNumber, char *text)
17459 {
17460     char title[MSG_SIZ];
17461
17462     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17463       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17464     } else {
17465       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17466               WhiteOnMove(moveNumber) ? " " : ".. ",
17467               parseList[moveNumber]);
17468     }
17469     if (text != NULL && (appData.autoDisplayComment || commentUp))
17470         CommentPopUp(title, text);
17471 }
17472
17473 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17474  * might be busy thinking or pondering.  It can be omitted if your
17475  * gnuchess is configured to stop thinking immediately on any user
17476  * input.  However, that gnuchess feature depends on the FIONREAD
17477  * ioctl, which does not work properly on some flavors of Unix.
17478  */
17479 void
17480 Attention (ChessProgramState *cps)
17481 {
17482 #if ATTENTION
17483     if (!cps->useSigint) return;
17484     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17485     switch (gameMode) {
17486       case MachinePlaysWhite:
17487       case MachinePlaysBlack:
17488       case TwoMachinesPlay:
17489       case IcsPlayingWhite:
17490       case IcsPlayingBlack:
17491       case AnalyzeMode:
17492       case AnalyzeFile:
17493         /* Skip if we know it isn't thinking */
17494         if (!cps->maybeThinking) return;
17495         if (appData.debugMode)
17496           fprintf(debugFP, "Interrupting %s\n", cps->which);
17497         InterruptChildProcess(cps->pr);
17498         cps->maybeThinking = FALSE;
17499         break;
17500       default:
17501         break;
17502     }
17503 #endif /*ATTENTION*/
17504 }
17505
17506 int
17507 CheckFlags ()
17508 {
17509     if (whiteTimeRemaining <= 0) {
17510         if (!whiteFlag) {
17511             whiteFlag = TRUE;
17512             if (appData.icsActive) {
17513                 if (appData.autoCallFlag &&
17514                     gameMode == IcsPlayingBlack && !blackFlag) {
17515                   SendToICS(ics_prefix);
17516                   SendToICS("flag\n");
17517                 }
17518             } else {
17519                 if (blackFlag) {
17520                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17521                 } else {
17522                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17523                     if (appData.autoCallFlag) {
17524                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17525                         return TRUE;
17526                     }
17527                 }
17528             }
17529         }
17530     }
17531     if (blackTimeRemaining <= 0) {
17532         if (!blackFlag) {
17533             blackFlag = TRUE;
17534             if (appData.icsActive) {
17535                 if (appData.autoCallFlag &&
17536                     gameMode == IcsPlayingWhite && !whiteFlag) {
17537                   SendToICS(ics_prefix);
17538                   SendToICS("flag\n");
17539                 }
17540             } else {
17541                 if (whiteFlag) {
17542                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17543                 } else {
17544                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17545                     if (appData.autoCallFlag) {
17546                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17547                         return TRUE;
17548                     }
17549                 }
17550             }
17551         }
17552     }
17553     return FALSE;
17554 }
17555
17556 void
17557 CheckTimeControl ()
17558 {
17559     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17560         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17561
17562     /*
17563      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17564      */
17565     if ( !WhiteOnMove(forwardMostMove) ) {
17566         /* White made time control */
17567         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17568         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17569         /* [HGM] time odds: correct new time quota for time odds! */
17570                                             / WhitePlayer()->timeOdds;
17571         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17572     } else {
17573         lastBlack -= blackTimeRemaining;
17574         /* Black made time control */
17575         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17576                                             / WhitePlayer()->other->timeOdds;
17577         lastWhite = whiteTimeRemaining;
17578     }
17579 }
17580
17581 void
17582 DisplayBothClocks ()
17583 {
17584     int wom = gameMode == EditPosition ?
17585       !blackPlaysFirst : WhiteOnMove(currentMove);
17586     DisplayWhiteClock(whiteTimeRemaining, wom);
17587     DisplayBlackClock(blackTimeRemaining, !wom);
17588 }
17589
17590
17591 /* Timekeeping seems to be a portability nightmare.  I think everyone
17592    has ftime(), but I'm really not sure, so I'm including some ifdefs
17593    to use other calls if you don't.  Clocks will be less accurate if
17594    you have neither ftime nor gettimeofday.
17595 */
17596
17597 /* VS 2008 requires the #include outside of the function */
17598 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17599 #include <sys/timeb.h>
17600 #endif
17601
17602 /* Get the current time as a TimeMark */
17603 void
17604 GetTimeMark (TimeMark *tm)
17605 {
17606 #if HAVE_GETTIMEOFDAY
17607
17608     struct timeval timeVal;
17609     struct timezone timeZone;
17610
17611     gettimeofday(&timeVal, &timeZone);
17612     tm->sec = (long) timeVal.tv_sec;
17613     tm->ms = (int) (timeVal.tv_usec / 1000L);
17614
17615 #else /*!HAVE_GETTIMEOFDAY*/
17616 #if HAVE_FTIME
17617
17618 // include <sys/timeb.h> / moved to just above start of function
17619     struct timeb timeB;
17620
17621     ftime(&timeB);
17622     tm->sec = (long) timeB.time;
17623     tm->ms = (int) timeB.millitm;
17624
17625 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17626     tm->sec = (long) time(NULL);
17627     tm->ms = 0;
17628 #endif
17629 #endif
17630 }
17631
17632 /* Return the difference in milliseconds between two
17633    time marks.  We assume the difference will fit in a long!
17634 */
17635 long
17636 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17637 {
17638     return 1000L*(tm2->sec - tm1->sec) +
17639            (long) (tm2->ms - tm1->ms);
17640 }
17641
17642
17643 /*
17644  * Code to manage the game clocks.
17645  *
17646  * In tournament play, black starts the clock and then white makes a move.
17647  * We give the human user a slight advantage if he is playing white---the
17648  * clocks don't run until he makes his first move, so it takes zero time.
17649  * Also, we don't account for network lag, so we could get out of sync
17650  * with GNU Chess's clock -- but then, referees are always right.
17651  */
17652
17653 static TimeMark tickStartTM;
17654 static long intendedTickLength;
17655
17656 long
17657 NextTickLength (long timeRemaining)
17658 {
17659     long nominalTickLength, nextTickLength;
17660
17661     if (timeRemaining > 0L && timeRemaining <= 10000L)
17662       nominalTickLength = 100L;
17663     else
17664       nominalTickLength = 1000L;
17665     nextTickLength = timeRemaining % nominalTickLength;
17666     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17667
17668     return nextTickLength;
17669 }
17670
17671 /* Adjust clock one minute up or down */
17672 void
17673 AdjustClock (Boolean which, int dir)
17674 {
17675     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17676     if(which) blackTimeRemaining += 60000*dir;
17677     else      whiteTimeRemaining += 60000*dir;
17678     DisplayBothClocks();
17679     adjustedClock = TRUE;
17680 }
17681
17682 /* Stop clocks and reset to a fresh time control */
17683 void
17684 ResetClocks ()
17685 {
17686     (void) StopClockTimer();
17687     if (appData.icsActive) {
17688         whiteTimeRemaining = blackTimeRemaining = 0;
17689     } else if (searchTime) {
17690         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17691         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17692     } else { /* [HGM] correct new time quote for time odds */
17693         whiteTC = blackTC = fullTimeControlString;
17694         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17695         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17696     }
17697     if (whiteFlag || blackFlag) {
17698         DisplayTitle("");
17699         whiteFlag = blackFlag = FALSE;
17700     }
17701     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17702     DisplayBothClocks();
17703     adjustedClock = FALSE;
17704 }
17705
17706 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17707
17708 /* Decrement running clock by amount of time that has passed */
17709 void
17710 DecrementClocks ()
17711 {
17712     long timeRemaining;
17713     long lastTickLength, fudge;
17714     TimeMark now;
17715
17716     if (!appData.clockMode) return;
17717     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17718
17719     GetTimeMark(&now);
17720
17721     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17722
17723     /* Fudge if we woke up a little too soon */
17724     fudge = intendedTickLength - lastTickLength;
17725     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17726
17727     if (WhiteOnMove(forwardMostMove)) {
17728         if(whiteNPS >= 0) lastTickLength = 0;
17729         timeRemaining = whiteTimeRemaining -= lastTickLength;
17730         if(timeRemaining < 0 && !appData.icsActive) {
17731             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17732             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17733                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17734                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17735             }
17736         }
17737         DisplayWhiteClock(whiteTimeRemaining - fudge,
17738                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17739     } else {
17740         if(blackNPS >= 0) lastTickLength = 0;
17741         timeRemaining = blackTimeRemaining -= lastTickLength;
17742         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17743             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17744             if(suddenDeath) {
17745                 blackStartMove = forwardMostMove;
17746                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17747             }
17748         }
17749         DisplayBlackClock(blackTimeRemaining - fudge,
17750                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17751     }
17752     if (CheckFlags()) return;
17753
17754     if(twoBoards) { // count down secondary board's clocks as well
17755         activePartnerTime -= lastTickLength;
17756         partnerUp = 1;
17757         if(activePartner == 'W')
17758             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17759         else
17760             DisplayBlackClock(activePartnerTime, TRUE);
17761         partnerUp = 0;
17762     }
17763
17764     tickStartTM = now;
17765     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17766     StartClockTimer(intendedTickLength);
17767
17768     /* if the time remaining has fallen below the alarm threshold, sound the
17769      * alarm. if the alarm has sounded and (due to a takeback or time control
17770      * with increment) the time remaining has increased to a level above the
17771      * threshold, reset the alarm so it can sound again.
17772      */
17773
17774     if (appData.icsActive && appData.icsAlarm) {
17775
17776         /* make sure we are dealing with the user's clock */
17777         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17778                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17779            )) return;
17780
17781         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17782             alarmSounded = FALSE;
17783         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17784             PlayAlarmSound();
17785             alarmSounded = TRUE;
17786         }
17787     }
17788 }
17789
17790
17791 /* A player has just moved, so stop the previously running
17792    clock and (if in clock mode) start the other one.
17793    We redisplay both clocks in case we're in ICS mode, because
17794    ICS gives us an update to both clocks after every move.
17795    Note that this routine is called *after* forwardMostMove
17796    is updated, so the last fractional tick must be subtracted
17797    from the color that is *not* on move now.
17798 */
17799 void
17800 SwitchClocks (int newMoveNr)
17801 {
17802     long lastTickLength;
17803     TimeMark now;
17804     int flagged = FALSE;
17805
17806     GetTimeMark(&now);
17807
17808     if (StopClockTimer() && appData.clockMode) {
17809         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17810         if (!WhiteOnMove(forwardMostMove)) {
17811             if(blackNPS >= 0) lastTickLength = 0;
17812             blackTimeRemaining -= lastTickLength;
17813            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17814 //         if(pvInfoList[forwardMostMove].time == -1)
17815                  pvInfoList[forwardMostMove].time =               // use GUI time
17816                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17817         } else {
17818            if(whiteNPS >= 0) lastTickLength = 0;
17819            whiteTimeRemaining -= lastTickLength;
17820            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17821 //         if(pvInfoList[forwardMostMove].time == -1)
17822                  pvInfoList[forwardMostMove].time =
17823                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17824         }
17825         flagged = CheckFlags();
17826     }
17827     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17828     CheckTimeControl();
17829
17830     if (flagged || !appData.clockMode) return;
17831
17832     switch (gameMode) {
17833       case MachinePlaysBlack:
17834       case MachinePlaysWhite:
17835       case BeginningOfGame:
17836         if (pausing) return;
17837         break;
17838
17839       case EditGame:
17840       case PlayFromGameFile:
17841       case IcsExamining:
17842         return;
17843
17844       default:
17845         break;
17846     }
17847
17848     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17849         if(WhiteOnMove(forwardMostMove))
17850              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17851         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17852     }
17853
17854     tickStartTM = now;
17855     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17856       whiteTimeRemaining : blackTimeRemaining);
17857     StartClockTimer(intendedTickLength);
17858 }
17859
17860
17861 /* Stop both clocks */
17862 void
17863 StopClocks ()
17864 {
17865     long lastTickLength;
17866     TimeMark now;
17867
17868     if (!StopClockTimer()) return;
17869     if (!appData.clockMode) return;
17870
17871     GetTimeMark(&now);
17872
17873     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17874     if (WhiteOnMove(forwardMostMove)) {
17875         if(whiteNPS >= 0) lastTickLength = 0;
17876         whiteTimeRemaining -= lastTickLength;
17877         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17878     } else {
17879         if(blackNPS >= 0) lastTickLength = 0;
17880         blackTimeRemaining -= lastTickLength;
17881         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17882     }
17883     CheckFlags();
17884 }
17885
17886 /* Start clock of player on move.  Time may have been reset, so
17887    if clock is already running, stop and restart it. */
17888 void
17889 StartClocks ()
17890 {
17891     (void) StopClockTimer(); /* in case it was running already */
17892     DisplayBothClocks();
17893     if (CheckFlags()) return;
17894
17895     if (!appData.clockMode) return;
17896     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17897
17898     GetTimeMark(&tickStartTM);
17899     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17900       whiteTimeRemaining : blackTimeRemaining);
17901
17902    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17903     whiteNPS = blackNPS = -1;
17904     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17905        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17906         whiteNPS = first.nps;
17907     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17908        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17909         blackNPS = first.nps;
17910     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17911         whiteNPS = second.nps;
17912     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17913         blackNPS = second.nps;
17914     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17915
17916     StartClockTimer(intendedTickLength);
17917 }
17918
17919 char *
17920 TimeString (long ms)
17921 {
17922     long second, minute, hour, day;
17923     char *sign = "";
17924     static char buf[32];
17925
17926     if (ms > 0 && ms <= 9900) {
17927       /* convert milliseconds to tenths, rounding up */
17928       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17929
17930       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17931       return buf;
17932     }
17933
17934     /* convert milliseconds to seconds, rounding up */
17935     /* use floating point to avoid strangeness of integer division
17936        with negative dividends on many machines */
17937     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17938
17939     if (second < 0) {
17940         sign = "-";
17941         second = -second;
17942     }
17943
17944     day = second / (60 * 60 * 24);
17945     second = second % (60 * 60 * 24);
17946     hour = second / (60 * 60);
17947     second = second % (60 * 60);
17948     minute = second / 60;
17949     second = second % 60;
17950
17951     if (day > 0)
17952       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17953               sign, day, hour, minute, second);
17954     else if (hour > 0)
17955       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17956     else
17957       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17958
17959     return buf;
17960 }
17961
17962
17963 /*
17964  * This is necessary because some C libraries aren't ANSI C compliant yet.
17965  */
17966 char *
17967 StrStr (char *string, char *match)
17968 {
17969     int i, length;
17970
17971     length = strlen(match);
17972
17973     for (i = strlen(string) - length; i >= 0; i--, string++)
17974       if (!strncmp(match, string, length))
17975         return string;
17976
17977     return NULL;
17978 }
17979
17980 char *
17981 StrCaseStr (char *string, char *match)
17982 {
17983     int i, j, length;
17984
17985     length = strlen(match);
17986
17987     for (i = strlen(string) - length; i >= 0; i--, string++) {
17988         for (j = 0; j < length; j++) {
17989             if (ToLower(match[j]) != ToLower(string[j]))
17990               break;
17991         }
17992         if (j == length) return string;
17993     }
17994
17995     return NULL;
17996 }
17997
17998 #ifndef _amigados
17999 int
18000 StrCaseCmp (char *s1, char *s2)
18001 {
18002     char c1, c2;
18003
18004     for (;;) {
18005         c1 = ToLower(*s1++);
18006         c2 = ToLower(*s2++);
18007         if (c1 > c2) return 1;
18008         if (c1 < c2) return -1;
18009         if (c1 == NULLCHAR) return 0;
18010     }
18011 }
18012
18013
18014 int
18015 ToLower (int c)
18016 {
18017     return isupper(c) ? tolower(c) : c;
18018 }
18019
18020
18021 int
18022 ToUpper (int c)
18023 {
18024     return islower(c) ? toupper(c) : c;
18025 }
18026 #endif /* !_amigados    */
18027
18028 char *
18029 StrSave (char *s)
18030 {
18031   char *ret;
18032
18033   if ((ret = (char *) malloc(strlen(s) + 1)))
18034     {
18035       safeStrCpy(ret, s, strlen(s)+1);
18036     }
18037   return ret;
18038 }
18039
18040 char *
18041 StrSavePtr (char *s, char **savePtr)
18042 {
18043     if (*savePtr) {
18044         free(*savePtr);
18045     }
18046     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18047       safeStrCpy(*savePtr, s, strlen(s)+1);
18048     }
18049     return(*savePtr);
18050 }
18051
18052 char *
18053 PGNDate ()
18054 {
18055     time_t clock;
18056     struct tm *tm;
18057     char buf[MSG_SIZ];
18058
18059     clock = time((time_t *)NULL);
18060     tm = localtime(&clock);
18061     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18062             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18063     return StrSave(buf);
18064 }
18065
18066
18067 char *
18068 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18069 {
18070     int i, j, fromX, fromY, toX, toY;
18071     int whiteToPlay, haveRights = nrCastlingRights;
18072     char buf[MSG_SIZ];
18073     char *p, *q;
18074     int emptycount;
18075     ChessSquare piece;
18076
18077     whiteToPlay = (gameMode == EditPosition) ?
18078       !blackPlaysFirst : (move % 2 == 0);
18079     p = buf;
18080
18081     /* Piece placement data */
18082     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18083         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18084         emptycount = 0;
18085         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18086             if (boards[move][i][j] == EmptySquare) {
18087                 emptycount++;
18088             } else { ChessSquare piece = boards[move][i][j];
18089                 if (emptycount > 0) {
18090                     if(emptycount<10) /* [HGM] can be >= 10 */
18091                         *p++ = '0' + emptycount;
18092                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18093                     emptycount = 0;
18094                 }
18095                 if(PieceToChar(piece) == '+') {
18096                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18097                     *p++ = '+';
18098                     piece = (ChessSquare)(CHUDEMOTED(piece));
18099                 }
18100                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18101                 if(*p = PieceSuffix(piece)) p++;
18102                 if(p[-1] == '~') {
18103                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18104                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18105                     *p++ = '~';
18106                 }
18107             }
18108         }
18109         if (emptycount > 0) {
18110             if(emptycount<10) /* [HGM] can be >= 10 */
18111                 *p++ = '0' + emptycount;
18112             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18113             emptycount = 0;
18114         }
18115         *p++ = '/';
18116     }
18117     *(p - 1) = ' ';
18118
18119     /* [HGM] print Crazyhouse or Shogi holdings */
18120     if( gameInfo.holdingsWidth ) {
18121         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18122         q = p;
18123         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18124             piece = boards[move][i][BOARD_WIDTH-1];
18125             if( piece != EmptySquare )
18126               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18127                   *p++ = PieceToChar(piece);
18128         }
18129         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18130             piece = boards[move][BOARD_HEIGHT-i-1][0];
18131             if( piece != EmptySquare )
18132               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18133                   *p++ = PieceToChar(piece);
18134         }
18135
18136         if( q == p ) *p++ = '-';
18137         *p++ = ']';
18138         *p++ = ' ';
18139     }
18140
18141     /* Active color */
18142     *p++ = whiteToPlay ? 'w' : 'b';
18143     *p++ = ' ';
18144
18145   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18146     haveRights = 0; q = p;
18147     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18148       piece = boards[move][0][i];
18149       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18150         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18151       }
18152     }
18153     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18154       piece = boards[move][BOARD_HEIGHT-1][i];
18155       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18156         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18157       }
18158     }
18159     if(p == q) *p++ = '-';
18160     *p++ = ' ';
18161   }
18162
18163   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18164     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18165   } else {
18166   if(haveRights) {
18167      int handW=0, handB=0;
18168      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18169         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18170         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18171      }
18172      q = p;
18173      if(appData.fischerCastling) {
18174         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18175            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18176                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18177         } else {
18178        /* [HGM] write directly from rights */
18179            if(boards[move][CASTLING][2] != NoRights &&
18180               boards[move][CASTLING][0] != NoRights   )
18181                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18182            if(boards[move][CASTLING][2] != NoRights &&
18183               boards[move][CASTLING][1] != NoRights   )
18184                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18185         }
18186         if(handB) {
18187            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18188                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18189         } else {
18190            if(boards[move][CASTLING][5] != NoRights &&
18191               boards[move][CASTLING][3] != NoRights   )
18192                 *p++ = boards[move][CASTLING][3] + AAA;
18193            if(boards[move][CASTLING][5] != NoRights &&
18194               boards[move][CASTLING][4] != NoRights   )
18195                 *p++ = boards[move][CASTLING][4] + AAA;
18196         }
18197      } else {
18198
18199         /* [HGM] write true castling rights */
18200         if( nrCastlingRights == 6 ) {
18201             int q, k=0;
18202             if(boards[move][CASTLING][0] != NoRights &&
18203                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18204             q = (boards[move][CASTLING][1] != NoRights &&
18205                  boards[move][CASTLING][2] != NoRights  );
18206             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18207                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18208                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18209                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18210             }
18211             if(q) *p++ = 'Q';
18212             k = 0;
18213             if(boards[move][CASTLING][3] != NoRights &&
18214                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18215             q = (boards[move][CASTLING][4] != NoRights &&
18216                  boards[move][CASTLING][5] != NoRights  );
18217             if(handB) {
18218                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18219                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18220                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18221             }
18222             if(q) *p++ = 'q';
18223         }
18224      }
18225      if (q == p) *p++ = '-'; /* No castling rights */
18226      *p++ = ' ';
18227   }
18228
18229   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18230      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18231      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18232     /* En passant target square */
18233     if (move > backwardMostMove) {
18234         fromX = moveList[move - 1][0] - AAA;
18235         fromY = moveList[move - 1][1] - ONE;
18236         toX = moveList[move - 1][2] - AAA;
18237         toY = moveList[move - 1][3] - ONE;
18238         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18239             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18240             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18241             fromX == toX) {
18242             /* 2-square pawn move just happened */
18243             *p++ = toX + AAA;
18244             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18245         } else {
18246             *p++ = '-';
18247         }
18248     } else if(move == backwardMostMove) {
18249         // [HGM] perhaps we should always do it like this, and forget the above?
18250         if((signed char)boards[move][EP_STATUS] >= 0) {
18251             *p++ = boards[move][EP_STATUS] + AAA;
18252             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18253         } else {
18254             *p++ = '-';
18255         }
18256     } else {
18257         *p++ = '-';
18258     }
18259     *p++ = ' ';
18260   }
18261   }
18262
18263     if(moveCounts)
18264     {   int i = 0, j=move;
18265
18266         /* [HGM] find reversible plies */
18267         if (appData.debugMode) { int k;
18268             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18269             for(k=backwardMostMove; k<=forwardMostMove; k++)
18270                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18271
18272         }
18273
18274         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18275         if( j == backwardMostMove ) i += initialRulePlies;
18276         sprintf(p, "%d ", i);
18277         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18278
18279         /* Fullmove number */
18280         sprintf(p, "%d", (move / 2) + 1);
18281     } else *--p = NULLCHAR;
18282
18283     return StrSave(buf);
18284 }
18285
18286 Boolean
18287 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18288 {
18289     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18290     char *p, c;
18291     int emptycount, virgin[BOARD_FILES];
18292     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18293
18294     p = fen;
18295
18296     /* Piece placement data */
18297     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18298         j = 0;
18299         for (;;) {
18300             if (*p == '/' || *p == ' ' || *p == '[' ) {
18301                 if(j > w) w = j;
18302                 emptycount = gameInfo.boardWidth - j;
18303                 while (emptycount--)
18304                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18305                 if (*p == '/') p++;
18306                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18307                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18308                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18309                     }
18310                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18311                 }
18312                 break;
18313 #if(BOARD_FILES >= 10)*0
18314             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18315                 p++; emptycount=10;
18316                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18317                 while (emptycount--)
18318                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18319 #endif
18320             } else if (*p == '*') {
18321                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18322             } else if (isdigit(*p)) {
18323                 emptycount = *p++ - '0';
18324                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18325                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18326                 while (emptycount--)
18327                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18328             } else if (*p == '<') {
18329                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18330                 else if (i != 0 || !shuffle) return FALSE;
18331                 p++;
18332             } else if (shuffle && *p == '>') {
18333                 p++; // for now ignore closing shuffle range, and assume rank-end
18334             } else if (*p == '?') {
18335                 if (j >= gameInfo.boardWidth) return FALSE;
18336                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18337                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18338             } else if (*p == '+' || isalpha(*p)) {
18339                 char *q, *s = SUFFIXES;
18340                 if (j >= gameInfo.boardWidth) return FALSE;
18341                 if(*p=='+') {
18342                     char c = *++p;
18343                     if(q = strchr(s, p[1])) p++;
18344                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18345                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18346                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18347                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18348                 } else {
18349                     char c = *p++;
18350                     if(q = strchr(s, *p)) p++;
18351                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18352                 }
18353
18354                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18355                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18356                     piece = (ChessSquare) (PROMOTED(piece));
18357                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18358                     p++;
18359                 }
18360                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18361                 if(piece == king) wKingRank = i;
18362                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18363             } else {
18364                 return FALSE;
18365             }
18366         }
18367     }
18368     while (*p == '/' || *p == ' ') p++;
18369
18370     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18371
18372     /* [HGM] by default clear Crazyhouse holdings, if present */
18373     if(gameInfo.holdingsWidth) {
18374        for(i=0; i<BOARD_HEIGHT; i++) {
18375            board[i][0]             = EmptySquare; /* black holdings */
18376            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18377            board[i][1]             = (ChessSquare) 0; /* black counts */
18378            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18379        }
18380     }
18381
18382     /* [HGM] look for Crazyhouse holdings here */
18383     while(*p==' ') p++;
18384     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18385         int swap=0, wcnt=0, bcnt=0;
18386         if(*p == '[') p++;
18387         if(*p == '<') swap++, p++;
18388         if(*p == '-' ) p++; /* empty holdings */ else {
18389             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18390             /* if we would allow FEN reading to set board size, we would   */
18391             /* have to add holdings and shift the board read so far here   */
18392             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18393                 p++;
18394                 if((int) piece >= (int) BlackPawn ) {
18395                     i = (int)piece - (int)BlackPawn;
18396                     i = PieceToNumber((ChessSquare)i);
18397                     if( i >= gameInfo.holdingsSize ) return FALSE;
18398                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18399                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18400                     bcnt++;
18401                 } else {
18402                     i = (int)piece - (int)WhitePawn;
18403                     i = PieceToNumber((ChessSquare)i);
18404                     if( i >= gameInfo.holdingsSize ) return FALSE;
18405                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18406                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18407                     wcnt++;
18408                 }
18409             }
18410             if(subst) { // substitute back-rank question marks by holdings pieces
18411                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18412                     int k, m, n = bcnt + 1;
18413                     if(board[0][j] == ClearBoard) {
18414                         if(!wcnt) return FALSE;
18415                         n = rand() % wcnt;
18416                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18417                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18418                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18419                             break;
18420                         }
18421                     }
18422                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18423                         if(!bcnt) return FALSE;
18424                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18425                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18426                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18427                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18428                             break;
18429                         }
18430                     }
18431                 }
18432                 subst = 0;
18433             }
18434         }
18435         if(*p == ']') p++;
18436     }
18437
18438     if(subst) return FALSE; // substitution requested, but no holdings
18439
18440     while(*p == ' ') p++;
18441
18442     /* Active color */
18443     c = *p++;
18444     if(appData.colorNickNames) {
18445       if( c == appData.colorNickNames[0] ) c = 'w'; else
18446       if( c == appData.colorNickNames[1] ) c = 'b';
18447     }
18448     switch (c) {
18449       case 'w':
18450         *blackPlaysFirst = FALSE;
18451         break;
18452       case 'b':
18453         *blackPlaysFirst = TRUE;
18454         break;
18455       default:
18456         return FALSE;
18457     }
18458
18459     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18460     /* return the extra info in global variiables             */
18461
18462     while(*p==' ') p++;
18463
18464     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18465         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18466         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18467     }
18468
18469     /* set defaults in case FEN is incomplete */
18470     board[EP_STATUS] = EP_UNKNOWN;
18471     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18472     for(i=0; i<nrCastlingRights; i++ ) {
18473         board[CASTLING][i] =
18474             appData.fischerCastling ? NoRights : initialRights[i];
18475     }   /* assume possible unless obviously impossible */
18476     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18477     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18478     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18479                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18480     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18481     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18482     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18483                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18484     FENrulePlies = 0;
18485
18486     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18487       char *q = p;
18488       int w=0, b=0;
18489       while(isalpha(*p)) {
18490         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18491         if(islower(*p)) b |= 1 << (*p++ - 'a');
18492       }
18493       if(*p == '-') p++;
18494       if(p != q) {
18495         board[TOUCHED_W] = ~w;
18496         board[TOUCHED_B] = ~b;
18497         while(*p == ' ') p++;
18498       }
18499     } else
18500
18501     if(nrCastlingRights) {
18502       int fischer = 0;
18503       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18504       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18505           /* castling indicator present, so default becomes no castlings */
18506           for(i=0; i<nrCastlingRights; i++ ) {
18507                  board[CASTLING][i] = NoRights;
18508           }
18509       }
18510       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18511              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18512              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18513              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18514         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18515
18516         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18517             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18518             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18519         }
18520         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18521             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18522         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18523                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18524         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18525                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18526         switch(c) {
18527           case'K':
18528               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18529               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18530               board[CASTLING][2] = whiteKingFile;
18531               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18532               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18533               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18534               break;
18535           case'Q':
18536               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18537               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18538               board[CASTLING][2] = whiteKingFile;
18539               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18540               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18541               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18542               break;
18543           case'k':
18544               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18545               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18546               board[CASTLING][5] = blackKingFile;
18547               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18548               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18549               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18550               break;
18551           case'q':
18552               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18553               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18554               board[CASTLING][5] = blackKingFile;
18555               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18556               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18557               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18558           case '-':
18559               break;
18560           default: /* FRC castlings */
18561               if(c >= 'a') { /* black rights */
18562                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18563                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18564                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18565                   if(i == BOARD_RGHT) break;
18566                   board[CASTLING][5] = i;
18567                   c -= AAA;
18568                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18569                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18570                   if(c > i)
18571                       board[CASTLING][3] = c;
18572                   else
18573                       board[CASTLING][4] = c;
18574               } else { /* white rights */
18575                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18576                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18577                     if(board[0][i] == WhiteKing) break;
18578                   if(i == BOARD_RGHT) break;
18579                   board[CASTLING][2] = i;
18580                   c -= AAA - 'a' + 'A';
18581                   if(board[0][c] >= WhiteKing) break;
18582                   if(c > i)
18583                       board[CASTLING][0] = c;
18584                   else
18585                       board[CASTLING][1] = c;
18586               }
18587         }
18588       }
18589       for(i=0; i<nrCastlingRights; i++)
18590         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18591       if(gameInfo.variant == VariantSChess)
18592         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18593       if(fischer && shuffle) appData.fischerCastling = TRUE;
18594     if (appData.debugMode) {
18595         fprintf(debugFP, "FEN castling rights:");
18596         for(i=0; i<nrCastlingRights; i++)
18597         fprintf(debugFP, " %d", board[CASTLING][i]);
18598         fprintf(debugFP, "\n");
18599     }
18600
18601       while(*p==' ') p++;
18602     }
18603
18604     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18605
18606     /* read e.p. field in games that know e.p. capture */
18607     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18608        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18609        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18610       if(*p=='-') {
18611         p++; board[EP_STATUS] = EP_NONE;
18612       } else {
18613          char c = *p++ - AAA;
18614
18615          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18616          if(*p >= '0' && *p <='9') p++;
18617          board[EP_STATUS] = c;
18618       }
18619     }
18620
18621
18622     if(sscanf(p, "%d", &i) == 1) {
18623         FENrulePlies = i; /* 50-move ply counter */
18624         /* (The move number is still ignored)    */
18625     }
18626
18627     return TRUE;
18628 }
18629
18630 void
18631 EditPositionPasteFEN (char *fen)
18632 {
18633   if (fen != NULL) {
18634     Board initial_position;
18635
18636     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18637       DisplayError(_("Bad FEN position in clipboard"), 0);
18638       return ;
18639     } else {
18640       int savedBlackPlaysFirst = blackPlaysFirst;
18641       EditPositionEvent();
18642       blackPlaysFirst = savedBlackPlaysFirst;
18643       CopyBoard(boards[0], initial_position);
18644       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18645       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18646       DisplayBothClocks();
18647       DrawPosition(FALSE, boards[currentMove]);
18648     }
18649   }
18650 }
18651
18652 static char cseq[12] = "\\   ";
18653
18654 Boolean
18655 set_cont_sequence (char *new_seq)
18656 {
18657     int len;
18658     Boolean ret;
18659
18660     // handle bad attempts to set the sequence
18661         if (!new_seq)
18662                 return 0; // acceptable error - no debug
18663
18664     len = strlen(new_seq);
18665     ret = (len > 0) && (len < sizeof(cseq));
18666     if (ret)
18667       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18668     else if (appData.debugMode)
18669       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18670     return ret;
18671 }
18672
18673 /*
18674     reformat a source message so words don't cross the width boundary.  internal
18675     newlines are not removed.  returns the wrapped size (no null character unless
18676     included in source message).  If dest is NULL, only calculate the size required
18677     for the dest buffer.  lp argument indicats line position upon entry, and it's
18678     passed back upon exit.
18679 */
18680 int
18681 wrap (char *dest, char *src, int count, int width, int *lp)
18682 {
18683     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18684
18685     cseq_len = strlen(cseq);
18686     old_line = line = *lp;
18687     ansi = len = clen = 0;
18688
18689     for (i=0; i < count; i++)
18690     {
18691         if (src[i] == '\033')
18692             ansi = 1;
18693
18694         // if we hit the width, back up
18695         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18696         {
18697             // store i & len in case the word is too long
18698             old_i = i, old_len = len;
18699
18700             // find the end of the last word
18701             while (i && src[i] != ' ' && src[i] != '\n')
18702             {
18703                 i--;
18704                 len--;
18705             }
18706
18707             // word too long?  restore i & len before splitting it
18708             if ((old_i-i+clen) >= width)
18709             {
18710                 i = old_i;
18711                 len = old_len;
18712             }
18713
18714             // extra space?
18715             if (i && src[i-1] == ' ')
18716                 len--;
18717
18718             if (src[i] != ' ' && src[i] != '\n')
18719             {
18720                 i--;
18721                 if (len)
18722                     len--;
18723             }
18724
18725             // now append the newline and continuation sequence
18726             if (dest)
18727                 dest[len] = '\n';
18728             len++;
18729             if (dest)
18730                 strncpy(dest+len, cseq, cseq_len);
18731             len += cseq_len;
18732             line = cseq_len;
18733             clen = cseq_len;
18734             continue;
18735         }
18736
18737         if (dest)
18738             dest[len] = src[i];
18739         len++;
18740         if (!ansi)
18741             line++;
18742         if (src[i] == '\n')
18743             line = 0;
18744         if (src[i] == 'm')
18745             ansi = 0;
18746     }
18747     if (dest && appData.debugMode)
18748     {
18749         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18750             count, width, line, len, *lp);
18751         show_bytes(debugFP, src, count);
18752         fprintf(debugFP, "\ndest: ");
18753         show_bytes(debugFP, dest, len);
18754         fprintf(debugFP, "\n");
18755     }
18756     *lp = dest ? line : old_line;
18757
18758     return len;
18759 }
18760
18761 // [HGM] vari: routines for shelving variations
18762 Boolean modeRestore = FALSE;
18763
18764 void
18765 PushInner (int firstMove, int lastMove)
18766 {
18767         int i, j, nrMoves = lastMove - firstMove;
18768
18769         // push current tail of game on stack
18770         savedResult[storedGames] = gameInfo.result;
18771         savedDetails[storedGames] = gameInfo.resultDetails;
18772         gameInfo.resultDetails = NULL;
18773         savedFirst[storedGames] = firstMove;
18774         savedLast [storedGames] = lastMove;
18775         savedFramePtr[storedGames] = framePtr;
18776         framePtr -= nrMoves; // reserve space for the boards
18777         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18778             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18779             for(j=0; j<MOVE_LEN; j++)
18780                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18781             for(j=0; j<2*MOVE_LEN; j++)
18782                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18783             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18784             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18785             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18786             pvInfoList[firstMove+i-1].depth = 0;
18787             commentList[framePtr+i] = commentList[firstMove+i];
18788             commentList[firstMove+i] = NULL;
18789         }
18790
18791         storedGames++;
18792         forwardMostMove = firstMove; // truncate game so we can start variation
18793 }
18794
18795 void
18796 PushTail (int firstMove, int lastMove)
18797 {
18798         if(appData.icsActive) { // only in local mode
18799                 forwardMostMove = currentMove; // mimic old ICS behavior
18800                 return;
18801         }
18802         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18803
18804         PushInner(firstMove, lastMove);
18805         if(storedGames == 1) GreyRevert(FALSE);
18806         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18807 }
18808
18809 void
18810 PopInner (Boolean annotate)
18811 {
18812         int i, j, nrMoves;
18813         char buf[8000], moveBuf[20];
18814
18815         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18816         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18817         nrMoves = savedLast[storedGames] - currentMove;
18818         if(annotate) {
18819                 int cnt = 10;
18820                 if(!WhiteOnMove(currentMove))
18821                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18822                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18823                 for(i=currentMove; i<forwardMostMove; i++) {
18824                         if(WhiteOnMove(i))
18825                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18826                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18827                         strcat(buf, moveBuf);
18828                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18829                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18830                 }
18831                 strcat(buf, ")");
18832         }
18833         for(i=1; i<=nrMoves; i++) { // copy last variation back
18834             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18835             for(j=0; j<MOVE_LEN; j++)
18836                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18837             for(j=0; j<2*MOVE_LEN; j++)
18838                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18839             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18840             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18841             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18842             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18843             commentList[currentMove+i] = commentList[framePtr+i];
18844             commentList[framePtr+i] = NULL;
18845         }
18846         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18847         framePtr = savedFramePtr[storedGames];
18848         gameInfo.result = savedResult[storedGames];
18849         if(gameInfo.resultDetails != NULL) {
18850             free(gameInfo.resultDetails);
18851       }
18852         gameInfo.resultDetails = savedDetails[storedGames];
18853         forwardMostMove = currentMove + nrMoves;
18854 }
18855
18856 Boolean
18857 PopTail (Boolean annotate)
18858 {
18859         if(appData.icsActive) return FALSE; // only in local mode
18860         if(!storedGames) return FALSE; // sanity
18861         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18862
18863         PopInner(annotate);
18864         if(currentMove < forwardMostMove) ForwardEvent(); else
18865         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18866
18867         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18868         return TRUE;
18869 }
18870
18871 void
18872 CleanupTail ()
18873 {       // remove all shelved variations
18874         int i;
18875         for(i=0; i<storedGames; i++) {
18876             if(savedDetails[i])
18877                 free(savedDetails[i]);
18878             savedDetails[i] = NULL;
18879         }
18880         for(i=framePtr; i<MAX_MOVES; i++) {
18881                 if(commentList[i]) free(commentList[i]);
18882                 commentList[i] = NULL;
18883         }
18884         framePtr = MAX_MOVES-1;
18885         storedGames = 0;
18886 }
18887
18888 void
18889 LoadVariation (int index, char *text)
18890 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18891         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18892         int level = 0, move;
18893
18894         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18895         // first find outermost bracketing variation
18896         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18897             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18898                 if(*p == '{') wait = '}'; else
18899                 if(*p == '[') wait = ']'; else
18900                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18901                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18902             }
18903             if(*p == wait) wait = NULLCHAR; // closing ]} found
18904             p++;
18905         }
18906         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18907         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18908         end[1] = NULLCHAR; // clip off comment beyond variation
18909         ToNrEvent(currentMove-1);
18910         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18911         // kludge: use ParsePV() to append variation to game
18912         move = currentMove;
18913         ParsePV(start, TRUE, TRUE);
18914         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18915         ClearPremoveHighlights();
18916         CommentPopDown();
18917         ToNrEvent(currentMove+1);
18918 }
18919
18920 void
18921 LoadTheme ()
18922 {
18923     char *p, *q, buf[MSG_SIZ];
18924     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18925         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18926         ParseArgsFromString(buf);
18927         ActivateTheme(TRUE); // also redo colors
18928         return;
18929     }
18930     p = nickName;
18931     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18932     {
18933         int len;
18934         q = appData.themeNames;
18935         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18936       if(appData.useBitmaps) {
18937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18938                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18939                 appData.liteBackTextureMode,
18940                 appData.darkBackTextureMode );
18941       } else {
18942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18943                 Col2Text(2),   // lightSquareColor
18944                 Col2Text(3) ); // darkSquareColor
18945       }
18946       if(appData.useBorder) {
18947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18948                 appData.border);
18949       } else {
18950         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18951       }
18952       if(appData.useFont) {
18953         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18954                 appData.renderPiecesWithFont,
18955                 appData.fontToPieceTable,
18956                 Col2Text(9),    // appData.fontBackColorWhite
18957                 Col2Text(10) ); // appData.fontForeColorBlack
18958       } else {
18959         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18960                 appData.pieceDirectory);
18961         if(!appData.pieceDirectory[0])
18962           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18963                 Col2Text(0),   // whitePieceColor
18964                 Col2Text(1) ); // blackPieceColor
18965       }
18966       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18967                 Col2Text(4),   // highlightSquareColor
18968                 Col2Text(5) ); // premoveHighlightColor
18969         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18970         if(insert != q) insert[-1] = NULLCHAR;
18971         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18972         if(q)   free(q);
18973     }
18974     ActivateTheme(FALSE);
18975 }