Prevent out-of-turn grabbing of piece in analysis mode
[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 (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, 0);
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
5165           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5166                                                m[5], m[6] - '0',
5167                                                m[5], m[6] - '0',
5168                                                m[2], m[3] - '0', c);
5169           SendToProgram(buf, cps);
5170       } else
5171       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5172         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5173           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5174           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5175                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5176         } else
5177           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5178                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5179         SendToProgram(buf, cps);
5180       }
5181       else SendToProgram(moveList[moveNum], cps);
5182       /* End of additions by Tord */
5183     }
5184
5185     /* [HGM] setting up the opening has brought engine in force mode! */
5186     /*       Send 'go' if we are in a mode where machine should play. */
5187     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5188         (gameMode == TwoMachinesPlay   ||
5189 #if ZIPPY
5190          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5191 #endif
5192          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5193         SendToProgram("go\n", cps);
5194   if (appData.debugMode) {
5195     fprintf(debugFP, "(extra)\n");
5196   }
5197     }
5198     setboardSpoiledMachineBlack = 0;
5199 }
5200
5201 void
5202 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5203 {
5204     char user_move[MSG_SIZ];
5205     char suffix[4];
5206
5207     if(gameInfo.variant == VariantSChess && promoChar) {
5208         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5209         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5210     } else suffix[0] = NULLCHAR;
5211
5212     switch (moveType) {
5213       default:
5214         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5215                 (int)moveType, fromX, fromY, toX, toY);
5216         DisplayError(user_move + strlen("say "), 0);
5217         break;
5218       case WhiteKingSideCastle:
5219       case BlackKingSideCastle:
5220       case WhiteQueenSideCastleWild:
5221       case BlackQueenSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteHSideCastleFR:
5224       case BlackHSideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5227         break;
5228       case WhiteQueenSideCastle:
5229       case BlackQueenSideCastle:
5230       case WhiteKingSideCastleWild:
5231       case BlackKingSideCastleWild:
5232       /* PUSH Fabien */
5233       case WhiteASideCastleFR:
5234       case BlackASideCastleFR:
5235       /* POP Fabien */
5236         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5237         break;
5238       case WhiteNonPromotion:
5239       case BlackNonPromotion:
5240         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5241         break;
5242       case WhitePromotion:
5243       case BlackPromotion:
5244         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5245            gameInfo.variant == VariantMakruk)
5246           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5247                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5248                 PieceToChar(WhiteFerz));
5249         else if(gameInfo.variant == VariantGreat)
5250           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5251                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5252                 PieceToChar(WhiteMan));
5253         else
5254           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5255                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5256                 promoChar);
5257         break;
5258       case WhiteDrop:
5259       case BlackDrop:
5260       drop:
5261         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5262                  ToUpper(PieceToChar((ChessSquare) fromX)),
5263                  AAA + toX, ONE + toY);
5264         break;
5265       case IllegalMove:  /* could be a variant we don't quite understand */
5266         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5267       case NormalMove:
5268       case WhiteCapturesEnPassant:
5269       case BlackCapturesEnPassant:
5270         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5272         break;
5273     }
5274     SendToICS(user_move);
5275     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5276         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5277 }
5278
5279 void
5280 UploadGameEvent ()
5281 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5282     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5283     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5284     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5285       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5286       return;
5287     }
5288     if(gameMode != IcsExamining) { // is this ever not the case?
5289         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5290
5291         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5292           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5293         } else { // on FICS we must first go to general examine mode
5294           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5295         }
5296         if(gameInfo.variant != VariantNormal) {
5297             // try figure out wild number, as xboard names are not always valid on ICS
5298             for(i=1; i<=36; i++) {
5299               snprintf(buf, MSG_SIZ, "wild/%d", i);
5300                 if(StringToVariant(buf) == gameInfo.variant) break;
5301             }
5302             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5303             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5304             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5305         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5306         SendToICS(ics_prefix);
5307         SendToICS(buf);
5308         if(startedFromSetupPosition || backwardMostMove != 0) {
5309           fen = PositionToFEN(backwardMostMove, NULL, 1);
5310           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5311             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5312             SendToICS(buf);
5313           } else { // FICS: everything has to set by separate bsetup commands
5314             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5315             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5316             SendToICS(buf);
5317             if(!WhiteOnMove(backwardMostMove)) {
5318                 SendToICS("bsetup tomove black\n");
5319             }
5320             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5321             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5322             SendToICS(buf);
5323             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5324             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5325             SendToICS(buf);
5326             i = boards[backwardMostMove][EP_STATUS];
5327             if(i >= 0) { // set e.p.
5328               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5329                 SendToICS(buf);
5330             }
5331             bsetup++;
5332           }
5333         }
5334       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5335             SendToICS("bsetup done\n"); // switch to normal examining.
5336     }
5337     for(i = backwardMostMove; i<last; i++) {
5338         char buf[20];
5339         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5340         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5341             int len = strlen(moveList[i]);
5342             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5343             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5344         }
5345         SendToICS(buf);
5346     }
5347     SendToICS(ics_prefix);
5348     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5349 }
5350
5351 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5352 int legNr = 1;
5353
5354 void
5355 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5356 {
5357     if (rf == DROP_RANK) {
5358       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5359       sprintf(move, "%c@%c%c\n",
5360                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5361     } else {
5362         if (promoChar == 'x' || promoChar == NULLCHAR) {
5363           sprintf(move, "%c%c%c%c\n",
5364                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5365           if(killX >= 0 && killY >= 0) {
5366             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5367             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5368           }
5369         } else {
5370             sprintf(move, "%c%c%c%c%c\n",
5371                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5372           if(killX >= 0 && killY >= 0) {
5373             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5374             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5375           }
5376         }
5377     }
5378 }
5379
5380 void
5381 ProcessICSInitScript (FILE *f)
5382 {
5383     char buf[MSG_SIZ];
5384
5385     while (fgets(buf, MSG_SIZ, f)) {
5386         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5387     }
5388
5389     fclose(f);
5390 }
5391
5392
5393 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5394 int dragging;
5395 static ClickType lastClickType;
5396
5397 int
5398 PieceInString (char *s, ChessSquare piece)
5399 {
5400   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5401   while((p = strchr(s, ID))) {
5402     if(!suffix || p[1] == suffix) return TRUE;
5403     s = p;
5404   }
5405   return FALSE;
5406 }
5407
5408 int
5409 Partner (ChessSquare *p)
5410 { // change piece into promotion partner if one shogi-promotes to the other
5411   ChessSquare partner = promoPartner[*p];
5412   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5413   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5414   *p = partner;
5415   return 1;
5416 }
5417
5418 void
5419 Sweep (int step)
5420 {
5421     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5422     static int toggleFlag;
5423     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5424     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5425     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5426     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5427     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5428     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5429     do {
5430         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5431         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5432         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5433         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5434         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5437             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5438             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5439             promoSweep == pawn ||
5440             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5441             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5442     if(toX >= 0) {
5443         int victim = boards[currentMove][toY][toX];
5444         boards[currentMove][toY][toX] = promoSweep;
5445         DrawPosition(FALSE, boards[currentMove]);
5446         boards[currentMove][toY][toX] = victim;
5447     } else
5448     ChangeDragPiece(promoSweep);
5449 }
5450
5451 int
5452 PromoScroll (int x, int y)
5453 {
5454   int step = 0;
5455
5456   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5457   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5458   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5459   if(!step) return FALSE;
5460   lastX = x; lastY = y;
5461   if((promoSweep < BlackPawn) == flipView) step = -step;
5462   if(step > 0) selectFlag = 1;
5463   if(!selectFlag) Sweep(step);
5464   return FALSE;
5465 }
5466
5467 void
5468 NextPiece (int step)
5469 {
5470     ChessSquare piece = boards[currentMove][toY][toX];
5471     do {
5472         pieceSweep -= step;
5473         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5474         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5475         if(!step) step = -1;
5476     } while(PieceToChar(pieceSweep) == '.');
5477     boards[currentMove][toY][toX] = pieceSweep;
5478     DrawPosition(FALSE, boards[currentMove]);
5479     boards[currentMove][toY][toX] = piece;
5480 }
5481 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5482 void
5483 AlphaRank (char *move, int n)
5484 {
5485 //    char *p = move, c; int x, y;
5486
5487     if (appData.debugMode) {
5488         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5489     }
5490
5491     if(move[1]=='*' &&
5492        move[2]>='0' && move[2]<='9' &&
5493        move[3]>='a' && move[3]<='x'    ) {
5494         move[1] = '@';
5495         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5496         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5497     } else
5498     if(move[0]>='0' && move[0]<='9' &&
5499        move[1]>='a' && move[1]<='x' &&
5500        move[2]>='0' && move[2]<='9' &&
5501        move[3]>='a' && move[3]<='x'    ) {
5502         /* input move, Shogi -> normal */
5503         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5504         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5505         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5506         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5507     } else
5508     if(move[1]=='@' &&
5509        move[3]>='0' && move[3]<='9' &&
5510        move[2]>='a' && move[2]<='x'    ) {
5511         move[1] = '*';
5512         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5513         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5514     } else
5515     if(
5516        move[0]>='a' && move[0]<='x' &&
5517        move[3]>='0' && move[3]<='9' &&
5518        move[2]>='a' && move[2]<='x'    ) {
5519          /* output move, normal -> Shogi */
5520         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5521         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5522         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5523         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5524         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5525     }
5526     if (appData.debugMode) {
5527         fprintf(debugFP, "   out = '%s'\n", move);
5528     }
5529 }
5530
5531 char yy_textstr[8000];
5532
5533 /* Parser for moves from gnuchess, ICS, or user typein box */
5534 Boolean
5535 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5536 {
5537     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5538
5539     switch (*moveType) {
5540       case WhitePromotion:
5541       case BlackPromotion:
5542       case WhiteNonPromotion:
5543       case BlackNonPromotion:
5544       case NormalMove:
5545       case FirstLeg:
5546       case WhiteCapturesEnPassant:
5547       case BlackCapturesEnPassant:
5548       case WhiteKingSideCastle:
5549       case WhiteQueenSideCastle:
5550       case BlackKingSideCastle:
5551       case BlackQueenSideCastle:
5552       case WhiteKingSideCastleWild:
5553       case WhiteQueenSideCastleWild:
5554       case BlackKingSideCastleWild:
5555       case BlackQueenSideCastleWild:
5556       /* Code added by Tord: */
5557       case WhiteHSideCastleFR:
5558       case WhiteASideCastleFR:
5559       case BlackHSideCastleFR:
5560       case BlackASideCastleFR:
5561       /* End of code added by Tord */
5562       case IllegalMove:         /* bug or odd chess variant */
5563         if(currentMoveString[1] == '@') { // illegal drop
5564           *fromX = WhiteOnMove(moveNum) ?
5565             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5566             (int) CharToPiece(ToLower(currentMoveString[0]));
5567           goto drop;
5568         }
5569         *fromX = currentMoveString[0] - AAA;
5570         *fromY = currentMoveString[1] - ONE;
5571         *toX = currentMoveString[2] - AAA;
5572         *toY = currentMoveString[3] - ONE;
5573         *promoChar = currentMoveString[4];
5574         if(*promoChar == ';') *promoChar = currentMoveString[7];
5575         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5576             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5577     if (appData.debugMode) {
5578         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5579     }
5580             *fromX = *fromY = *toX = *toY = 0;
5581             return FALSE;
5582         }
5583         if (appData.testLegality) {
5584           return (*moveType != IllegalMove);
5585         } else {
5586           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5587                          // [HGM] lion: if this is a double move we are less critical
5588                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5589         }
5590
5591       case WhiteDrop:
5592       case BlackDrop:
5593         *fromX = *moveType == WhiteDrop ?
5594           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5595           (int) CharToPiece(ToLower(currentMoveString[0]));
5596       drop:
5597         *fromY = DROP_RANK;
5598         *toX = currentMoveString[2] - AAA;
5599         *toY = currentMoveString[3] - ONE;
5600         *promoChar = NULLCHAR;
5601         return TRUE;
5602
5603       case AmbiguousMove:
5604       case ImpossibleMove:
5605       case EndOfFile:
5606       case ElapsedTime:
5607       case Comment:
5608       case PGNTag:
5609       case NAG:
5610       case WhiteWins:
5611       case BlackWins:
5612       case GameIsDrawn:
5613       default:
5614     if (appData.debugMode) {
5615         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5616     }
5617         /* bug? */
5618         *fromX = *fromY = *toX = *toY = 0;
5619         *promoChar = NULLCHAR;
5620         return FALSE;
5621     }
5622 }
5623
5624 Boolean pushed = FALSE;
5625 char *lastParseAttempt;
5626
5627 void
5628 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5629 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5630   int fromX, fromY, toX, toY; char promoChar;
5631   ChessMove moveType;
5632   Boolean valid;
5633   int nr = 0;
5634
5635   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5636   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5637     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5638     pushed = TRUE;
5639   }
5640   endPV = forwardMostMove;
5641   do {
5642     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5643     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5644     lastParseAttempt = pv;
5645     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5646     if(!valid && nr == 0 &&
5647        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5648         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5649         // Hande case where played move is different from leading PV move
5650         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5651         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5652         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5653         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5654           endPV += 2; // if position different, keep this
5655           moveList[endPV-1][0] = fromX + AAA;
5656           moveList[endPV-1][1] = fromY + ONE;
5657           moveList[endPV-1][2] = toX + AAA;
5658           moveList[endPV-1][3] = toY + ONE;
5659           parseList[endPV-1][0] = NULLCHAR;
5660           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5661         }
5662       }
5663     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5664     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5665     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5666     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5667         valid++; // allow comments in PV
5668         continue;
5669     }
5670     nr++;
5671     if(endPV+1 > framePtr) break; // no space, truncate
5672     if(!valid) break;
5673     endPV++;
5674     CopyBoard(boards[endPV], boards[endPV-1]);
5675     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5676     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5677     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5678     CoordsToAlgebraic(boards[endPV - 1],
5679                              PosFlags(endPV - 1),
5680                              fromY, fromX, toY, toX, promoChar,
5681                              parseList[endPV - 1]);
5682   } while(valid);
5683   if(atEnd == 2) return; // used hidden, for PV conversion
5684   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5685   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5686   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5687                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5688   DrawPosition(TRUE, boards[currentMove]);
5689 }
5690
5691 int
5692 MultiPV (ChessProgramState *cps, int kind)
5693 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5694         int i;
5695         for(i=0; i<cps->nrOptions; i++) {
5696             char *s = cps->option[i].name;
5697             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5698             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5699                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5700         }
5701         return -1;
5702 }
5703
5704 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5705 static int multi, pv_margin;
5706 static ChessProgramState *activeCps;
5707
5708 Boolean
5709 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5710 {
5711         int startPV, lineStart, origIndex = index;
5712         char *p, buf2[MSG_SIZ];
5713         ChessProgramState *cps = (pane ? &second : &first);
5714
5715         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5716         lastX = x; lastY = y;
5717         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5718         lineStart = startPV = index;
5719         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5720         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5721         index = startPV;
5722         do{ while(buf[index] && buf[index] != '\n') index++;
5723         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5724         buf[index] = 0;
5725         if(lineStart == 0 && gameMode == AnalyzeMode) {
5726             int n = 0;
5727             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5728             if(n == 0) { // click not on "fewer" or "more"
5729                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5730                     pv_margin = cps->option[multi].value;
5731                     activeCps = cps; // non-null signals margin adjustment
5732                 }
5733             } else if((multi = MultiPV(cps, 1)) >= 0) {
5734                 n += cps->option[multi].value; if(n < 1) n = 1;
5735                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5736                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5737                 cps->option[multi].value = n;
5738                 *start = *end = 0;
5739                 return FALSE;
5740             }
5741         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5742                 ExcludeClick(origIndex - lineStart);
5743                 return FALSE;
5744         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5745                 Collapse(origIndex - lineStart);
5746                 return FALSE;
5747         }
5748         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5749         *start = startPV; *end = index-1;
5750         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5751         return TRUE;
5752 }
5753
5754 char *
5755 PvToSAN (char *pv)
5756 {
5757         static char buf[10*MSG_SIZ];
5758         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5759         *buf = NULLCHAR;
5760         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5761         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5762         for(i = forwardMostMove; i<endPV; i++){
5763             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5764             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5765             k += strlen(buf+k);
5766         }
5767         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5768         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5769         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5770         endPV = savedEnd;
5771         return buf;
5772 }
5773
5774 Boolean
5775 LoadPV (int x, int y)
5776 { // called on right mouse click to load PV
5777   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5778   lastX = x; lastY = y;
5779   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5780   extendGame = FALSE;
5781   return TRUE;
5782 }
5783
5784 void
5785 UnLoadPV ()
5786 {
5787   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5788   if(activeCps) {
5789     if(pv_margin != activeCps->option[multi].value) {
5790       char buf[MSG_SIZ];
5791       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5792       SendToProgram(buf, activeCps);
5793       activeCps->option[multi].value = pv_margin;
5794     }
5795     activeCps = NULL;
5796     return;
5797   }
5798   if(endPV < 0) return;
5799   if(appData.autoCopyPV) CopyFENToClipboard();
5800   endPV = -1;
5801   if(extendGame && currentMove > forwardMostMove) {
5802         Boolean saveAnimate = appData.animate;
5803         if(pushed) {
5804             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5805                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5806             } else storedGames--; // abandon shelved tail of original game
5807         }
5808         pushed = FALSE;
5809         forwardMostMove = currentMove;
5810         currentMove = oldFMM;
5811         appData.animate = FALSE;
5812         ToNrEvent(forwardMostMove);
5813         appData.animate = saveAnimate;
5814   }
5815   currentMove = forwardMostMove;
5816   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5817   ClearPremoveHighlights();
5818   DrawPosition(TRUE, boards[currentMove]);
5819 }
5820
5821 void
5822 MovePV (int x, int y, int h)
5823 { // step through PV based on mouse coordinates (called on mouse move)
5824   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5825
5826   if(activeCps) { // adjusting engine's multi-pv margin
5827     if(x > lastX) pv_margin++; else
5828     if(x < lastX) pv_margin -= (pv_margin > 0);
5829     if(x != lastX) {
5830       char buf[MSG_SIZ];
5831       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5832       DisplayMessage(buf, "");
5833     }
5834     lastX = x;
5835     return;
5836   }
5837   // we must somehow check if right button is still down (might be released off board!)
5838   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5839   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5840   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5841   if(!step) return;
5842   lastX = x; lastY = y;
5843
5844   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5845   if(endPV < 0) return;
5846   if(y < margin) step = 1; else
5847   if(y > h - margin) step = -1;
5848   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5849   currentMove += step;
5850   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5851   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5852                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5853   DrawPosition(FALSE, boards[currentMove]);
5854 }
5855
5856
5857 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5858 // All positions will have equal probability, but the current method will not provide a unique
5859 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5860 #define DARK 1
5861 #define LITE 2
5862 #define ANY 3
5863
5864 int squaresLeft[4];
5865 int piecesLeft[(int)BlackPawn];
5866 int seed, nrOfShuffles;
5867
5868 void
5869 GetPositionNumber ()
5870 {       // sets global variable seed
5871         int i;
5872
5873         seed = appData.defaultFrcPosition;
5874         if(seed < 0) { // randomize based on time for negative FRC position numbers
5875                 for(i=0; i<50; i++) seed += random();
5876                 seed = random() ^ random() >> 8 ^ random() << 8;
5877                 if(seed<0) seed = -seed;
5878         }
5879 }
5880
5881 int
5882 put (Board board, int pieceType, int rank, int n, int shade)
5883 // put the piece on the (n-1)-th empty squares of the given shade
5884 {
5885         int i;
5886
5887         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5888                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5889                         board[rank][i] = (ChessSquare) pieceType;
5890                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5891                         squaresLeft[ANY]--;
5892                         piecesLeft[pieceType]--;
5893                         return i;
5894                 }
5895         }
5896         return -1;
5897 }
5898
5899
5900 void
5901 AddOnePiece (Board board, int pieceType, int rank, int shade)
5902 // calculate where the next piece goes, (any empty square), and put it there
5903 {
5904         int i;
5905
5906         i = seed % squaresLeft[shade];
5907         nrOfShuffles *= squaresLeft[shade];
5908         seed /= squaresLeft[shade];
5909         put(board, pieceType, rank, i, shade);
5910 }
5911
5912 void
5913 AddTwoPieces (Board board, int pieceType, int rank)
5914 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5915 {
5916         int i, n=squaresLeft[ANY], j=n-1, k;
5917
5918         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5919         i = seed % k;  // pick one
5920         nrOfShuffles *= k;
5921         seed /= k;
5922         while(i >= j) i -= j--;
5923         j = n - 1 - j; i += j;
5924         put(board, pieceType, rank, j, ANY);
5925         put(board, pieceType, rank, i, ANY);
5926 }
5927
5928 void
5929 SetUpShuffle (Board board, int number)
5930 {
5931         int i, p, first=1;
5932
5933         GetPositionNumber(); nrOfShuffles = 1;
5934
5935         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5936         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5937         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5938
5939         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5940
5941         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5942             p = (int) board[0][i];
5943             if(p < (int) BlackPawn) piecesLeft[p] ++;
5944             board[0][i] = EmptySquare;
5945         }
5946
5947         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5948             // shuffles restricted to allow normal castling put KRR first
5949             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5950                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5951             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5952                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5953             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5954                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5955             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5956                 put(board, WhiteRook, 0, 0, ANY);
5957             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5958         }
5959
5960         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5961             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5962             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5963                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5964                 while(piecesLeft[p] >= 2) {
5965                     AddOnePiece(board, p, 0, LITE);
5966                     AddOnePiece(board, p, 0, DARK);
5967                 }
5968                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5969             }
5970
5971         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5972             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5973             // but we leave King and Rooks for last, to possibly obey FRC restriction
5974             if(p == (int)WhiteRook) continue;
5975             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5976             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5977         }
5978
5979         // now everything is placed, except perhaps King (Unicorn) and Rooks
5980
5981         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5982             // Last King gets castling rights
5983             while(piecesLeft[(int)WhiteUnicorn]) {
5984                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5985                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5986             }
5987
5988             while(piecesLeft[(int)WhiteKing]) {
5989                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5990                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5991             }
5992
5993
5994         } else {
5995             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5996             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5997         }
5998
5999         // Only Rooks can be left; simply place them all
6000         while(piecesLeft[(int)WhiteRook]) {
6001                 i = put(board, WhiteRook, 0, 0, ANY);
6002                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6003                         if(first) {
6004                                 first=0;
6005                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6006                         }
6007                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6008                 }
6009         }
6010         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6011             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6012         }
6013
6014         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6015 }
6016
6017 int
6018 ptclen (const char *s, char *escapes)
6019 {
6020     int n = 0;
6021     if(!*escapes) return strlen(s);
6022     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6023     return n;
6024 }
6025
6026 int
6027 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6028 /* [HGM] moved here from winboard.c because of its general usefulness */
6029 /*       Basically a safe strcpy that uses the last character as King */
6030 {
6031     int result = FALSE; int NrPieces;
6032     unsigned char partner[EmptySquare];
6033
6034     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6035                     && NrPieces >= 12 && !(NrPieces&1)) {
6036         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6037
6038         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6039         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6040             char *p, c=0;
6041             if(map[j] == '/') offs = WhitePBishop - i, j++;
6042             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6043             table[i+offs] = map[j++];
6044             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6045             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6046             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6047         }
6048         table[(int) WhiteKing]  = map[j++];
6049         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6050             char *p, c=0;
6051             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6052             i = WHITE_TO_BLACK ii;
6053             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6054             table[i+offs] = map[j++];
6055             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6056             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6057             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6058         }
6059         table[(int) BlackKing]  = map[j++];
6060
6061
6062         if(*escapes) { // set up promotion pairing
6063             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6064             // pieceToChar entirely filled, so we can look up specified partners
6065             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6066                 int c = table[i];
6067                 if(c == '^' || c == '-') { // has specified partner
6068                     int p;
6069                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6070                     if(c == '^') table[i] = '+';
6071                     if(p < EmptySquare) {
6072                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6073                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6074                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6075                     }
6076                 } else if(c == '*') {
6077                     table[i] = partner[i];
6078                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6079                 }
6080             }
6081         }
6082
6083         result = TRUE;
6084     }
6085
6086     return result;
6087 }
6088
6089 int
6090 SetCharTable (unsigned char *table, const char * map)
6091 {
6092     return SetCharTableEsc(table, map, "");
6093 }
6094
6095 void
6096 Prelude (Board board)
6097 {       // [HGM] superchess: random selection of exo-pieces
6098         int i, j, k; ChessSquare p;
6099         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6100
6101         GetPositionNumber(); // use FRC position number
6102
6103         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6104             SetCharTable(pieceToChar, appData.pieceToCharTable);
6105             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6106                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6107         }
6108
6109         j = seed%4;                 seed /= 4;
6110         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6111         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6112         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6113         j = seed%3 + (seed%3 >= j); seed /= 3;
6114         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6115         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6116         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6117         j = seed%3;                 seed /= 3;
6118         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
6122         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6126         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6127         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6128         put(board, exoPieces[0],    0, 0, ANY);
6129         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6130 }
6131
6132 void
6133 InitPosition (int redraw)
6134 {
6135     ChessSquare (* pieces)[BOARD_FILES];
6136     int i, j, pawnRow=1, pieceRows=1, overrule,
6137     oldx = gameInfo.boardWidth,
6138     oldy = gameInfo.boardHeight,
6139     oldh = gameInfo.holdingsWidth;
6140     static int oldv;
6141
6142     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6143
6144     /* [AS] Initialize pv info list [HGM] and game status */
6145     {
6146         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6147             pvInfoList[i].depth = 0;
6148             boards[i][EP_STATUS] = EP_NONE;
6149             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6150         }
6151
6152         initialRulePlies = 0; /* 50-move counter start */
6153
6154         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6155         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6156     }
6157
6158
6159     /* [HGM] logic here is completely changed. In stead of full positions */
6160     /* the initialized data only consist of the two backranks. The switch */
6161     /* selects which one we will use, which is than copied to the Board   */
6162     /* initialPosition, which for the rest is initialized by Pawns and    */
6163     /* empty squares. This initial position is then copied to boards[0],  */
6164     /* possibly after shuffling, so that it remains available.            */
6165
6166     gameInfo.holdingsWidth = 0; /* default board sizes */
6167     gameInfo.boardWidth    = 8;
6168     gameInfo.boardHeight   = 8;
6169     gameInfo.holdingsSize  = 0;
6170     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6171     for(i=0; i<BOARD_FILES-6; i++)
6172       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6173     initialPosition[EP_STATUS] = EP_NONE;
6174     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6175     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6176     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6177          SetCharTable(pieceNickName, appData.pieceNickNames);
6178     else SetCharTable(pieceNickName, "............");
6179     pieces = FIDEArray;
6180
6181     switch (gameInfo.variant) {
6182     case VariantFischeRandom:
6183       shuffleOpenings = TRUE;
6184       appData.fischerCastling = TRUE;
6185     default:
6186       break;
6187     case VariantShatranj:
6188       pieces = ShatranjArray;
6189       nrCastlingRights = 0;
6190       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6191       break;
6192     case VariantMakruk:
6193       pieces = makrukArray;
6194       nrCastlingRights = 0;
6195       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6196       break;
6197     case VariantASEAN:
6198       pieces = aseanArray;
6199       nrCastlingRights = 0;
6200       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6201       break;
6202     case VariantTwoKings:
6203       pieces = twoKingsArray;
6204       break;
6205     case VariantGrand:
6206       pieces = GrandArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6209       gameInfo.boardWidth = 10;
6210       gameInfo.boardHeight = 10;
6211       gameInfo.holdingsSize = 7;
6212       break;
6213     case VariantCapaRandom:
6214       shuffleOpenings = TRUE;
6215       appData.fischerCastling = TRUE;
6216     case VariantCapablanca:
6217       pieces = CapablancaArray;
6218       gameInfo.boardWidth = 10;
6219       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6220       break;
6221     case VariantGothic:
6222       pieces = GothicArray;
6223       gameInfo.boardWidth = 10;
6224       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225       break;
6226     case VariantSChess:
6227       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6228       gameInfo.holdingsSize = 7;
6229       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6230       break;
6231     case VariantJanus:
6232       pieces = JanusArray;
6233       gameInfo.boardWidth = 10;
6234       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6235       nrCastlingRights = 6;
6236         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6237         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6238         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6239         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6240         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6241         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6242       break;
6243     case VariantFalcon:
6244       pieces = FalconArray;
6245       gameInfo.boardWidth = 10;
6246       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6247       break;
6248     case VariantXiangqi:
6249       pieces = XiangqiArray;
6250       gameInfo.boardWidth  = 9;
6251       gameInfo.boardHeight = 10;
6252       nrCastlingRights = 0;
6253       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6254       break;
6255     case VariantShogi:
6256       pieces = ShogiArray;
6257       gameInfo.boardWidth  = 9;
6258       gameInfo.boardHeight = 9;
6259       gameInfo.holdingsSize = 7;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6262       break;
6263     case VariantChu:
6264       pieces = ChuArray; pieceRows = 3;
6265       gameInfo.boardWidth  = 12;
6266       gameInfo.boardHeight = 12;
6267       nrCastlingRights = 0;
6268       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6269                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6270       break;
6271     case VariantCourier:
6272       pieces = CourierArray;
6273       gameInfo.boardWidth  = 12;
6274       nrCastlingRights = 0;
6275       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6276       break;
6277     case VariantKnightmate:
6278       pieces = KnightmateArray;
6279       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6280       break;
6281     case VariantSpartan:
6282       pieces = SpartanArray;
6283       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6284       break;
6285     case VariantLion:
6286       pieces = lionArray;
6287       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6288       break;
6289     case VariantChuChess:
6290       pieces = ChuChessArray;
6291       gameInfo.boardWidth = 10;
6292       gameInfo.boardHeight = 10;
6293       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6294       break;
6295     case VariantFairy:
6296       pieces = fairyArray;
6297       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6298       break;
6299     case VariantGreat:
6300       pieces = GreatArray;
6301       gameInfo.boardWidth = 10;
6302       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6303       gameInfo.holdingsSize = 8;
6304       break;
6305     case VariantSuper:
6306       pieces = FIDEArray;
6307       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6308       gameInfo.holdingsSize = 8;
6309       startedFromSetupPosition = TRUE;
6310       break;
6311     case VariantCrazyhouse:
6312     case VariantBughouse:
6313       pieces = FIDEArray;
6314       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6315       gameInfo.holdingsSize = 5;
6316       break;
6317     case VariantWildCastle:
6318       pieces = FIDEArray;
6319       /* !!?shuffle with kings guaranteed to be on d or e file */
6320       shuffleOpenings = 1;
6321       break;
6322     case VariantNoCastle:
6323       pieces = FIDEArray;
6324       nrCastlingRights = 0;
6325       /* !!?unconstrained back-rank shuffle */
6326       shuffleOpenings = 1;
6327       break;
6328     }
6329
6330     overrule = 0;
6331     if(appData.NrFiles >= 0) {
6332         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6333         gameInfo.boardWidth = appData.NrFiles;
6334     }
6335     if(appData.NrRanks >= 0) {
6336         gameInfo.boardHeight = appData.NrRanks;
6337     }
6338     if(appData.holdingsSize >= 0) {
6339         i = appData.holdingsSize;
6340         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6341         gameInfo.holdingsSize = i;
6342     }
6343     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6344     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6345         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6346
6347     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6348     if(pawnRow < 1) pawnRow = 1;
6349     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6350        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6351     if(gameInfo.variant == VariantChu) pawnRow = 3;
6352
6353     /* User pieceToChar list overrules defaults */
6354     if(appData.pieceToCharTable != NULL)
6355         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6356
6357     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6358
6359         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6360             s = (ChessSquare) 0; /* account holding counts in guard band */
6361         for( i=0; i<BOARD_HEIGHT; i++ )
6362             initialPosition[i][j] = s;
6363
6364         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6365         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6366         initialPosition[pawnRow][j] = WhitePawn;
6367         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6368         if(gameInfo.variant == VariantXiangqi) {
6369             if(j&1) {
6370                 initialPosition[pawnRow][j] =
6371                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6372                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6373                    initialPosition[2][j] = WhiteCannon;
6374                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6375                 }
6376             }
6377         }
6378         if(gameInfo.variant == VariantChu) {
6379              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6380                initialPosition[pawnRow+1][j] = WhiteCobra,
6381                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6382              for(i=1; i<pieceRows; i++) {
6383                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6384                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6385              }
6386         }
6387         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6388             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6389                initialPosition[0][j] = WhiteRook;
6390                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6391             }
6392         }
6393         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6394     }
6395     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6396     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6397
6398             j=BOARD_LEFT+1;
6399             initialPosition[1][j] = WhiteBishop;
6400             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6401             j=BOARD_RGHT-2;
6402             initialPosition[1][j] = WhiteRook;
6403             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6404     }
6405
6406     if( nrCastlingRights == -1) {
6407         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6408         /*       This sets default castling rights from none to normal corners   */
6409         /* Variants with other castling rights must set them themselves above    */
6410         nrCastlingRights = 6;
6411
6412         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6413         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6414         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6415         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6416         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6417         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6418      }
6419
6420      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6421      if(gameInfo.variant == VariantGreat) { // promotion commoners
6422         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6423         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6424         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6425         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6426      }
6427      if( gameInfo.variant == VariantSChess ) {
6428       initialPosition[1][0] = BlackMarshall;
6429       initialPosition[2][0] = BlackAngel;
6430       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6431       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6432       initialPosition[1][1] = initialPosition[2][1] =
6433       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6434      }
6435   if (appData.debugMode) {
6436     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6437   }
6438     if(shuffleOpenings) {
6439         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6440         startedFromSetupPosition = TRUE;
6441     }
6442     if(startedFromPositionFile) {
6443       /* [HGM] loadPos: use PositionFile for every new game */
6444       CopyBoard(initialPosition, filePosition);
6445       for(i=0; i<nrCastlingRights; i++)
6446           initialRights[i] = filePosition[CASTLING][i];
6447       startedFromSetupPosition = TRUE;
6448     }
6449
6450     CopyBoard(boards[0], initialPosition);
6451
6452     if(oldx != gameInfo.boardWidth ||
6453        oldy != gameInfo.boardHeight ||
6454        oldv != gameInfo.variant ||
6455        oldh != gameInfo.holdingsWidth
6456                                          )
6457             InitDrawingSizes(-2 ,0);
6458
6459     oldv = gameInfo.variant;
6460     if (redraw)
6461       DrawPosition(TRUE, boards[currentMove]);
6462 }
6463
6464 void
6465 SendBoard (ChessProgramState *cps, int moveNum)
6466 {
6467     char message[MSG_SIZ];
6468
6469     if (cps->useSetboard) {
6470       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6471       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6472       SendToProgram(message, cps);
6473       free(fen);
6474
6475     } else {
6476       ChessSquare *bp;
6477       int i, j, left=0, right=BOARD_WIDTH;
6478       /* Kludge to set black to move, avoiding the troublesome and now
6479        * deprecated "black" command.
6480        */
6481       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6482         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6483
6484       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6485
6486       SendToProgram("edit\n", cps);
6487       SendToProgram("#\n", cps);
6488       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6489         bp = &boards[moveNum][i][left];
6490         for (j = left; j < right; j++, bp++) {
6491           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6492           if ((int) *bp < (int) BlackPawn) {
6493             if(j == BOARD_RGHT+1)
6494                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6495             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6496             if(message[0] == '+' || message[0] == '~') {
6497               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6498                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6499                         AAA + j, ONE + i - '0');
6500             }
6501             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6502                 message[1] = BOARD_RGHT   - 1 - j + '1';
6503                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6504             }
6505             SendToProgram(message, cps);
6506           }
6507         }
6508       }
6509
6510       SendToProgram("c\n", cps);
6511       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6512         bp = &boards[moveNum][i][left];
6513         for (j = left; j < right; j++, bp++) {
6514           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6515           if (((int) *bp != (int) EmptySquare)
6516               && ((int) *bp >= (int) BlackPawn)) {
6517             if(j == BOARD_LEFT-2)
6518                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6519             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6520                     AAA + j, ONE + i - '0');
6521             if(message[0] == '+' || message[0] == '~') {
6522               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6523                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6524                         AAA + j, ONE + i - '0');
6525             }
6526             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6527                 message[1] = BOARD_RGHT   - 1 - j + '1';
6528                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6529             }
6530             SendToProgram(message, cps);
6531           }
6532         }
6533       }
6534
6535       SendToProgram(".\n", cps);
6536     }
6537     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6538 }
6539
6540 char exclusionHeader[MSG_SIZ];
6541 int exCnt, excludePtr;
6542 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6543 static Exclusion excluTab[200];
6544 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6545
6546 static void
6547 WriteMap (int s)
6548 {
6549     int j;
6550     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6551     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6552 }
6553
6554 static void
6555 ClearMap ()
6556 {
6557     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6558     excludePtr = 24; exCnt = 0;
6559     WriteMap(0);
6560 }
6561
6562 static void
6563 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6564 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6565     char buf[2*MOVE_LEN], *p;
6566     Exclusion *e = excluTab;
6567     int i;
6568     for(i=0; i<exCnt; i++)
6569         if(e[i].ff == fromX && e[i].fr == fromY &&
6570            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6571     if(i == exCnt) { // was not in exclude list; add it
6572         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6573         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6574             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6575             return; // abort
6576         }
6577         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6578         excludePtr++; e[i].mark = excludePtr++;
6579         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6580         exCnt++;
6581     }
6582     exclusionHeader[e[i].mark] = state;
6583 }
6584
6585 static int
6586 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6587 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6588     char buf[MSG_SIZ];
6589     int j, k;
6590     ChessMove moveType;
6591     if((signed char)promoChar == -1) { // kludge to indicate best move
6592         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6593             return 1; // if unparsable, abort
6594     }
6595     // update exclusion map (resolving toggle by consulting existing state)
6596     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6597     j = k%8; k >>= 3;
6598     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6599     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6600          excludeMap[k] |=   1<<j;
6601     else excludeMap[k] &= ~(1<<j);
6602     // update header
6603     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6604     // inform engine
6605     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6606     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6607     SendToBoth(buf);
6608     return (state == '+');
6609 }
6610
6611 static void
6612 ExcludeClick (int index)
6613 {
6614     int i, j;
6615     Exclusion *e = excluTab;
6616     if(index < 25) { // none, best or tail clicked
6617         if(index < 13) { // none: include all
6618             WriteMap(0); // clear map
6619             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6620             SendToBoth("include all\n"); // and inform engine
6621         } else if(index > 18) { // tail
6622             if(exclusionHeader[19] == '-') { // tail was excluded
6623                 SendToBoth("include all\n");
6624                 WriteMap(0); // clear map completely
6625                 // now re-exclude selected moves
6626                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6627                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6628             } else { // tail was included or in mixed state
6629                 SendToBoth("exclude all\n");
6630                 WriteMap(0xFF); // fill map completely
6631                 // now re-include selected moves
6632                 j = 0; // count them
6633                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6634                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6635                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6636             }
6637         } else { // best
6638             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6639         }
6640     } else {
6641         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6642             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6643             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6644             break;
6645         }
6646     }
6647 }
6648
6649 ChessSquare
6650 DefaultPromoChoice (int white)
6651 {
6652     ChessSquare result;
6653     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6654        gameInfo.variant == VariantMakruk)
6655         result = WhiteFerz; // no choice
6656     else if(gameInfo.variant == VariantASEAN)
6657         result = WhiteRook; // no choice
6658     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6659         result= WhiteKing; // in Suicide Q is the last thing we want
6660     else if(gameInfo.variant == VariantSpartan)
6661         result = white ? WhiteQueen : WhiteAngel;
6662     else result = WhiteQueen;
6663     if(!white) result = WHITE_TO_BLACK result;
6664     return result;
6665 }
6666
6667 static int autoQueen; // [HGM] oneclick
6668
6669 int
6670 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6671 {
6672     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6673     /* [HGM] add Shogi promotions */
6674     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6675     ChessSquare piece, partner;
6676     ChessMove moveType;
6677     Boolean premove;
6678
6679     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6680     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6681
6682     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6683       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6684         return FALSE;
6685
6686     piece = boards[currentMove][fromY][fromX];
6687     if(gameInfo.variant == VariantChu) {
6688         promotionZoneSize = BOARD_HEIGHT/3;
6689         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6690     } else if(gameInfo.variant == VariantShogi) {
6691         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6692         highestPromotingPiece = (int)WhiteAlfil;
6693     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6694         promotionZoneSize = 3;
6695     }
6696
6697     // Treat Lance as Pawn when it is not representing Amazon or Lance
6698     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6699         if(piece == WhiteLance) piece = WhitePawn; else
6700         if(piece == BlackLance) piece = BlackPawn;
6701     }
6702
6703     // next weed out all moves that do not touch the promotion zone at all
6704     if((int)piece >= BlackPawn) {
6705         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6706              return FALSE;
6707         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6708         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6709     } else {
6710         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6711            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6712         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6713              return FALSE;
6714     }
6715
6716     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6717
6718     // weed out mandatory Shogi promotions
6719     if(gameInfo.variant == VariantShogi) {
6720         if(piece >= BlackPawn) {
6721             if(toY == 0 && piece == BlackPawn ||
6722                toY == 0 && piece == BlackQueen ||
6723                toY <= 1 && piece == BlackKnight) {
6724                 *promoChoice = '+';
6725                 return FALSE;
6726             }
6727         } else {
6728             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6729                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6730                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6731                 *promoChoice = '+';
6732                 return FALSE;
6733             }
6734         }
6735     }
6736
6737     // weed out obviously illegal Pawn moves
6738     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6739         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6740         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6741         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6742         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6743         // note we are not allowed to test for valid (non-)capture, due to premove
6744     }
6745
6746     // we either have a choice what to promote to, or (in Shogi) whether to promote
6747     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6748        gameInfo.variant == VariantMakruk) {
6749         ChessSquare p=BlackFerz;  // no choice
6750         while(p < EmptySquare) {  //but make sure we use piece that exists
6751             *promoChoice = PieceToChar(p++);
6752             if(*promoChoice != '.') break;
6753         }
6754         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6755     }
6756     // no sense asking what we must promote to if it is going to explode...
6757     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6758         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6759         return FALSE;
6760     }
6761     // give caller the default choice even if we will not make it
6762     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6763     partner = piece; // pieces can promote if the pieceToCharTable says so
6764     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6765     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6766     if(        sweepSelect && gameInfo.variant != VariantGreat
6767                            && gameInfo.variant != VariantGrand
6768                            && gameInfo.variant != VariantSuper) return FALSE;
6769     if(autoQueen) return FALSE; // predetermined
6770
6771     // suppress promotion popup on illegal moves that are not premoves
6772     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6773               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6774     if(appData.testLegality && !premove) {
6775         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6776                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6777         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6778         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6779             return FALSE;
6780     }
6781
6782     return TRUE;
6783 }
6784
6785 int
6786 InPalace (int row, int column)
6787 {   /* [HGM] for Xiangqi */
6788     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6789          column < (BOARD_WIDTH + 4)/2 &&
6790          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6791     return FALSE;
6792 }
6793
6794 int
6795 PieceForSquare (int x, int y)
6796 {
6797   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6798      return -1;
6799   else
6800      return boards[currentMove][y][x];
6801 }
6802
6803 int
6804 OKToStartUserMove (int x, int y)
6805 {
6806     ChessSquare from_piece;
6807     int white_piece;
6808
6809     if (matchMode) return FALSE;
6810     if (gameMode == EditPosition) return TRUE;
6811
6812     if (x >= 0 && y >= 0)
6813       from_piece = boards[currentMove][y][x];
6814     else
6815       from_piece = EmptySquare;
6816
6817     if (from_piece == EmptySquare) return FALSE;
6818
6819     white_piece = (int)from_piece >= (int)WhitePawn &&
6820       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6821
6822     switch (gameMode) {
6823       case AnalyzeFile:
6824       case TwoMachinesPlay:
6825       case EndOfGame:
6826         return FALSE;
6827
6828       case IcsObserving:
6829       case IcsIdle:
6830         return FALSE;
6831
6832       case MachinePlaysWhite:
6833       case IcsPlayingBlack:
6834         if (appData.zippyPlay) return FALSE;
6835         if (white_piece) {
6836             DisplayMoveError(_("You are playing Black"));
6837             return FALSE;
6838         }
6839         break;
6840
6841       case MachinePlaysBlack:
6842       case IcsPlayingWhite:
6843         if (appData.zippyPlay) return FALSE;
6844         if (!white_piece) {
6845             DisplayMoveError(_("You are playing White"));
6846             return FALSE;
6847         }
6848         break;
6849
6850       case PlayFromGameFile:
6851             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6852       case EditGame:
6853       case AnalyzeMode:
6854         if (!white_piece && WhiteOnMove(currentMove)) {
6855             DisplayMoveError(_("It is White's turn"));
6856             return FALSE;
6857         }
6858         if (white_piece && !WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is Black's turn"));
6860             return FALSE;
6861         }
6862         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6863             /* Editing correspondence game history */
6864             /* Could disallow this or prompt for confirmation */
6865             cmailOldMove = -1;
6866         }
6867         break;
6868
6869       case BeginningOfGame:
6870         if (appData.icsActive) return FALSE;
6871         if (!appData.noChessProgram) {
6872             if (!white_piece) {
6873                 DisplayMoveError(_("You are playing White"));
6874                 return FALSE;
6875             }
6876         }
6877         break;
6878
6879       case Training:
6880         if (!white_piece && WhiteOnMove(currentMove)) {
6881             DisplayMoveError(_("It is White's turn"));
6882             return FALSE;
6883         }
6884         if (white_piece && !WhiteOnMove(currentMove)) {
6885             DisplayMoveError(_("It is Black's turn"));
6886             return FALSE;
6887         }
6888         break;
6889
6890       default:
6891       case IcsExamining:
6892         break;
6893     }
6894     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6895         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6896         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6897         && gameMode != AnalyzeFile && gameMode != Training) {
6898         DisplayMoveError(_("Displayed position is not current"));
6899         return FALSE;
6900     }
6901     return TRUE;
6902 }
6903
6904 Boolean
6905 OnlyMove (int *x, int *y, Boolean captures)
6906 {
6907     DisambiguateClosure cl;
6908     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6909     switch(gameMode) {
6910       case MachinePlaysBlack:
6911       case IcsPlayingWhite:
6912       case BeginningOfGame:
6913         if(!WhiteOnMove(currentMove)) return FALSE;
6914         break;
6915       case MachinePlaysWhite:
6916       case IcsPlayingBlack:
6917         if(WhiteOnMove(currentMove)) return FALSE;
6918         break;
6919       case EditGame:
6920         break;
6921       default:
6922         return FALSE;
6923     }
6924     cl.pieceIn = EmptySquare;
6925     cl.rfIn = *y;
6926     cl.ffIn = *x;
6927     cl.rtIn = -1;
6928     cl.ftIn = -1;
6929     cl.promoCharIn = NULLCHAR;
6930     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6931     if( cl.kind == NormalMove ||
6932         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6933         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6934         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6935       fromX = cl.ff;
6936       fromY = cl.rf;
6937       *x = cl.ft;
6938       *y = cl.rt;
6939       return TRUE;
6940     }
6941     if(cl.kind != ImpossibleMove) return FALSE;
6942     cl.pieceIn = EmptySquare;
6943     cl.rfIn = -1;
6944     cl.ffIn = -1;
6945     cl.rtIn = *y;
6946     cl.ftIn = *x;
6947     cl.promoCharIn = NULLCHAR;
6948     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6949     if( cl.kind == NormalMove ||
6950         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6951         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6952         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6953       fromX = cl.ff;
6954       fromY = cl.rf;
6955       *x = cl.ft;
6956       *y = cl.rt;
6957       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6958       return TRUE;
6959     }
6960     return FALSE;
6961 }
6962
6963 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6964 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6965 int lastLoadGameUseList = FALSE;
6966 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6967 ChessMove lastLoadGameStart = EndOfFile;
6968 int doubleClick;
6969 Boolean addToBookFlag;
6970
6971 void
6972 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6973 {
6974     ChessMove moveType;
6975     ChessSquare pup;
6976     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6977
6978     /* Check if the user is playing in turn.  This is complicated because we
6979        let the user "pick up" a piece before it is his turn.  So the piece he
6980        tried to pick up may have been captured by the time he puts it down!
6981        Therefore we use the color the user is supposed to be playing in this
6982        test, not the color of the piece that is currently on the starting
6983        square---except in EditGame mode, where the user is playing both
6984        sides; fortunately there the capture race can't happen.  (It can
6985        now happen in IcsExamining mode, but that's just too bad.  The user
6986        will get a somewhat confusing message in that case.)
6987        */
6988
6989     switch (gameMode) {
6990       case AnalyzeFile:
6991       case TwoMachinesPlay:
6992       case EndOfGame:
6993       case IcsObserving:
6994       case IcsIdle:
6995         /* We switched into a game mode where moves are not accepted,
6996            perhaps while the mouse button was down. */
6997         return;
6998
6999       case MachinePlaysWhite:
7000         /* User is moving for Black */
7001         if (WhiteOnMove(currentMove)) {
7002             DisplayMoveError(_("It is White's turn"));
7003             return;
7004         }
7005         break;
7006
7007       case MachinePlaysBlack:
7008         /* User is moving for White */
7009         if (!WhiteOnMove(currentMove)) {
7010             DisplayMoveError(_("It is Black's turn"));
7011             return;
7012         }
7013         break;
7014
7015       case PlayFromGameFile:
7016             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7017       case EditGame:
7018       case IcsExamining:
7019       case BeginningOfGame:
7020       case AnalyzeMode:
7021       case Training:
7022         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7023         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7024             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7025             /* User is moving for Black */
7026             if (WhiteOnMove(currentMove)) {
7027                 DisplayMoveError(_("It is White's turn"));
7028                 return;
7029             }
7030         } else {
7031             /* User is moving for White */
7032             if (!WhiteOnMove(currentMove)) {
7033                 DisplayMoveError(_("It is Black's turn"));
7034                 return;
7035             }
7036         }
7037         break;
7038
7039       case IcsPlayingBlack:
7040         /* User is moving for Black */
7041         if (WhiteOnMove(currentMove)) {
7042             if (!appData.premove) {
7043                 DisplayMoveError(_("It is White's turn"));
7044             } else if (toX >= 0 && toY >= 0) {
7045                 premoveToX = toX;
7046                 premoveToY = toY;
7047                 premoveFromX = fromX;
7048                 premoveFromY = fromY;
7049                 premovePromoChar = promoChar;
7050                 gotPremove = 1;
7051                 if (appData.debugMode)
7052                     fprintf(debugFP, "Got premove: fromX %d,"
7053                             "fromY %d, toX %d, toY %d\n",
7054                             fromX, fromY, toX, toY);
7055             }
7056             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7057             return;
7058         }
7059         break;
7060
7061       case IcsPlayingWhite:
7062         /* User is moving for White */
7063         if (!WhiteOnMove(currentMove)) {
7064             if (!appData.premove) {
7065                 DisplayMoveError(_("It is Black's turn"));
7066             } else if (toX >= 0 && toY >= 0) {
7067                 premoveToX = toX;
7068                 premoveToY = toY;
7069                 premoveFromX = fromX;
7070                 premoveFromY = fromY;
7071                 premovePromoChar = promoChar;
7072                 gotPremove = 1;
7073                 if (appData.debugMode)
7074                     fprintf(debugFP, "Got premove: fromX %d,"
7075                             "fromY %d, toX %d, toY %d\n",
7076                             fromX, fromY, toX, toY);
7077             }
7078             DrawPosition(TRUE, boards[currentMove]);
7079             return;
7080         }
7081         break;
7082
7083       default:
7084         break;
7085
7086       case EditPosition:
7087         /* EditPosition, empty square, or different color piece;
7088            click-click move is possible */
7089         if (toX == -2 || toY == -2) {
7090             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7091             DrawPosition(FALSE, boards[currentMove]);
7092             return;
7093         } else if (toX >= 0 && toY >= 0) {
7094             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7095                 ChessSquare p = boards[0][rf][ff];
7096                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7097                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7098             }
7099             boards[0][toY][toX] = boards[0][fromY][fromX];
7100             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7101                 if(boards[0][fromY][0] != EmptySquare) {
7102                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7103                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7104                 }
7105             } else
7106             if(fromX == BOARD_RGHT+1) {
7107                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7108                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7109                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7110                 }
7111             } else
7112             boards[0][fromY][fromX] = gatingPiece;
7113             ClearHighlights();
7114             DrawPosition(FALSE, boards[currentMove]);
7115             return;
7116         }
7117         return;
7118     }
7119
7120     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7121     pup = boards[currentMove][toY][toX];
7122
7123     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7124     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7125          if( pup != EmptySquare ) return;
7126          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7127            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7128                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7129            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7130            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7131            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7132            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7133          fromY = DROP_RANK;
7134     }
7135
7136     /* [HGM] always test for legality, to get promotion info */
7137     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7138                                          fromY, fromX, toY, toX, promoChar);
7139
7140     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7141
7142     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7143
7144     /* [HGM] but possibly ignore an IllegalMove result */
7145     if (appData.testLegality) {
7146         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7147             DisplayMoveError(_("Illegal move"));
7148             return;
7149         }
7150     }
7151
7152     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7153         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7154              ClearPremoveHighlights(); // was included
7155         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7156         return;
7157     }
7158
7159     if(addToBookFlag) { // adding moves to book
7160         char buf[MSG_SIZ], move[MSG_SIZ];
7161         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7162         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7163                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7164         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7165         AddBookMove(buf);
7166         addToBookFlag = FALSE;
7167         ClearHighlights();
7168         return;
7169     }
7170
7171     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7172 }
7173
7174 /* Common tail of UserMoveEvent and DropMenuEvent */
7175 int
7176 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7177 {
7178     char *bookHit = 0;
7179
7180     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7181         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7182         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7183         if(WhiteOnMove(currentMove)) {
7184             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7185         } else {
7186             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7187         }
7188     }
7189
7190     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7191        move type in caller when we know the move is a legal promotion */
7192     if(moveType == NormalMove && promoChar)
7193         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7194
7195     /* [HGM] <popupFix> The following if has been moved here from
7196        UserMoveEvent(). Because it seemed to belong here (why not allow
7197        piece drops in training games?), and because it can only be
7198        performed after it is known to what we promote. */
7199     if (gameMode == Training) {
7200       /* compare the move played on the board to the next move in the
7201        * game. If they match, display the move and the opponent's response.
7202        * If they don't match, display an error message.
7203        */
7204       int saveAnimate;
7205       Board testBoard;
7206       CopyBoard(testBoard, boards[currentMove]);
7207       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7208
7209       if (CompareBoards(testBoard, boards[currentMove+1])) {
7210         ForwardInner(currentMove+1);
7211
7212         /* Autoplay the opponent's response.
7213          * if appData.animate was TRUE when Training mode was entered,
7214          * the response will be animated.
7215          */
7216         saveAnimate = appData.animate;
7217         appData.animate = animateTraining;
7218         ForwardInner(currentMove+1);
7219         appData.animate = saveAnimate;
7220
7221         /* check for the end of the game */
7222         if (currentMove >= forwardMostMove) {
7223           gameMode = PlayFromGameFile;
7224           ModeHighlight();
7225           SetTrainingModeOff();
7226           DisplayInformation(_("End of game"));
7227         }
7228       } else {
7229         DisplayError(_("Incorrect move"), 0);
7230       }
7231       return 1;
7232     }
7233
7234   /* Ok, now we know that the move is good, so we can kill
7235      the previous line in Analysis Mode */
7236   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7237                                 && currentMove < forwardMostMove) {
7238     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7239     else forwardMostMove = currentMove;
7240   }
7241
7242   ClearMap();
7243
7244   /* If we need the chess program but it's dead, restart it */
7245   ResurrectChessProgram();
7246
7247   /* A user move restarts a paused game*/
7248   if (pausing)
7249     PauseEvent();
7250
7251   thinkOutput[0] = NULLCHAR;
7252
7253   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7254
7255   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7256     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7257     return 1;
7258   }
7259
7260   if (gameMode == BeginningOfGame) {
7261     if (appData.noChessProgram) {
7262       gameMode = EditGame;
7263       SetGameInfo();
7264     } else {
7265       char buf[MSG_SIZ];
7266       gameMode = MachinePlaysBlack;
7267       StartClocks();
7268       SetGameInfo();
7269       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7270       DisplayTitle(buf);
7271       if (first.sendName) {
7272         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7273         SendToProgram(buf, &first);
7274       }
7275       StartClocks();
7276     }
7277     ModeHighlight();
7278   }
7279
7280   /* Relay move to ICS or chess engine */
7281   if (appData.icsActive) {
7282     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7283         gameMode == IcsExamining) {
7284       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7285         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7286         SendToICS("draw ");
7287         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7288       }
7289       // also send plain move, in case ICS does not understand atomic claims
7290       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7291       ics_user_moved = 1;
7292     }
7293   } else {
7294     if (first.sendTime && (gameMode == BeginningOfGame ||
7295                            gameMode == MachinePlaysWhite ||
7296                            gameMode == MachinePlaysBlack)) {
7297       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7298     }
7299     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7300          // [HGM] book: if program might be playing, let it use book
7301         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7302         first.maybeThinking = TRUE;
7303     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7304         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7305         SendBoard(&first, currentMove+1);
7306         if(second.analyzing) {
7307             if(!second.useSetboard) SendToProgram("undo\n", &second);
7308             SendBoard(&second, currentMove+1);
7309         }
7310     } else {
7311         SendMoveToProgram(forwardMostMove-1, &first);
7312         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7313     }
7314     if (currentMove == cmailOldMove + 1) {
7315       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7316     }
7317   }
7318
7319   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7320
7321   switch (gameMode) {
7322   case EditGame:
7323     if(appData.testLegality)
7324     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7325     case MT_NONE:
7326     case MT_CHECK:
7327       break;
7328     case MT_CHECKMATE:
7329     case MT_STAINMATE:
7330       if (WhiteOnMove(currentMove)) {
7331         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7332       } else {
7333         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7334       }
7335       break;
7336     case MT_STALEMATE:
7337       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7338       break;
7339     }
7340     break;
7341
7342   case MachinePlaysBlack:
7343   case MachinePlaysWhite:
7344     /* disable certain menu options while machine is thinking */
7345     SetMachineThinkingEnables();
7346     break;
7347
7348   default:
7349     break;
7350   }
7351
7352   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7353   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7354
7355   if(bookHit) { // [HGM] book: simulate book reply
7356         static char bookMove[MSG_SIZ]; // a bit generous?
7357
7358         programStats.nodes = programStats.depth = programStats.time =
7359         programStats.score = programStats.got_only_move = 0;
7360         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7361
7362         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7363         strcat(bookMove, bookHit);
7364         HandleMachineMove(bookMove, &first);
7365   }
7366   return 1;
7367 }
7368
7369 void
7370 MarkByFEN(char *fen)
7371 {
7372         int r, f;
7373         if(!appData.markers || !appData.highlightDragging) return;
7374         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7375         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7376         while(*fen) {
7377             int s = 0;
7378             marker[r][f] = 0;
7379             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7380             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7381             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7382             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7383             if(*fen == 'T') marker[r][f++] = 0; else
7384             if(*fen == 'Y') marker[r][f++] = 1; else
7385             if(*fen == 'G') marker[r][f++] = 3; else
7386             if(*fen == 'B') marker[r][f++] = 4; else
7387             if(*fen == 'C') marker[r][f++] = 5; else
7388             if(*fen == 'M') marker[r][f++] = 6; else
7389             if(*fen == 'W') marker[r][f++] = 7; else
7390             if(*fen == 'D') marker[r][f++] = 8; else
7391             if(*fen == 'R') marker[r][f++] = 2; else {
7392                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7393               f += s; fen -= s>0;
7394             }
7395             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7396             if(r < 0) break;
7397             fen++;
7398         }
7399         DrawPosition(TRUE, NULL);
7400 }
7401
7402 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7403
7404 void
7405 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7406 {
7407     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7408     Markers *m = (Markers *) closure;
7409     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7410         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7411                          || kind == WhiteCapturesEnPassant
7412                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7413     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7414 }
7415
7416 static int hoverSavedValid;
7417
7418 void
7419 MarkTargetSquares (int clear)
7420 {
7421   int x, y, sum=0;
7422   if(clear) { // no reason to ever suppress clearing
7423     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7424     hoverSavedValid = 0;
7425     if(!sum) return; // nothing was cleared,no redraw needed
7426   } else {
7427     int capt = 0;
7428     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7429        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7430     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7431     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7432       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7433       if(capt)
7434       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7435     }
7436   }
7437   DrawPosition(FALSE, NULL);
7438 }
7439
7440 int
7441 Explode (Board board, int fromX, int fromY, int toX, int toY)
7442 {
7443     if(gameInfo.variant == VariantAtomic &&
7444        (board[toY][toX] != EmptySquare ||                     // capture?
7445         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7446                          board[fromY][fromX] == BlackPawn   )
7447       )) {
7448         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7449         return TRUE;
7450     }
7451     return FALSE;
7452 }
7453
7454 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7455
7456 int
7457 CanPromote (ChessSquare piece, int y)
7458 {
7459         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7460         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7461         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7462         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7463            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7464           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7465            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7466         return (piece == BlackPawn && y <= zone ||
7467                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7468                 piece == BlackLance && y <= zone ||
7469                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7470 }
7471
7472 void
7473 HoverEvent (int xPix, int yPix, int x, int y)
7474 {
7475         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7476         int r, f;
7477         if(!first.highlight) return;
7478         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7479         if(x == oldX && y == oldY) return; // only do something if we enter new square
7480         oldFromX = fromX; oldFromY = fromY;
7481         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7482           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7483             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7484           hoverSavedValid = 1;
7485         } else if(oldX != x || oldY != y) {
7486           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7487           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7488           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7489             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7490           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7491             char buf[MSG_SIZ];
7492             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7493             SendToProgram(buf, &first);
7494           }
7495           oldX = x; oldY = y;
7496 //        SetHighlights(fromX, fromY, x, y);
7497         }
7498 }
7499
7500 void ReportClick(char *action, int x, int y)
7501 {
7502         char buf[MSG_SIZ]; // Inform engine of what user does
7503         int r, f;
7504         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7505           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7506             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7507         if(!first.highlight || gameMode == EditPosition) return;
7508         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7509         SendToProgram(buf, &first);
7510 }
7511
7512 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7513
7514 void
7515 LeftClick (ClickType clickType, int xPix, int yPix)
7516 {
7517     int x, y;
7518     Boolean saveAnimate;
7519     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7520     char promoChoice = NULLCHAR;
7521     ChessSquare piece;
7522     static TimeMark lastClickTime, prevClickTime;
7523
7524     x = EventToSquare(xPix, BOARD_WIDTH);
7525     y = EventToSquare(yPix, BOARD_HEIGHT);
7526     if (!flipView && y >= 0) {
7527         y = BOARD_HEIGHT - 1 - y;
7528     }
7529     if (flipView && x >= 0) {
7530         x = BOARD_WIDTH - 1 - x;
7531     }
7532
7533     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7534         static int dummy;
7535         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7536         right = TRUE;
7537         return;
7538     }
7539
7540     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7541
7542     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7543
7544     if (clickType == Press) ErrorPopDown();
7545     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7546
7547     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7548         defaultPromoChoice = promoSweep;
7549         promoSweep = EmptySquare;   // terminate sweep
7550         promoDefaultAltered = TRUE;
7551         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7552     }
7553
7554     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7555         if(clickType == Release) return; // ignore upclick of click-click destination
7556         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7557         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7558         if(gameInfo.holdingsWidth &&
7559                 (WhiteOnMove(currentMove)
7560                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7561                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7562             // click in right holdings, for determining promotion piece
7563             ChessSquare p = boards[currentMove][y][x];
7564             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7565             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7566             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7567                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7568                 fromX = fromY = -1;
7569                 return;
7570             }
7571         }
7572         DrawPosition(FALSE, boards[currentMove]);
7573         return;
7574     }
7575
7576     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7577     if(clickType == Press
7578             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7579               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7580               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7581         return;
7582
7583     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7584         // could be static click on premove from-square: abort premove
7585         gotPremove = 0;
7586         ClearPremoveHighlights();
7587     }
7588
7589     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7590         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7591
7592     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7593         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7594                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7595         defaultPromoChoice = DefaultPromoChoice(side);
7596     }
7597
7598     autoQueen = appData.alwaysPromoteToQueen;
7599
7600     if (fromX == -1) {
7601       int originalY = y;
7602       gatingPiece = EmptySquare;
7603       if (clickType != Press) {
7604         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7605             DragPieceEnd(xPix, yPix); dragging = 0;
7606             DrawPosition(FALSE, NULL);
7607         }
7608         return;
7609       }
7610       doubleClick = FALSE;
7611       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7612         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7613       }
7614       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7615       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7616          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7617          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7618             /* First square */
7619             if (OKToStartUserMove(fromX, fromY)) {
7620                 second = 0;
7621                 ReportClick("lift", x, y);
7622                 MarkTargetSquares(0);
7623                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7624                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7625                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7626                     promoSweep = defaultPromoChoice;
7627                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7628                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7629                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7630                 }
7631                 if (appData.highlightDragging) {
7632                     SetHighlights(fromX, fromY, -1, -1);
7633                 } else {
7634                     ClearHighlights();
7635                 }
7636             } else fromX = fromY = -1;
7637             return;
7638         }
7639     }
7640
7641     /* fromX != -1 */
7642     if (clickType == Press && gameMode != EditPosition) {
7643         ChessSquare fromP;
7644         ChessSquare toP;
7645         int frc;
7646
7647         // ignore off-board to clicks
7648         if(y < 0 || x < 0) return;
7649
7650         /* Check if clicking again on the same color piece */
7651         fromP = boards[currentMove][fromY][fromX];
7652         toP = boards[currentMove][y][x];
7653         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7654         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7655             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7656            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7657              WhitePawn <= toP && toP <= WhiteKing &&
7658              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7659              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7660             (BlackPawn <= fromP && fromP <= BlackKing &&
7661              BlackPawn <= toP && toP <= BlackKing &&
7662              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7663              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7664             /* Clicked again on same color piece -- changed his mind */
7665             second = (x == fromX && y == fromY);
7666             killX = killY = -1;
7667             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7668                 second = FALSE; // first double-click rather than scond click
7669                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7670             }
7671             promoDefaultAltered = FALSE;
7672             MarkTargetSquares(1);
7673            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7674             if (appData.highlightDragging) {
7675                 SetHighlights(x, y, -1, -1);
7676             } else {
7677                 ClearHighlights();
7678             }
7679             if (OKToStartUserMove(x, y)) {
7680                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7681                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7682                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7683                  gatingPiece = boards[currentMove][fromY][fromX];
7684                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7685                 fromX = x;
7686                 fromY = y; dragging = 1;
7687                 if(!second) ReportClick("lift", x, y);
7688                 MarkTargetSquares(0);
7689                 DragPieceBegin(xPix, yPix, FALSE);
7690                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7691                     promoSweep = defaultPromoChoice;
7692                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7693                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7694                 }
7695             }
7696            }
7697            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7698            second = FALSE;
7699         }
7700         // ignore clicks on holdings
7701         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7702     }
7703
7704     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7705         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7706         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7707         return;
7708     }
7709
7710     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7711         DragPieceEnd(xPix, yPix); dragging = 0;
7712         if(clearFlag) {
7713             // a deferred attempt to click-click move an empty square on top of a piece
7714             boards[currentMove][y][x] = EmptySquare;
7715             ClearHighlights();
7716             DrawPosition(FALSE, boards[currentMove]);
7717             fromX = fromY = -1; clearFlag = 0;
7718             return;
7719         }
7720         if (appData.animateDragging) {
7721             /* Undo animation damage if any */
7722             DrawPosition(FALSE, NULL);
7723         }
7724         if (second) {
7725             /* Second up/down in same square; just abort move */
7726             second = 0;
7727             fromX = fromY = -1;
7728             gatingPiece = EmptySquare;
7729             MarkTargetSquares(1);
7730             ClearHighlights();
7731             gotPremove = 0;
7732             ClearPremoveHighlights();
7733         } else {
7734             /* First upclick in same square; start click-click mode */
7735             SetHighlights(x, y, -1, -1);
7736         }
7737         return;
7738     }
7739
7740     clearFlag = 0;
7741
7742     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7743        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7744         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7745         DisplayMessage(_("only marked squares are legal"),"");
7746         DrawPosition(TRUE, NULL);
7747         return; // ignore to-click
7748     }
7749
7750     /* we now have a different from- and (possibly off-board) to-square */
7751     /* Completed move */
7752     if(!sweepSelecting) {
7753         toX = x;
7754         toY = y;
7755     }
7756
7757     piece = boards[currentMove][fromY][fromX];
7758
7759     saveAnimate = appData.animate;
7760     if (clickType == Press) {
7761         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7762         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7763             // must be Edit Position mode with empty-square selected
7764             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7765             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7766             return;
7767         }
7768         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7769             return;
7770         }
7771         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7772             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7773         } else
7774         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7775         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7776           if(appData.sweepSelect) {
7777             promoSweep = defaultPromoChoice;
7778             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7779             selectFlag = 0; lastX = xPix; lastY = yPix;
7780             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7781             Sweep(0); // Pawn that is going to promote: preview promotion piece
7782             sweepSelecting = 1;
7783             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7784             MarkTargetSquares(1);
7785           }
7786           return; // promo popup appears on up-click
7787         }
7788         /* Finish clickclick move */
7789         if (appData.animate || appData.highlightLastMove) {
7790             SetHighlights(fromX, fromY, toX, toY);
7791         } else {
7792             ClearHighlights();
7793         }
7794     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7795         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7796         *promoRestrict = 0;
7797         if (appData.animate || appData.highlightLastMove) {
7798             SetHighlights(fromX, fromY, toX, toY);
7799         } else {
7800             ClearHighlights();
7801         }
7802     } else {
7803 #if 0
7804 // [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
7805         /* Finish drag move */
7806         if (appData.highlightLastMove) {
7807             SetHighlights(fromX, fromY, toX, toY);
7808         } else {
7809             ClearHighlights();
7810         }
7811 #endif
7812         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7813           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7814         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7815         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7816           dragging *= 2;            // flag button-less dragging if we are dragging
7817           MarkTargetSquares(1);
7818           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7819           else {
7820             kill2X = killX; kill2Y = killY;
7821             killX = x; killY = y;     //remeber this square as intermediate
7822             ReportClick("put", x, y); // and inform engine
7823             ReportClick("lift", x, y);
7824             MarkTargetSquares(0);
7825             return;
7826           }
7827         }
7828         DragPieceEnd(xPix, yPix); dragging = 0;
7829         /* Don't animate move and drag both */
7830         appData.animate = FALSE;
7831     }
7832
7833     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7834     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7835         ChessSquare piece = boards[currentMove][fromY][fromX];
7836         if(gameMode == EditPosition && piece != EmptySquare &&
7837            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7838             int n;
7839
7840             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7841                 n = PieceToNumber(piece - (int)BlackPawn);
7842                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7843                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7844                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7845             } else
7846             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7847                 n = PieceToNumber(piece);
7848                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7849                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7850                 boards[currentMove][n][BOARD_WIDTH-2]++;
7851             }
7852             boards[currentMove][fromY][fromX] = EmptySquare;
7853         }
7854         ClearHighlights();
7855         fromX = fromY = -1;
7856         MarkTargetSquares(1);
7857         DrawPosition(TRUE, boards[currentMove]);
7858         return;
7859     }
7860
7861     // off-board moves should not be highlighted
7862     if(x < 0 || y < 0) ClearHighlights();
7863     else ReportClick("put", x, y);
7864
7865     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7866
7867     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7868
7869     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7870         SetHighlights(fromX, fromY, toX, toY);
7871         MarkTargetSquares(1);
7872         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7873             // [HGM] super: promotion to captured piece selected from holdings
7874             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7875             promotionChoice = TRUE;
7876             // kludge follows to temporarily execute move on display, without promoting yet
7877             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7878             boards[currentMove][toY][toX] = p;
7879             DrawPosition(FALSE, boards[currentMove]);
7880             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7881             boards[currentMove][toY][toX] = q;
7882             DisplayMessage("Click in holdings to choose piece", "");
7883             return;
7884         }
7885         PromotionPopUp(promoChoice);
7886     } else {
7887         int oldMove = currentMove;
7888         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7889         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7890         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7891         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7892            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7893             DrawPosition(TRUE, boards[currentMove]);
7894         MarkTargetSquares(1);
7895         fromX = fromY = -1;
7896     }
7897     appData.animate = saveAnimate;
7898     if (appData.animate || appData.animateDragging) {
7899         /* Undo animation damage if needed */
7900         DrawPosition(FALSE, NULL);
7901     }
7902 }
7903
7904 int
7905 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7906 {   // front-end-free part taken out of PieceMenuPopup
7907     int whichMenu; int xSqr, ySqr;
7908
7909     if(seekGraphUp) { // [HGM] seekgraph
7910         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7911         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7912         return -2;
7913     }
7914
7915     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7916          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7917         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7918         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7919         if(action == Press)   {
7920             originalFlip = flipView;
7921             flipView = !flipView; // temporarily flip board to see game from partners perspective
7922             DrawPosition(TRUE, partnerBoard);
7923             DisplayMessage(partnerStatus, "");
7924             partnerUp = TRUE;
7925         } else if(action == Release) {
7926             flipView = originalFlip;
7927             DrawPosition(TRUE, boards[currentMove]);
7928             partnerUp = FALSE;
7929         }
7930         return -2;
7931     }
7932
7933     xSqr = EventToSquare(x, BOARD_WIDTH);
7934     ySqr = EventToSquare(y, BOARD_HEIGHT);
7935     if (action == Release) {
7936         if(pieceSweep != EmptySquare) {
7937             EditPositionMenuEvent(pieceSweep, toX, toY);
7938             pieceSweep = EmptySquare;
7939         } else UnLoadPV(); // [HGM] pv
7940     }
7941     if (action != Press) return -2; // return code to be ignored
7942     switch (gameMode) {
7943       case IcsExamining:
7944         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7945       case EditPosition:
7946         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7947         if (xSqr < 0 || ySqr < 0) return -1;
7948         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7949         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7950         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7951         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7952         NextPiece(0);
7953         return 2; // grab
7954       case IcsObserving:
7955         if(!appData.icsEngineAnalyze) return -1;
7956       case IcsPlayingWhite:
7957       case IcsPlayingBlack:
7958         if(!appData.zippyPlay) goto noZip;
7959       case AnalyzeMode:
7960       case AnalyzeFile:
7961       case MachinePlaysWhite:
7962       case MachinePlaysBlack:
7963       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7964         if (!appData.dropMenu) {
7965           LoadPV(x, y);
7966           return 2; // flag front-end to grab mouse events
7967         }
7968         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7969            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7970       case EditGame:
7971       noZip:
7972         if (xSqr < 0 || ySqr < 0) return -1;
7973         if (!appData.dropMenu || appData.testLegality &&
7974             gameInfo.variant != VariantBughouse &&
7975             gameInfo.variant != VariantCrazyhouse) return -1;
7976         whichMenu = 1; // drop menu
7977         break;
7978       default:
7979         return -1;
7980     }
7981
7982     if (((*fromX = xSqr) < 0) ||
7983         ((*fromY = ySqr) < 0)) {
7984         *fromX = *fromY = -1;
7985         return -1;
7986     }
7987     if (flipView)
7988       *fromX = BOARD_WIDTH - 1 - *fromX;
7989     else
7990       *fromY = BOARD_HEIGHT - 1 - *fromY;
7991
7992     return whichMenu;
7993 }
7994
7995 void
7996 Wheel (int dir, int x, int y)
7997 {
7998     if(gameMode == EditPosition) {
7999         int xSqr = EventToSquare(x, BOARD_WIDTH);
8000         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8001         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8002         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8003         do {
8004             boards[currentMove][ySqr][xSqr] += dir;
8005             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8006             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8007         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8008         DrawPosition(FALSE, boards[currentMove]);
8009     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8010 }
8011
8012 void
8013 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8014 {
8015 //    char * hint = lastHint;
8016     FrontEndProgramStats stats;
8017
8018     stats.which = cps == &first ? 0 : 1;
8019     stats.depth = cpstats->depth;
8020     stats.nodes = cpstats->nodes;
8021     stats.score = cpstats->score;
8022     stats.time = cpstats->time;
8023     stats.pv = cpstats->movelist;
8024     stats.hint = lastHint;
8025     stats.an_move_index = 0;
8026     stats.an_move_count = 0;
8027
8028     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8029         stats.hint = cpstats->move_name;
8030         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8031         stats.an_move_count = cpstats->nr_moves;
8032     }
8033
8034     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
8035
8036     SetProgramStats( &stats );
8037 }
8038
8039 void
8040 ClearEngineOutputPane (int which)
8041 {
8042     static FrontEndProgramStats dummyStats;
8043     dummyStats.which = which;
8044     dummyStats.pv = "#";
8045     SetProgramStats( &dummyStats );
8046 }
8047
8048 #define MAXPLAYERS 500
8049
8050 char *
8051 TourneyStandings (int display)
8052 {
8053     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8054     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8055     char result, *p, *names[MAXPLAYERS];
8056
8057     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8058         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8059     names[0] = p = strdup(appData.participants);
8060     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8061
8062     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8063
8064     while(result = appData.results[nr]) {
8065         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8066         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8067         wScore = bScore = 0;
8068         switch(result) {
8069           case '+': wScore = 2; break;
8070           case '-': bScore = 2; break;
8071           case '=': wScore = bScore = 1; break;
8072           case ' ':
8073           case '*': return strdup("busy"); // tourney not finished
8074         }
8075         score[w] += wScore;
8076         score[b] += bScore;
8077         games[w]++;
8078         games[b]++;
8079         nr++;
8080     }
8081     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8082     for(w=0; w<nPlayers; w++) {
8083         bScore = -1;
8084         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8085         ranking[w] = b; points[w] = bScore; score[b] = -2;
8086     }
8087     p = malloc(nPlayers*34+1);
8088     for(w=0; w<nPlayers && w<display; w++)
8089         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8090     free(names[0]);
8091     return p;
8092 }
8093
8094 void
8095 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8096 {       // count all piece types
8097         int p, f, r;
8098         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8099         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8100         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8101                 p = board[r][f];
8102                 pCnt[p]++;
8103                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8104                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8105                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8106                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8107                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8108                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8109         }
8110 }
8111
8112 int
8113 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8114 {
8115         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8116         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8117
8118         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8119         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8120         if(myPawns == 2 && nMine == 3) // KPP
8121             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8122         if(myPawns == 1 && nMine == 2) // KP
8123             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8124         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8125             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8126         if(myPawns) return FALSE;
8127         if(pCnt[WhiteRook+side])
8128             return pCnt[BlackRook-side] ||
8129                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8130                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8131                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8132         if(pCnt[WhiteCannon+side]) {
8133             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8134             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8135         }
8136         if(pCnt[WhiteKnight+side])
8137             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8138         return FALSE;
8139 }
8140
8141 int
8142 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8143 {
8144         VariantClass v = gameInfo.variant;
8145
8146         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8147         if(v == VariantShatranj) return TRUE; // always winnable through baring
8148         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8149         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8150
8151         if(v == VariantXiangqi) {
8152                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8153
8154                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8155                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8156                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8157                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8158                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8159                 if(stale) // we have at least one last-rank P plus perhaps C
8160                     return majors // KPKX
8161                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8162                 else // KCA*E*
8163                     return pCnt[WhiteFerz+side] // KCAK
8164                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8165                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8166                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8167
8168         } else if(v == VariantKnightmate) {
8169                 if(nMine == 1) return FALSE;
8170                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8171         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8172                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8173
8174                 if(nMine == 1) return FALSE; // bare King
8175                 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
8176                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8177                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8178                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8179                 if(pCnt[WhiteKnight+side])
8180                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8181                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8182                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8183                 if(nBishops)
8184                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8185                 if(pCnt[WhiteAlfil+side])
8186                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8187                 if(pCnt[WhiteWazir+side])
8188                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8189         }
8190
8191         return TRUE;
8192 }
8193
8194 int
8195 CompareWithRights (Board b1, Board b2)
8196 {
8197     int rights = 0;
8198     if(!CompareBoards(b1, b2)) return FALSE;
8199     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8200     /* compare castling rights */
8201     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8202            rights++; /* King lost rights, while rook still had them */
8203     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8204         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8205            rights++; /* but at least one rook lost them */
8206     }
8207     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8208            rights++;
8209     if( b1[CASTLING][5] != NoRights ) {
8210         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8211            rights++;
8212     }
8213     return rights == 0;
8214 }
8215
8216 int
8217 Adjudicate (ChessProgramState *cps)
8218 {       // [HGM] some adjudications useful with buggy engines
8219         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8220         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8221         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8222         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8223         int k, drop, count = 0; static int bare = 1;
8224         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8225         Boolean canAdjudicate = !appData.icsActive;
8226
8227         // most tests only when we understand the game, i.e. legality-checking on
8228             if( appData.testLegality )
8229             {   /* [HGM] Some more adjudications for obstinate engines */
8230                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8231                 static int moveCount = 6;
8232                 ChessMove result;
8233                 char *reason = NULL;
8234
8235                 /* Count what is on board. */
8236                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8237
8238                 /* Some material-based adjudications that have to be made before stalemate test */
8239                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8240                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8241                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8242                      if(canAdjudicate && appData.checkMates) {
8243                          if(engineOpponent)
8244                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8245                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8246                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8247                          return 1;
8248                      }
8249                 }
8250
8251                 /* Bare King in Shatranj (loses) or Losers (wins) */
8252                 if( nrW == 1 || nrB == 1) {
8253                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8254                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8255                      if(canAdjudicate && appData.checkMates) {
8256                          if(engineOpponent)
8257                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8258                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8259                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8260                          return 1;
8261                      }
8262                   } else
8263                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8264                   {    /* bare King */
8265                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8266                         if(canAdjudicate && appData.checkMates) {
8267                             /* but only adjudicate if adjudication enabled */
8268                             if(engineOpponent)
8269                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8270                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8271                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8272                             return 1;
8273                         }
8274                   }
8275                 } else bare = 1;
8276
8277
8278             // don't wait for engine to announce game end if we can judge ourselves
8279             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8280               case MT_CHECK:
8281                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8282                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8283                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8284                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8285                             checkCnt++;
8286                         if(checkCnt >= 2) {
8287                             reason = "Xboard adjudication: 3rd check";
8288                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8289                             break;
8290                         }
8291                     }
8292                 }
8293               case MT_NONE:
8294               default:
8295                 break;
8296               case MT_STEALMATE:
8297               case MT_STALEMATE:
8298               case MT_STAINMATE:
8299                 reason = "Xboard adjudication: Stalemate";
8300                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8301                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8302                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8303                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8304                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8305                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8306                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8307                                                                         EP_CHECKMATE : EP_WINS);
8308                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8309                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8310                 }
8311                 break;
8312               case MT_CHECKMATE:
8313                 reason = "Xboard adjudication: Checkmate";
8314                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8315                 if(gameInfo.variant == VariantShogi) {
8316                     if(forwardMostMove > backwardMostMove
8317                        && moveList[forwardMostMove-1][1] == '@'
8318                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8319                         reason = "XBoard adjudication: pawn-drop mate";
8320                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8321                     }
8322                 }
8323                 break;
8324             }
8325
8326                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8327                     case EP_STALEMATE:
8328                         result = GameIsDrawn; break;
8329                     case EP_CHECKMATE:
8330                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8331                     case EP_WINS:
8332                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8333                     default:
8334                         result = EndOfFile;
8335                 }
8336                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8337                     if(engineOpponent)
8338                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8339                     GameEnds( result, reason, GE_XBOARD );
8340                     return 1;
8341                 }
8342
8343                 /* Next absolutely insufficient mating material. */
8344                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8345                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8346                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8347
8348                      /* always flag draws, for judging claims */
8349                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8350
8351                      if(canAdjudicate && appData.materialDraws) {
8352                          /* but only adjudicate them if adjudication enabled */
8353                          if(engineOpponent) {
8354                            SendToProgram("force\n", engineOpponent); // suppress reply
8355                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8356                          }
8357                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8358                          return 1;
8359                      }
8360                 }
8361
8362                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8363                 if(gameInfo.variant == VariantXiangqi ?
8364                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8365                  : nrW + nrB == 4 &&
8366                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8367                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8368                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8369                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8370                    ) ) {
8371                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8372                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8373                           if(engineOpponent) {
8374                             SendToProgram("force\n", engineOpponent); // suppress reply
8375                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8376                           }
8377                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8378                           return 1;
8379                      }
8380                 } else moveCount = 6;
8381             }
8382
8383         // Repetition draws and 50-move rule can be applied independently of legality testing
8384
8385                 /* Check for rep-draws */
8386                 count = 0;
8387                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8388                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8389                 for(k = forwardMostMove-2;
8390                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8391                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8392                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8393                     k-=2)
8394                 {   int rights=0;
8395                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8396                         /* compare castling rights */
8397                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8398                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8399                                 rights++; /* King lost rights, while rook still had them */
8400                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8401                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8402                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8403                                    rights++; /* but at least one rook lost them */
8404                         }
8405                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8406                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8407                                 rights++;
8408                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8409                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8410                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8411                                    rights++;
8412                         }
8413                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8414                             && appData.drawRepeats > 1) {
8415                              /* adjudicate after user-specified nr of repeats */
8416                              int result = GameIsDrawn;
8417                              char *details = "XBoard adjudication: repetition draw";
8418                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8419                                 // [HGM] xiangqi: check for forbidden perpetuals
8420                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8421                                 for(m=forwardMostMove; m>k; m-=2) {
8422                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8423                                         ourPerpetual = 0; // the current mover did not always check
8424                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8425                                         hisPerpetual = 0; // the opponent did not always check
8426                                 }
8427                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8428                                                                         ourPerpetual, hisPerpetual);
8429                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8430                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8431                                     details = "Xboard adjudication: perpetual checking";
8432                                 } else
8433                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8434                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8435                                 } else
8436                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8437                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8438                                         result = BlackWins;
8439                                         details = "Xboard adjudication: repetition";
8440                                     }
8441                                 } else // it must be XQ
8442                                 // Now check for perpetual chases
8443                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8444                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8445                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8446                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8447                                         static char resdet[MSG_SIZ];
8448                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8449                                         details = resdet;
8450                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8451                                     } else
8452                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8453                                         break; // Abort repetition-checking loop.
8454                                 }
8455                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8456                              }
8457                              if(engineOpponent) {
8458                                SendToProgram("force\n", engineOpponent); // suppress reply
8459                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8460                              }
8461                              GameEnds( result, details, GE_XBOARD );
8462                              return 1;
8463                         }
8464                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8465                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8466                     }
8467                 }
8468
8469                 /* Now we test for 50-move draws. Determine ply count */
8470                 count = forwardMostMove;
8471                 /* look for last irreversble move */
8472                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8473                     count--;
8474                 /* if we hit starting position, add initial plies */
8475                 if( count == backwardMostMove )
8476                     count -= initialRulePlies;
8477                 count = forwardMostMove - count;
8478                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8479                         // adjust reversible move counter for checks in Xiangqi
8480                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8481                         if(i < backwardMostMove) i = backwardMostMove;
8482                         while(i <= forwardMostMove) {
8483                                 lastCheck = inCheck; // check evasion does not count
8484                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8485                                 if(inCheck || lastCheck) count--; // check does not count
8486                                 i++;
8487                         }
8488                 }
8489                 if( count >= 100)
8490                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8491                          /* this is used to judge if draw claims are legal */
8492                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8493                          if(engineOpponent) {
8494                            SendToProgram("force\n", engineOpponent); // suppress reply
8495                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8496                          }
8497                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8498                          return 1;
8499                 }
8500
8501                 /* if draw offer is pending, treat it as a draw claim
8502                  * when draw condition present, to allow engines a way to
8503                  * claim draws before making their move to avoid a race
8504                  * condition occurring after their move
8505                  */
8506                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8507                          char *p = NULL;
8508                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8509                              p = "Draw claim: 50-move rule";
8510                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8511                              p = "Draw claim: 3-fold repetition";
8512                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8513                              p = "Draw claim: insufficient mating material";
8514                          if( p != NULL && canAdjudicate) {
8515                              if(engineOpponent) {
8516                                SendToProgram("force\n", engineOpponent); // suppress reply
8517                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8518                              }
8519                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8520                              return 1;
8521                          }
8522                 }
8523
8524                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8525                     if(engineOpponent) {
8526                       SendToProgram("force\n", engineOpponent); // suppress reply
8527                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8528                     }
8529                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8530                     return 1;
8531                 }
8532         return 0;
8533 }
8534
8535 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8536 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8537 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8538
8539 static int
8540 BitbaseProbe ()
8541 {
8542     int pieces[10], squares[10], cnt=0, r, f, res;
8543     static int loaded;
8544     static PPROBE_EGBB probeBB;
8545     if(!appData.testLegality) return 10;
8546     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8547     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8548     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8549     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8550         ChessSquare piece = boards[forwardMostMove][r][f];
8551         int black = (piece >= BlackPawn);
8552         int type = piece - black*BlackPawn;
8553         if(piece == EmptySquare) continue;
8554         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8555         if(type == WhiteKing) type = WhiteQueen + 1;
8556         type = egbbCode[type];
8557         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8558         pieces[cnt] = type + black*6;
8559         if(++cnt > 5) return 11;
8560     }
8561     pieces[cnt] = squares[cnt] = 0;
8562     // probe EGBB
8563     if(loaded == 2) return 13; // loading failed before
8564     if(loaded == 0) {
8565         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8566         HMODULE lib;
8567         PLOAD_EGBB loadBB;
8568         loaded = 2; // prepare for failure
8569         if(!path) return 13; // no egbb installed
8570         strncpy(buf, path + 8, MSG_SIZ);
8571         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8572         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8573         lib = LoadLibrary(buf);
8574         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8575         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8576         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8577         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8578         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8579         loaded = 1; // success!
8580     }
8581     res = probeBB(forwardMostMove & 1, pieces, squares);
8582     return res > 0 ? 1 : res < 0 ? -1 : 0;
8583 }
8584
8585 char *
8586 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8587 {   // [HGM] book: this routine intercepts moves to simulate book replies
8588     char *bookHit = NULL;
8589
8590     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8591         char buf[MSG_SIZ];
8592         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8593         SendToProgram(buf, cps);
8594     }
8595     //first determine if the incoming move brings opponent into his book
8596     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8597         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8598     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8599     if(bookHit != NULL && !cps->bookSuspend) {
8600         // make sure opponent is not going to reply after receiving move to book position
8601         SendToProgram("force\n", cps);
8602         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8603     }
8604     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8605     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8606     // now arrange restart after book miss
8607     if(bookHit) {
8608         // after a book hit we never send 'go', and the code after the call to this routine
8609         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8610         char buf[MSG_SIZ], *move = bookHit;
8611         if(cps->useSAN) {
8612             int fromX, fromY, toX, toY;
8613             char promoChar;
8614             ChessMove moveType;
8615             move = buf + 30;
8616             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8617                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8618                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8619                                     PosFlags(forwardMostMove),
8620                                     fromY, fromX, toY, toX, promoChar, move);
8621             } else {
8622                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8623                 bookHit = NULL;
8624             }
8625         }
8626         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8627         SendToProgram(buf, cps);
8628         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8629     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8630         SendToProgram("go\n", cps);
8631         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8632     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8633         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8634             SendToProgram("go\n", cps);
8635         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8636     }
8637     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8638 }
8639
8640 int
8641 LoadError (char *errmess, ChessProgramState *cps)
8642 {   // unloads engine and switches back to -ncp mode if it was first
8643     if(cps->initDone) return FALSE;
8644     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8645     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8646     cps->pr = NoProc;
8647     if(cps == &first) {
8648         appData.noChessProgram = TRUE;
8649         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8650         gameMode = BeginningOfGame; ModeHighlight();
8651         SetNCPMode();
8652     }
8653     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8654     DisplayMessage("", ""); // erase waiting message
8655     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8656     return TRUE;
8657 }
8658
8659 char *savedMessage;
8660 ChessProgramState *savedState;
8661 void
8662 DeferredBookMove (void)
8663 {
8664         if(savedState->lastPing != savedState->lastPong)
8665                     ScheduleDelayedEvent(DeferredBookMove, 10);
8666         else
8667         HandleMachineMove(savedMessage, savedState);
8668 }
8669
8670 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8671 static ChessProgramState *stalledEngine;
8672 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8673
8674 void
8675 HandleMachineMove (char *message, ChessProgramState *cps)
8676 {
8677     static char firstLeg[20];
8678     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8679     char realname[MSG_SIZ];
8680     int fromX, fromY, toX, toY;
8681     ChessMove moveType;
8682     char promoChar, roar;
8683     char *p, *pv=buf1;
8684     int oldError;
8685     char *bookHit;
8686
8687     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8688         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8689         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8690             DisplayError(_("Invalid pairing from pairing engine"), 0);
8691             return;
8692         }
8693         pairingReceived = 1;
8694         NextMatchGame();
8695         return; // Skim the pairing messages here.
8696     }
8697
8698     oldError = cps->userError; cps->userError = 0;
8699
8700 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8701     /*
8702      * Kludge to ignore BEL characters
8703      */
8704     while (*message == '\007') message++;
8705
8706     /*
8707      * [HGM] engine debug message: ignore lines starting with '#' character
8708      */
8709     if(cps->debug && *message == '#') return;
8710
8711     /*
8712      * Look for book output
8713      */
8714     if (cps == &first && bookRequested) {
8715         if (message[0] == '\t' || message[0] == ' ') {
8716             /* Part of the book output is here; append it */
8717             strcat(bookOutput, message);
8718             strcat(bookOutput, "  \n");
8719             return;
8720         } else if (bookOutput[0] != NULLCHAR) {
8721             /* All of book output has arrived; display it */
8722             char *p = bookOutput;
8723             while (*p != NULLCHAR) {
8724                 if (*p == '\t') *p = ' ';
8725                 p++;
8726             }
8727             DisplayInformation(bookOutput);
8728             bookRequested = FALSE;
8729             /* Fall through to parse the current output */
8730         }
8731     }
8732
8733     /*
8734      * Look for machine move.
8735      */
8736     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8737         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8738     {
8739         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8740             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8741             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8742             stalledEngine = cps;
8743             if(appData.ponderNextMove) { // bring opponent out of ponder
8744                 if(gameMode == TwoMachinesPlay) {
8745                     if(cps->other->pause)
8746                         PauseEngine(cps->other);
8747                     else
8748                         SendToProgram("easy\n", cps->other);
8749                 }
8750             }
8751             StopClocks();
8752             return;
8753         }
8754
8755       if(cps->usePing) {
8756
8757         /* This method is only useful on engines that support ping */
8758         if(abortEngineThink) {
8759             if (appData.debugMode) {
8760                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8761             }
8762             SendToProgram("undo\n", cps);
8763             return;
8764         }
8765
8766         if (cps->lastPing != cps->lastPong) {
8767             /* Extra move from before last new; ignore */
8768             if (appData.debugMode) {
8769                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8770             }
8771           return;
8772         }
8773
8774       } else {
8775
8776         int machineWhite = FALSE;
8777
8778         switch (gameMode) {
8779           case BeginningOfGame:
8780             /* Extra move from before last reset; ignore */
8781             if (appData.debugMode) {
8782                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8783             }
8784             return;
8785
8786           case EndOfGame:
8787           case IcsIdle:
8788           default:
8789             /* Extra move after we tried to stop.  The mode test is
8790                not a reliable way of detecting this problem, but it's
8791                the best we can do on engines that don't support ping.
8792             */
8793             if (appData.debugMode) {
8794                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8795                         cps->which, gameMode);
8796             }
8797             SendToProgram("undo\n", cps);
8798             return;
8799
8800           case MachinePlaysWhite:
8801           case IcsPlayingWhite:
8802             machineWhite = TRUE;
8803             break;
8804
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             machineWhite = FALSE;
8808             break;
8809
8810           case TwoMachinesPlay:
8811             machineWhite = (cps->twoMachinesColor[0] == 'w');
8812             break;
8813         }
8814         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8815             if (appData.debugMode) {
8816                 fprintf(debugFP,
8817                         "Ignoring move out of turn by %s, gameMode %d"
8818                         ", forwardMost %d\n",
8819                         cps->which, gameMode, forwardMostMove);
8820             }
8821             return;
8822         }
8823       }
8824
8825         if(cps->alphaRank) AlphaRank(machineMove, 4);
8826
8827         // [HGM] lion: (some very limited) support for Alien protocol
8828         killX = killY = kill2X = kill2Y = -1;
8829         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8830             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8831             return;
8832         }
8833         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8834             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8835             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8836         }
8837         if(firstLeg[0]) { // there was a previous leg;
8838             // only support case where same piece makes two step
8839             char buf[20], *p = machineMove+1, *q = buf+1, f;
8840             safeStrCpy(buf, machineMove, 20);
8841             while(isdigit(*q)) q++; // find start of to-square
8842             safeStrCpy(machineMove, firstLeg, 20);
8843             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8844             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8845             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8846             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8847             firstLeg[0] = NULLCHAR;
8848         }
8849
8850         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8851                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8852             /* Machine move could not be parsed; ignore it. */
8853           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8854                     machineMove, _(cps->which));
8855             DisplayMoveError(buf1);
8856             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8857                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8858             if (gameMode == TwoMachinesPlay) {
8859               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8860                        buf1, GE_XBOARD);
8861             }
8862             return;
8863         }
8864
8865         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8866         /* So we have to redo legality test with true e.p. status here,  */
8867         /* to make sure an illegal e.p. capture does not slip through,   */
8868         /* to cause a forfeit on a justified illegal-move complaint      */
8869         /* of the opponent.                                              */
8870         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8871            ChessMove moveType;
8872            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8873                              fromY, fromX, toY, toX, promoChar);
8874             if(moveType == IllegalMove) {
8875               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8876                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8877                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8878                            buf1, GE_XBOARD);
8879                 return;
8880            } else if(!appData.fischerCastling)
8881            /* [HGM] Kludge to handle engines that send FRC-style castling
8882               when they shouldn't (like TSCP-Gothic) */
8883            switch(moveType) {
8884              case WhiteASideCastleFR:
8885              case BlackASideCastleFR:
8886                toX+=2;
8887                currentMoveString[2]++;
8888                break;
8889              case WhiteHSideCastleFR:
8890              case BlackHSideCastleFR:
8891                toX--;
8892                currentMoveString[2]--;
8893                break;
8894              default: ; // nothing to do, but suppresses warning of pedantic compilers
8895            }
8896         }
8897         hintRequested = FALSE;
8898         lastHint[0] = NULLCHAR;
8899         bookRequested = FALSE;
8900         /* Program may be pondering now */
8901         cps->maybeThinking = TRUE;
8902         if (cps->sendTime == 2) cps->sendTime = 1;
8903         if (cps->offeredDraw) cps->offeredDraw--;
8904
8905         /* [AS] Save move info*/
8906         pvInfoList[ forwardMostMove ].score = programStats.score;
8907         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8908         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8909
8910         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8911
8912         /* Test suites abort the 'game' after one move */
8913         if(*appData.finger) {
8914            static FILE *f;
8915            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8916            if(!f) f = fopen(appData.finger, "w");
8917            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8918            else { DisplayFatalError("Bad output file", errno, 0); return; }
8919            free(fen);
8920            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8921         }
8922         if(appData.epd) {
8923            if(solvingTime >= 0) {
8924               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8925               totalTime += solvingTime; first.matchWins++;
8926            } else {
8927               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8928               second.matchWins++;
8929            }
8930            OutputKibitz(2, buf1);
8931            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8932         }
8933
8934         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8935         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8936             int count = 0;
8937
8938             while( count < adjudicateLossPlies ) {
8939                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8940
8941                 if( count & 1 ) {
8942                     score = -score; /* Flip score for winning side */
8943                 }
8944
8945                 if( score > appData.adjudicateLossThreshold ) {
8946                     break;
8947                 }
8948
8949                 count++;
8950             }
8951
8952             if( count >= adjudicateLossPlies ) {
8953                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8954
8955                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8956                     "Xboard adjudication",
8957                     GE_XBOARD );
8958
8959                 return;
8960             }
8961         }
8962
8963         if(Adjudicate(cps)) {
8964             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8965             return; // [HGM] adjudicate: for all automatic game ends
8966         }
8967
8968 #if ZIPPY
8969         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8970             first.initDone) {
8971           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8972                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8973                 SendToICS("draw ");
8974                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8975           }
8976           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8977           ics_user_moved = 1;
8978           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8979                 char buf[3*MSG_SIZ];
8980
8981                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8982                         programStats.score / 100.,
8983                         programStats.depth,
8984                         programStats.time / 100.,
8985                         (unsigned int)programStats.nodes,
8986                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8987                         programStats.movelist);
8988                 SendToICS(buf);
8989           }
8990         }
8991 #endif
8992
8993         /* [AS] Clear stats for next move */
8994         ClearProgramStats();
8995         thinkOutput[0] = NULLCHAR;
8996         hiddenThinkOutputState = 0;
8997
8998         bookHit = NULL;
8999         if (gameMode == TwoMachinesPlay) {
9000             /* [HGM] relaying draw offers moved to after reception of move */
9001             /* and interpreting offer as claim if it brings draw condition */
9002             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9003                 SendToProgram("draw\n", cps->other);
9004             }
9005             if (cps->other->sendTime) {
9006                 SendTimeRemaining(cps->other,
9007                                   cps->other->twoMachinesColor[0] == 'w');
9008             }
9009             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9010             if (firstMove && !bookHit) {
9011                 firstMove = FALSE;
9012                 if (cps->other->useColors) {
9013                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9014                 }
9015                 SendToProgram("go\n", cps->other);
9016             }
9017             cps->other->maybeThinking = TRUE;
9018         }
9019
9020         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9021
9022         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9023
9024         if (!pausing && appData.ringBellAfterMoves) {
9025             if(!roar) RingBell();
9026         }
9027
9028         /*
9029          * Reenable menu items that were disabled while
9030          * machine was thinking
9031          */
9032         if (gameMode != TwoMachinesPlay)
9033             SetUserThinkingEnables();
9034
9035         // [HGM] book: after book hit opponent has received move and is now in force mode
9036         // force the book reply into it, and then fake that it outputted this move by jumping
9037         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9038         if(bookHit) {
9039                 static char bookMove[MSG_SIZ]; // a bit generous?
9040
9041                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9042                 strcat(bookMove, bookHit);
9043                 message = bookMove;
9044                 cps = cps->other;
9045                 programStats.nodes = programStats.depth = programStats.time =
9046                 programStats.score = programStats.got_only_move = 0;
9047                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9048
9049                 if(cps->lastPing != cps->lastPong) {
9050                     savedMessage = message; // args for deferred call
9051                     savedState = cps;
9052                     ScheduleDelayedEvent(DeferredBookMove, 10);
9053                     return;
9054                 }
9055                 goto FakeBookMove;
9056         }
9057
9058         return;
9059     }
9060
9061     /* Set special modes for chess engines.  Later something general
9062      *  could be added here; for now there is just one kludge feature,
9063      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9064      *  when "xboard" is given as an interactive command.
9065      */
9066     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9067         cps->useSigint = FALSE;
9068         cps->useSigterm = FALSE;
9069     }
9070     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9071       ParseFeatures(message+8, cps);
9072       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9073     }
9074
9075     if (!strncmp(message, "setup ", 6) && 
9076         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9077           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9078                                         ) { // [HGM] allow first engine to define opening position
9079       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9080       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9081       *buf = NULLCHAR;
9082       if(sscanf(message, "setup (%s", buf) == 1) {
9083         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9084         ASSIGN(appData.pieceToCharTable, buf);
9085       }
9086       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9087       if(dummy >= 3) {
9088         while(message[s] && message[s++] != ' ');
9089         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9090            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9091             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9092             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9093           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9094           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9095           startedFromSetupPosition = FALSE;
9096         }
9097       }
9098       if(startedFromSetupPosition) return;
9099       ParseFEN(boards[0], &dummy, message+s, FALSE);
9100       DrawPosition(TRUE, boards[0]);
9101       CopyBoard(initialPosition, boards[0]);
9102       startedFromSetupPosition = TRUE;
9103       return;
9104     }
9105     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9106       ChessSquare piece = WhitePawn;
9107       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9108       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9109       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9110       piece += CharToPiece(ID & 255) - WhitePawn;
9111       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9112       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9113       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9114       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9115       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9116       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9117                                                && gameInfo.variant != VariantGreat
9118                                                && gameInfo.variant != VariantFairy    ) return;
9119       if(piece < EmptySquare) {
9120         pieceDefs = TRUE;
9121         ASSIGN(pieceDesc[piece], buf1);
9122         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9123       }
9124       return;
9125     }
9126     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9127       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9128       Sweep(0);
9129       return;
9130     }
9131     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9132      * want this, I was asked to put it in, and obliged.
9133      */
9134     if (!strncmp(message, "setboard ", 9)) {
9135         Board initial_position;
9136
9137         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9138
9139         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9140             DisplayError(_("Bad FEN received from engine"), 0);
9141             return ;
9142         } else {
9143            Reset(TRUE, FALSE);
9144            CopyBoard(boards[0], initial_position);
9145            initialRulePlies = FENrulePlies;
9146            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9147            else gameMode = MachinePlaysBlack;
9148            DrawPosition(FALSE, boards[currentMove]);
9149         }
9150         return;
9151     }
9152
9153     /*
9154      * Look for communication commands
9155      */
9156     if (!strncmp(message, "telluser ", 9)) {
9157         if(message[9] == '\\' && message[10] == '\\')
9158             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9159         PlayTellSound();
9160         DisplayNote(message + 9);
9161         return;
9162     }
9163     if (!strncmp(message, "tellusererror ", 14)) {
9164         cps->userError = 1;
9165         if(message[14] == '\\' && message[15] == '\\')
9166             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9167         PlayTellSound();
9168         DisplayError(message + 14, 0);
9169         return;
9170     }
9171     if (!strncmp(message, "tellopponent ", 13)) {
9172       if (appData.icsActive) {
9173         if (loggedOn) {
9174           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9175           SendToICS(buf1);
9176         }
9177       } else {
9178         DisplayNote(message + 13);
9179       }
9180       return;
9181     }
9182     if (!strncmp(message, "tellothers ", 11)) {
9183       if (appData.icsActive) {
9184         if (loggedOn) {
9185           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9186           SendToICS(buf1);
9187         }
9188       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9189       return;
9190     }
9191     if (!strncmp(message, "tellall ", 8)) {
9192       if (appData.icsActive) {
9193         if (loggedOn) {
9194           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9195           SendToICS(buf1);
9196         }
9197       } else {
9198         DisplayNote(message + 8);
9199       }
9200       return;
9201     }
9202     if (strncmp(message, "warning", 7) == 0) {
9203         /* Undocumented feature, use tellusererror in new code */
9204         DisplayError(message, 0);
9205         return;
9206     }
9207     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9208         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9209         strcat(realname, " query");
9210         AskQuestion(realname, buf2, buf1, cps->pr);
9211         return;
9212     }
9213     /* Commands from the engine directly to ICS.  We don't allow these to be
9214      *  sent until we are logged on. Crafty kibitzes have been known to
9215      *  interfere with the login process.
9216      */
9217     if (loggedOn) {
9218         if (!strncmp(message, "tellics ", 8)) {
9219             SendToICS(message + 8);
9220             SendToICS("\n");
9221             return;
9222         }
9223         if (!strncmp(message, "tellicsnoalias ", 15)) {
9224             SendToICS(ics_prefix);
9225             SendToICS(message + 15);
9226             SendToICS("\n");
9227             return;
9228         }
9229         /* The following are for backward compatibility only */
9230         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9231             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9232             SendToICS(ics_prefix);
9233             SendToICS(message);
9234             SendToICS("\n");
9235             return;
9236         }
9237     }
9238     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9239         if(initPing == cps->lastPong) {
9240             if(gameInfo.variant == VariantUnknown) {
9241                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9242                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9243                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9244             }
9245             initPing = -1;
9246         }
9247         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9248             abortEngineThink = FALSE;
9249             DisplayMessage("", "");
9250             ThawUI();
9251         }
9252         return;
9253     }
9254     if(!strncmp(message, "highlight ", 10)) {
9255         if(appData.testLegality && !*engineVariant && appData.markers) return;
9256         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9257         return;
9258     }
9259     if(!strncmp(message, "click ", 6)) {
9260         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9261         if(appData.testLegality || !appData.oneClick) return;
9262         sscanf(message+6, "%c%d%c", &f, &y, &c);
9263         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9264         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9265         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9266         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9267         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9268         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9269             LeftClick(Release, lastLeftX, lastLeftY);
9270         controlKey  = (c == ',');
9271         LeftClick(Press, x, y);
9272         LeftClick(Release, x, y);
9273         first.highlight = f;
9274         return;
9275     }
9276     /*
9277      * If the move is illegal, cancel it and redraw the board.
9278      * Also deal with other error cases.  Matching is rather loose
9279      * here to accommodate engines written before the spec.
9280      */
9281     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9282         strncmp(message, "Error", 5) == 0) {
9283         if (StrStr(message, "name") ||
9284             StrStr(message, "rating") || StrStr(message, "?") ||
9285             StrStr(message, "result") || StrStr(message, "board") ||
9286             StrStr(message, "bk") || StrStr(message, "computer") ||
9287             StrStr(message, "variant") || StrStr(message, "hint") ||
9288             StrStr(message, "random") || StrStr(message, "depth") ||
9289             StrStr(message, "accepted")) {
9290             return;
9291         }
9292         if (StrStr(message, "protover")) {
9293           /* Program is responding to input, so it's apparently done
9294              initializing, and this error message indicates it is
9295              protocol version 1.  So we don't need to wait any longer
9296              for it to initialize and send feature commands. */
9297           FeatureDone(cps, 1);
9298           cps->protocolVersion = 1;
9299           return;
9300         }
9301         cps->maybeThinking = FALSE;
9302
9303         if (StrStr(message, "draw")) {
9304             /* Program doesn't have "draw" command */
9305             cps->sendDrawOffers = 0;
9306             return;
9307         }
9308         if (cps->sendTime != 1 &&
9309             (StrStr(message, "time") || StrStr(message, "otim"))) {
9310           /* Program apparently doesn't have "time" or "otim" command */
9311           cps->sendTime = 0;
9312           return;
9313         }
9314         if (StrStr(message, "analyze")) {
9315             cps->analysisSupport = FALSE;
9316             cps->analyzing = FALSE;
9317 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9318             EditGameEvent(); // [HGM] try to preserve loaded game
9319             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9320             DisplayError(buf2, 0);
9321             return;
9322         }
9323         if (StrStr(message, "(no matching move)st")) {
9324           /* Special kludge for GNU Chess 4 only */
9325           cps->stKludge = TRUE;
9326           SendTimeControl(cps, movesPerSession, timeControl,
9327                           timeIncrement, appData.searchDepth,
9328                           searchTime);
9329           return;
9330         }
9331         if (StrStr(message, "(no matching move)sd")) {
9332           /* Special kludge for GNU Chess 4 only */
9333           cps->sdKludge = TRUE;
9334           SendTimeControl(cps, movesPerSession, timeControl,
9335                           timeIncrement, appData.searchDepth,
9336                           searchTime);
9337           return;
9338         }
9339         if (!StrStr(message, "llegal")) {
9340             return;
9341         }
9342         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9343             gameMode == IcsIdle) return;
9344         if (forwardMostMove <= backwardMostMove) return;
9345         if (pausing) PauseEvent();
9346       if(appData.forceIllegal) {
9347             // [HGM] illegal: machine refused move; force position after move into it
9348           SendToProgram("force\n", cps);
9349           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9350                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9351                 // when black is to move, while there might be nothing on a2 or black
9352                 // might already have the move. So send the board as if white has the move.
9353                 // But first we must change the stm of the engine, as it refused the last move
9354                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9355                 if(WhiteOnMove(forwardMostMove)) {
9356                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9357                     SendBoard(cps, forwardMostMove); // kludgeless board
9358                 } else {
9359                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9360                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9361                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9362                 }
9363           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9364             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9365                  gameMode == TwoMachinesPlay)
9366               SendToProgram("go\n", cps);
9367             return;
9368       } else
9369         if (gameMode == PlayFromGameFile) {
9370             /* Stop reading this game file */
9371             gameMode = EditGame;
9372             ModeHighlight();
9373         }
9374         /* [HGM] illegal-move claim should forfeit game when Xboard */
9375         /* only passes fully legal moves                            */
9376         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9377             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9378                                 "False illegal-move claim", GE_XBOARD );
9379             return; // do not take back move we tested as valid
9380         }
9381         currentMove = forwardMostMove-1;
9382         DisplayMove(currentMove-1); /* before DisplayMoveError */
9383         SwitchClocks(forwardMostMove-1); // [HGM] race
9384         DisplayBothClocks();
9385         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9386                 parseList[currentMove], _(cps->which));
9387         DisplayMoveError(buf1);
9388         DrawPosition(FALSE, boards[currentMove]);
9389
9390         SetUserThinkingEnables();
9391         return;
9392     }
9393     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9394         /* Program has a broken "time" command that
9395            outputs a string not ending in newline.
9396            Don't use it. */
9397         cps->sendTime = 0;
9398     }
9399     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9400         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9401             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9402     }
9403
9404     /*
9405      * If chess program startup fails, exit with an error message.
9406      * Attempts to recover here are futile. [HGM] Well, we try anyway
9407      */
9408     if ((StrStr(message, "unknown host") != NULL)
9409         || (StrStr(message, "No remote directory") != NULL)
9410         || (StrStr(message, "not found") != NULL)
9411         || (StrStr(message, "No such file") != NULL)
9412         || (StrStr(message, "can't alloc") != NULL)
9413         || (StrStr(message, "Permission denied") != NULL)) {
9414
9415         cps->maybeThinking = FALSE;
9416         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9417                 _(cps->which), cps->program, cps->host, message);
9418         RemoveInputSource(cps->isr);
9419         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9420             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9421             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9422         }
9423         return;
9424     }
9425
9426     /*
9427      * Look for hint output
9428      */
9429     if (sscanf(message, "Hint: %s", buf1) == 1) {
9430         if (cps == &first && hintRequested) {
9431             hintRequested = FALSE;
9432             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9433                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9434                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9435                                     PosFlags(forwardMostMove),
9436                                     fromY, fromX, toY, toX, promoChar, buf1);
9437                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9438                 DisplayInformation(buf2);
9439             } else {
9440                 /* Hint move could not be parsed!? */
9441               snprintf(buf2, sizeof(buf2),
9442                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9443                         buf1, _(cps->which));
9444                 DisplayError(buf2, 0);
9445             }
9446         } else {
9447           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9448         }
9449         return;
9450     }
9451
9452     /*
9453      * Ignore other messages if game is not in progress
9454      */
9455     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9456         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9457
9458     /*
9459      * look for win, lose, draw, or draw offer
9460      */
9461     if (strncmp(message, "1-0", 3) == 0) {
9462         char *p, *q, *r = "";
9463         p = strchr(message, '{');
9464         if (p) {
9465             q = strchr(p, '}');
9466             if (q) {
9467                 *q = NULLCHAR;
9468                 r = p + 1;
9469             }
9470         }
9471         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9472         return;
9473     } else if (strncmp(message, "0-1", 3) == 0) {
9474         char *p, *q, *r = "";
9475         p = strchr(message, '{');
9476         if (p) {
9477             q = strchr(p, '}');
9478             if (q) {
9479                 *q = NULLCHAR;
9480                 r = p + 1;
9481             }
9482         }
9483         /* Kludge for Arasan 4.1 bug */
9484         if (strcmp(r, "Black resigns") == 0) {
9485             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9486             return;
9487         }
9488         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9489         return;
9490     } else if (strncmp(message, "1/2", 3) == 0) {
9491         char *p, *q, *r = "";
9492         p = strchr(message, '{');
9493         if (p) {
9494             q = strchr(p, '}');
9495             if (q) {
9496                 *q = NULLCHAR;
9497                 r = p + 1;
9498             }
9499         }
9500
9501         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9502         return;
9503
9504     } else if (strncmp(message, "White resign", 12) == 0) {
9505         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9506         return;
9507     } else if (strncmp(message, "Black resign", 12) == 0) {
9508         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9509         return;
9510     } else if (strncmp(message, "White matches", 13) == 0 ||
9511                strncmp(message, "Black matches", 13) == 0   ) {
9512         /* [HGM] ignore GNUShogi noises */
9513         return;
9514     } else if (strncmp(message, "White", 5) == 0 &&
9515                message[5] != '(' &&
9516                StrStr(message, "Black") == NULL) {
9517         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9518         return;
9519     } else if (strncmp(message, "Black", 5) == 0 &&
9520                message[5] != '(') {
9521         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9522         return;
9523     } else if (strcmp(message, "resign") == 0 ||
9524                strcmp(message, "computer resigns") == 0) {
9525         switch (gameMode) {
9526           case MachinePlaysBlack:
9527           case IcsPlayingBlack:
9528             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9529             break;
9530           case MachinePlaysWhite:
9531           case IcsPlayingWhite:
9532             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9533             break;
9534           case TwoMachinesPlay:
9535             if (cps->twoMachinesColor[0] == 'w')
9536               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9537             else
9538               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9539             break;
9540           default:
9541             /* can't happen */
9542             break;
9543         }
9544         return;
9545     } else if (strncmp(message, "opponent mates", 14) == 0) {
9546         switch (gameMode) {
9547           case MachinePlaysBlack:
9548           case IcsPlayingBlack:
9549             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9550             break;
9551           case MachinePlaysWhite:
9552           case IcsPlayingWhite:
9553             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9554             break;
9555           case TwoMachinesPlay:
9556             if (cps->twoMachinesColor[0] == 'w')
9557               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9558             else
9559               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9560             break;
9561           default:
9562             /* can't happen */
9563             break;
9564         }
9565         return;
9566     } else if (strncmp(message, "computer mates", 14) == 0) {
9567         switch (gameMode) {
9568           case MachinePlaysBlack:
9569           case IcsPlayingBlack:
9570             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9571             break;
9572           case MachinePlaysWhite:
9573           case IcsPlayingWhite:
9574             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9575             break;
9576           case TwoMachinesPlay:
9577             if (cps->twoMachinesColor[0] == 'w')
9578               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9579             else
9580               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9581             break;
9582           default:
9583             /* can't happen */
9584             break;
9585         }
9586         return;
9587     } else if (strncmp(message, "checkmate", 9) == 0) {
9588         if (WhiteOnMove(forwardMostMove)) {
9589             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9590         } else {
9591             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592         }
9593         return;
9594     } else if (strstr(message, "Draw") != NULL ||
9595                strstr(message, "game is a draw") != NULL) {
9596         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9597         return;
9598     } else if (strstr(message, "offer") != NULL &&
9599                strstr(message, "draw") != NULL) {
9600 #if ZIPPY
9601         if (appData.zippyPlay && first.initDone) {
9602             /* Relay offer to ICS */
9603             SendToICS(ics_prefix);
9604             SendToICS("draw\n");
9605         }
9606 #endif
9607         cps->offeredDraw = 2; /* valid until this engine moves twice */
9608         if (gameMode == TwoMachinesPlay) {
9609             if (cps->other->offeredDraw) {
9610                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9611             /* [HGM] in two-machine mode we delay relaying draw offer      */
9612             /* until after we also have move, to see if it is really claim */
9613             }
9614         } else if (gameMode == MachinePlaysWhite ||
9615                    gameMode == MachinePlaysBlack) {
9616           if (userOfferedDraw) {
9617             DisplayInformation(_("Machine accepts your draw offer"));
9618             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9619           } else {
9620             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9621           }
9622         }
9623     }
9624
9625
9626     /*
9627      * Look for thinking output
9628      */
9629     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9630           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9631                                 ) {
9632         int plylev, mvleft, mvtot, curscore, time;
9633         char mvname[MOVE_LEN];
9634         u64 nodes; // [DM]
9635         char plyext;
9636         int ignore = FALSE;
9637         int prefixHint = FALSE;
9638         mvname[0] = NULLCHAR;
9639
9640         switch (gameMode) {
9641           case MachinePlaysBlack:
9642           case IcsPlayingBlack:
9643             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9644             break;
9645           case MachinePlaysWhite:
9646           case IcsPlayingWhite:
9647             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9648             break;
9649           case AnalyzeMode:
9650           case AnalyzeFile:
9651             break;
9652           case IcsObserving: /* [DM] icsEngineAnalyze */
9653             if (!appData.icsEngineAnalyze) ignore = TRUE;
9654             break;
9655           case TwoMachinesPlay:
9656             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9657                 ignore = TRUE;
9658             }
9659             break;
9660           default:
9661             ignore = TRUE;
9662             break;
9663         }
9664
9665         if (!ignore) {
9666             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9667             buf1[0] = NULLCHAR;
9668             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9669                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9670                 char score_buf[MSG_SIZ];
9671
9672                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9673                     nodes += u64Const(0x100000000);
9674
9675                 if (plyext != ' ' && plyext != '\t') {
9676                     time *= 100;
9677                 }
9678
9679                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9680                 if( cps->scoreIsAbsolute &&
9681                     ( gameMode == MachinePlaysBlack ||
9682                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9683                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9684                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9685                      !WhiteOnMove(currentMove)
9686                     ) )
9687                 {
9688                     curscore = -curscore;
9689                 }
9690
9691                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9692
9693                 if(*bestMove) { // rememer time best EPD move was first found
9694                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9695                     ChessMove mt;
9696                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9697                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9698                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9699                 }
9700
9701                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9702                         char buf[MSG_SIZ];
9703                         FILE *f;
9704                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9705                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9706                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9707                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9708                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9709                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9710                                 fclose(f);
9711                         }
9712                         else
9713                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9714                           DisplayError(_("failed writing PV"), 0);
9715                 }
9716
9717                 tempStats.depth = plylev;
9718                 tempStats.nodes = nodes;
9719                 tempStats.time = time;
9720                 tempStats.score = curscore;
9721                 tempStats.got_only_move = 0;
9722
9723                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9724                         int ticklen;
9725
9726                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9727                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9728                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9729                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9730                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9731                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9732                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9733                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9734                 }
9735
9736                 /* Buffer overflow protection */
9737                 if (pv[0] != NULLCHAR) {
9738                     if (strlen(pv) >= sizeof(tempStats.movelist)
9739                         && appData.debugMode) {
9740                         fprintf(debugFP,
9741                                 "PV is too long; using the first %u bytes.\n",
9742                                 (unsigned) sizeof(tempStats.movelist) - 1);
9743                     }
9744
9745                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9746                 } else {
9747                     sprintf(tempStats.movelist, " no PV\n");
9748                 }
9749
9750                 if (tempStats.seen_stat) {
9751                     tempStats.ok_to_send = 1;
9752                 }
9753
9754                 if (strchr(tempStats.movelist, '(') != NULL) {
9755                     tempStats.line_is_book = 1;
9756                     tempStats.nr_moves = 0;
9757                     tempStats.moves_left = 0;
9758                 } else {
9759                     tempStats.line_is_book = 0;
9760                 }
9761
9762                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9763                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9764
9765                 SendProgramStatsToFrontend( cps, &tempStats );
9766
9767                 /*
9768                     [AS] Protect the thinkOutput buffer from overflow... this
9769                     is only useful if buf1 hasn't overflowed first!
9770                 */
9771                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9772                 if(curscore >= MATE_SCORE) 
9773                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9774                 else if(curscore <= -MATE_SCORE) 
9775                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9776                 else
9777                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9778                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9779                          plylev,
9780                          (gameMode == TwoMachinesPlay ?
9781                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9782                          score_buf,
9783                          prefixHint ? lastHint : "",
9784                          prefixHint ? " " : "" );
9785
9786                 if( buf1[0] != NULLCHAR ) {
9787                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9788
9789                     if( strlen(pv) > max_len ) {
9790                         if( appData.debugMode) {
9791                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9792                         }
9793                         pv[max_len+1] = '\0';
9794                     }
9795
9796                     strcat( thinkOutput, pv);
9797                 }
9798
9799                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9800                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9801                     DisplayMove(currentMove - 1);
9802                 }
9803                 return;
9804
9805             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9806                 /* crafty (9.25+) says "(only move) <move>"
9807                  * if there is only 1 legal move
9808                  */
9809                 sscanf(p, "(only move) %s", buf1);
9810                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9811                 sprintf(programStats.movelist, "%s (only move)", buf1);
9812                 programStats.depth = 1;
9813                 programStats.nr_moves = 1;
9814                 programStats.moves_left = 1;
9815                 programStats.nodes = 1;
9816                 programStats.time = 1;
9817                 programStats.got_only_move = 1;
9818
9819                 /* Not really, but we also use this member to
9820                    mean "line isn't going to change" (Crafty
9821                    isn't searching, so stats won't change) */
9822                 programStats.line_is_book = 1;
9823
9824                 SendProgramStatsToFrontend( cps, &programStats );
9825
9826                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9827                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9828                     DisplayMove(currentMove - 1);
9829                 }
9830                 return;
9831             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9832                               &time, &nodes, &plylev, &mvleft,
9833                               &mvtot, mvname) >= 5) {
9834                 /* The stat01: line is from Crafty (9.29+) in response
9835                    to the "." command */
9836                 programStats.seen_stat = 1;
9837                 cps->maybeThinking = TRUE;
9838
9839                 if (programStats.got_only_move || !appData.periodicUpdates)
9840                   return;
9841
9842                 programStats.depth = plylev;
9843                 programStats.time = time;
9844                 programStats.nodes = nodes;
9845                 programStats.moves_left = mvleft;
9846                 programStats.nr_moves = mvtot;
9847                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9848                 programStats.ok_to_send = 1;
9849                 programStats.movelist[0] = '\0';
9850
9851                 SendProgramStatsToFrontend( cps, &programStats );
9852
9853                 return;
9854
9855             } else if (strncmp(message,"++",2) == 0) {
9856                 /* Crafty 9.29+ outputs this */
9857                 programStats.got_fail = 2;
9858                 return;
9859
9860             } else if (strncmp(message,"--",2) == 0) {
9861                 /* Crafty 9.29+ outputs this */
9862                 programStats.got_fail = 1;
9863                 return;
9864
9865             } else if (thinkOutput[0] != NULLCHAR &&
9866                        strncmp(message, "    ", 4) == 0) {
9867                 unsigned message_len;
9868
9869                 p = message;
9870                 while (*p && *p == ' ') p++;
9871
9872                 message_len = strlen( p );
9873
9874                 /* [AS] Avoid buffer overflow */
9875                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9876                     strcat(thinkOutput, " ");
9877                     strcat(thinkOutput, p);
9878                 }
9879
9880                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9881                     strcat(programStats.movelist, " ");
9882                     strcat(programStats.movelist, p);
9883                 }
9884
9885                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9886                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9887                     DisplayMove(currentMove - 1);
9888                 }
9889                 return;
9890             }
9891         }
9892         else {
9893             buf1[0] = NULLCHAR;
9894
9895             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9896                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9897             {
9898                 ChessProgramStats cpstats;
9899
9900                 if (plyext != ' ' && plyext != '\t') {
9901                     time *= 100;
9902                 }
9903
9904                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9905                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9906                     curscore = -curscore;
9907                 }
9908
9909                 cpstats.depth = plylev;
9910                 cpstats.nodes = nodes;
9911                 cpstats.time = time;
9912                 cpstats.score = curscore;
9913                 cpstats.got_only_move = 0;
9914                 cpstats.movelist[0] = '\0';
9915
9916                 if (buf1[0] != NULLCHAR) {
9917                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9918                 }
9919
9920                 cpstats.ok_to_send = 0;
9921                 cpstats.line_is_book = 0;
9922                 cpstats.nr_moves = 0;
9923                 cpstats.moves_left = 0;
9924
9925                 SendProgramStatsToFrontend( cps, &cpstats );
9926             }
9927         }
9928     }
9929 }
9930
9931
9932 /* Parse a game score from the character string "game", and
9933    record it as the history of the current game.  The game
9934    score is NOT assumed to start from the standard position.
9935    The display is not updated in any way.
9936    */
9937 void
9938 ParseGameHistory (char *game)
9939 {
9940     ChessMove moveType;
9941     int fromX, fromY, toX, toY, boardIndex;
9942     char promoChar;
9943     char *p, *q;
9944     char buf[MSG_SIZ];
9945
9946     if (appData.debugMode)
9947       fprintf(debugFP, "Parsing game history: %s\n", game);
9948
9949     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9950     gameInfo.site = StrSave(appData.icsHost);
9951     gameInfo.date = PGNDate();
9952     gameInfo.round = StrSave("-");
9953
9954     /* Parse out names of players */
9955     while (*game == ' ') game++;
9956     p = buf;
9957     while (*game != ' ') *p++ = *game++;
9958     *p = NULLCHAR;
9959     gameInfo.white = StrSave(buf);
9960     while (*game == ' ') game++;
9961     p = buf;
9962     while (*game != ' ' && *game != '\n') *p++ = *game++;
9963     *p = NULLCHAR;
9964     gameInfo.black = StrSave(buf);
9965
9966     /* Parse moves */
9967     boardIndex = blackPlaysFirst ? 1 : 0;
9968     yynewstr(game);
9969     for (;;) {
9970         yyboardindex = boardIndex;
9971         moveType = (ChessMove) Myylex();
9972         switch (moveType) {
9973           case IllegalMove:             /* maybe suicide chess, etc. */
9974   if (appData.debugMode) {
9975     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9976     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9977     setbuf(debugFP, NULL);
9978   }
9979           case WhitePromotion:
9980           case BlackPromotion:
9981           case WhiteNonPromotion:
9982           case BlackNonPromotion:
9983           case NormalMove:
9984           case FirstLeg:
9985           case WhiteCapturesEnPassant:
9986           case BlackCapturesEnPassant:
9987           case WhiteKingSideCastle:
9988           case WhiteQueenSideCastle:
9989           case BlackKingSideCastle:
9990           case BlackQueenSideCastle:
9991           case WhiteKingSideCastleWild:
9992           case WhiteQueenSideCastleWild:
9993           case BlackKingSideCastleWild:
9994           case BlackQueenSideCastleWild:
9995           /* PUSH Fabien */
9996           case WhiteHSideCastleFR:
9997           case WhiteASideCastleFR:
9998           case BlackHSideCastleFR:
9999           case BlackASideCastleFR:
10000           /* POP Fabien */
10001             fromX = currentMoveString[0] - AAA;
10002             fromY = currentMoveString[1] - ONE;
10003             toX = currentMoveString[2] - AAA;
10004             toY = currentMoveString[3] - ONE;
10005             promoChar = currentMoveString[4];
10006             break;
10007           case WhiteDrop:
10008           case BlackDrop:
10009             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10010             fromX = moveType == WhiteDrop ?
10011               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10012             (int) CharToPiece(ToLower(currentMoveString[0]));
10013             fromY = DROP_RANK;
10014             toX = currentMoveString[2] - AAA;
10015             toY = currentMoveString[3] - ONE;
10016             promoChar = NULLCHAR;
10017             break;
10018           case AmbiguousMove:
10019             /* bug? */
10020             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10021   if (appData.debugMode) {
10022     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10023     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10024     setbuf(debugFP, NULL);
10025   }
10026             DisplayError(buf, 0);
10027             return;
10028           case ImpossibleMove:
10029             /* bug? */
10030             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10031   if (appData.debugMode) {
10032     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10033     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10034     setbuf(debugFP, NULL);
10035   }
10036             DisplayError(buf, 0);
10037             return;
10038           case EndOfFile:
10039             if (boardIndex < backwardMostMove) {
10040                 /* Oops, gap.  How did that happen? */
10041                 DisplayError(_("Gap in move list"), 0);
10042                 return;
10043             }
10044             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10045             if (boardIndex > forwardMostMove) {
10046                 forwardMostMove = boardIndex;
10047             }
10048             return;
10049           case ElapsedTime:
10050             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10051                 strcat(parseList[boardIndex-1], " ");
10052                 strcat(parseList[boardIndex-1], yy_text);
10053             }
10054             continue;
10055           case Comment:
10056           case PGNTag:
10057           case NAG:
10058           default:
10059             /* ignore */
10060             continue;
10061           case WhiteWins:
10062           case BlackWins:
10063           case GameIsDrawn:
10064           case GameUnfinished:
10065             if (gameMode == IcsExamining) {
10066                 if (boardIndex < backwardMostMove) {
10067                     /* Oops, gap.  How did that happen? */
10068                     return;
10069                 }
10070                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10071                 return;
10072             }
10073             gameInfo.result = moveType;
10074             p = strchr(yy_text, '{');
10075             if (p == NULL) p = strchr(yy_text, '(');
10076             if (p == NULL) {
10077                 p = yy_text;
10078                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10079             } else {
10080                 q = strchr(p, *p == '{' ? '}' : ')');
10081                 if (q != NULL) *q = NULLCHAR;
10082                 p++;
10083             }
10084             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10085             gameInfo.resultDetails = StrSave(p);
10086             continue;
10087         }
10088         if (boardIndex >= forwardMostMove &&
10089             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10090             backwardMostMove = blackPlaysFirst ? 1 : 0;
10091             return;
10092         }
10093         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10094                                  fromY, fromX, toY, toX, promoChar,
10095                                  parseList[boardIndex]);
10096         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10097         /* currentMoveString is set as a side-effect of yylex */
10098         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10099         strcat(moveList[boardIndex], "\n");
10100         boardIndex++;
10101         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10102         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10103           case MT_NONE:
10104           case MT_STALEMATE:
10105           default:
10106             break;
10107           case MT_CHECK:
10108             if(!IS_SHOGI(gameInfo.variant))
10109                 strcat(parseList[boardIndex - 1], "+");
10110             break;
10111           case MT_CHECKMATE:
10112           case MT_STAINMATE:
10113             strcat(parseList[boardIndex - 1], "#");
10114             break;
10115         }
10116     }
10117 }
10118
10119
10120 /* Apply a move to the given board  */
10121 void
10122 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10123 {
10124   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10125   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10126
10127     /* [HGM] compute & store e.p. status and castling rights for new position */
10128     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10129
10130       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10131       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10132       board[EP_STATUS] = EP_NONE;
10133       board[EP_FILE] = board[EP_RANK] = 100;
10134
10135   if (fromY == DROP_RANK) {
10136         /* must be first */
10137         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10138             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10139             return;
10140         }
10141         piece = board[toY][toX] = (ChessSquare) fromX;
10142   } else {
10143 //      ChessSquare victim;
10144       int i;
10145
10146       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10147 //           victim = board[killY][killX],
10148            killed = board[killY][killX],
10149            board[killY][killX] = EmptySquare,
10150            board[EP_STATUS] = EP_CAPTURE;
10151            if( kill2X >= 0 && kill2Y >= 0)
10152              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10153       }
10154
10155       if( board[toY][toX] != EmptySquare ) {
10156            board[EP_STATUS] = EP_CAPTURE;
10157            if( (fromX != toX || fromY != toY) && // not igui!
10158                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10159                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10160                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10161            }
10162       }
10163
10164       pawn = board[fromY][fromX];
10165       if( pawn == WhiteLance || pawn == BlackLance ) {
10166            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10167                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10168                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10169            }
10170       }
10171       if( pawn == WhitePawn ) {
10172            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10173                board[EP_STATUS] = EP_PAWN_MOVE;
10174            if( toY-fromY>=2) {
10175                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10176                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10177                         gameInfo.variant != VariantBerolina || toX < fromX)
10178                       board[EP_STATUS] = toX | berolina;
10179                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10180                         gameInfo.variant != VariantBerolina || toX > fromX)
10181                       board[EP_STATUS] = toX;
10182            }
10183       } else
10184       if( pawn == BlackPawn ) {
10185            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10186                board[EP_STATUS] = EP_PAWN_MOVE;
10187            if( toY-fromY<= -2) {
10188                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10189                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10190                         gameInfo.variant != VariantBerolina || toX < fromX)
10191                       board[EP_STATUS] = toX | berolina;
10192                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10193                         gameInfo.variant != VariantBerolina || toX > fromX)
10194                       board[EP_STATUS] = toX;
10195            }
10196        }
10197
10198        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10199        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10200        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10201        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10202
10203        for(i=0; i<nrCastlingRights; i++) {
10204            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10205               board[CASTLING][i] == toX   && castlingRank[i] == toY
10206              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10207        }
10208
10209        if(gameInfo.variant == VariantSChess) { // update virginity
10210            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10211            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10212            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10213            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10214        }
10215
10216      if (fromX == toX && fromY == toY) return;
10217
10218      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10219      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10220      if(gameInfo.variant == VariantKnightmate)
10221          king += (int) WhiteUnicorn - (int) WhiteKing;
10222
10223     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10224        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10225         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10226         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10227         board[EP_STATUS] = EP_NONE; // capture was fake!
10228     } else
10229     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10230         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10231         board[toY][toX] = piece;
10232         board[EP_STATUS] = EP_NONE; // capture was fake!
10233     } else
10234     /* Code added by Tord: */
10235     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10236     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10237         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10238       board[EP_STATUS] = EP_NONE; // capture was fake!
10239       board[fromY][fromX] = EmptySquare;
10240       board[toY][toX] = EmptySquare;
10241       if((toX > fromX) != (piece == WhiteRook)) {
10242         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10243       } else {
10244         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10245       }
10246     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10247                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10248       board[EP_STATUS] = EP_NONE;
10249       board[fromY][fromX] = EmptySquare;
10250       board[toY][toX] = EmptySquare;
10251       if((toX > fromX) != (piece == BlackRook)) {
10252         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10253       } else {
10254         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10255       }
10256     /* End of code added by Tord */
10257
10258     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10259         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10260         board[toY][toX] = piece;
10261     } else if (board[fromY][fromX] == king
10262         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10263         && toY == fromY && toX > fromX+1) {
10264         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10265         board[fromY][toX-1] = board[fromY][rookX];
10266         board[fromY][rookX] = EmptySquare;
10267         board[fromY][fromX] = EmptySquare;
10268         board[toY][toX] = king;
10269     } else if (board[fromY][fromX] == king
10270         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10271                && toY == fromY && toX < fromX-1) {
10272         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10273         board[fromY][toX+1] = board[fromY][rookX];
10274         board[fromY][rookX] = EmptySquare;
10275         board[fromY][fromX] = EmptySquare;
10276         board[toY][toX] = king;
10277     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10278                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10279                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10280                ) {
10281         /* white pawn promotion */
10282         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10283         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10284             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10285         board[fromY][fromX] = EmptySquare;
10286     } else if ((fromY >= BOARD_HEIGHT>>1)
10287                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10288                && (toX != fromX)
10289                && gameInfo.variant != VariantXiangqi
10290                && gameInfo.variant != VariantBerolina
10291                && (pawn == WhitePawn)
10292                && (board[toY][toX] == EmptySquare)) {
10293         board[fromY][fromX] = EmptySquare;
10294         board[toY][toX] = piece;
10295         if(toY == epRank - 128 + 1)
10296             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10297         else
10298             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10299     } else if ((fromY == BOARD_HEIGHT-4)
10300                && (toX == fromX)
10301                && gameInfo.variant == VariantBerolina
10302                && (board[fromY][fromX] == WhitePawn)
10303                && (board[toY][toX] == EmptySquare)) {
10304         board[fromY][fromX] = EmptySquare;
10305         board[toY][toX] = WhitePawn;
10306         if(oldEP & EP_BEROLIN_A) {
10307                 captured = board[fromY][fromX-1];
10308                 board[fromY][fromX-1] = EmptySquare;
10309         }else{  captured = board[fromY][fromX+1];
10310                 board[fromY][fromX+1] = EmptySquare;
10311         }
10312     } else if (board[fromY][fromX] == king
10313         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10314                && toY == fromY && toX > fromX+1) {
10315         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10316         board[fromY][toX-1] = board[fromY][rookX];
10317         board[fromY][rookX] = EmptySquare;
10318         board[fromY][fromX] = EmptySquare;
10319         board[toY][toX] = king;
10320     } else if (board[fromY][fromX] == king
10321         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10322                && toY == fromY && toX < fromX-1) {
10323         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10324         board[fromY][toX+1] = board[fromY][rookX];
10325         board[fromY][rookX] = EmptySquare;
10326         board[fromY][fromX] = EmptySquare;
10327         board[toY][toX] = king;
10328     } else if (fromY == 7 && fromX == 3
10329                && board[fromY][fromX] == BlackKing
10330                && toY == 7 && toX == 5) {
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = BlackKing;
10333         board[fromY][7] = EmptySquare;
10334         board[toY][4] = BlackRook;
10335     } else if (fromY == 7 && fromX == 3
10336                && board[fromY][fromX] == BlackKing
10337                && toY == 7 && toX == 1) {
10338         board[fromY][fromX] = EmptySquare;
10339         board[toY][toX] = BlackKing;
10340         board[fromY][0] = EmptySquare;
10341         board[toY][2] = BlackRook;
10342     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10343                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10344                && toY < promoRank && promoChar
10345                ) {
10346         /* black pawn promotion */
10347         board[toY][toX] = CharToPiece(ToLower(promoChar));
10348         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10349             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10350         board[fromY][fromX] = EmptySquare;
10351     } else if ((fromY < BOARD_HEIGHT>>1)
10352                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10353                && (toX != fromX)
10354                && gameInfo.variant != VariantXiangqi
10355                && gameInfo.variant != VariantBerolina
10356                && (pawn == BlackPawn)
10357                && (board[toY][toX] == EmptySquare)) {
10358         board[fromY][fromX] = EmptySquare;
10359         board[toY][toX] = piece;
10360         if(toY == epRank - 128 - 1)
10361             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10362         else
10363             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10364     } else if ((fromY == 3)
10365                && (toX == fromX)
10366                && gameInfo.variant == VariantBerolina
10367                && (board[fromY][fromX] == BlackPawn)
10368                && (board[toY][toX] == EmptySquare)) {
10369         board[fromY][fromX] = EmptySquare;
10370         board[toY][toX] = BlackPawn;
10371         if(oldEP & EP_BEROLIN_A) {
10372                 captured = board[fromY][fromX-1];
10373                 board[fromY][fromX-1] = EmptySquare;
10374         }else{  captured = board[fromY][fromX+1];
10375                 board[fromY][fromX+1] = EmptySquare;
10376         }
10377     } else {
10378         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10379         board[fromY][fromX] = EmptySquare;
10380         board[toY][toX] = piece;
10381     }
10382   }
10383
10384     if (gameInfo.holdingsWidth != 0) {
10385
10386       /* !!A lot more code needs to be written to support holdings  */
10387       /* [HGM] OK, so I have written it. Holdings are stored in the */
10388       /* penultimate board files, so they are automaticlly stored   */
10389       /* in the game history.                                       */
10390       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10391                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10392         /* Delete from holdings, by decreasing count */
10393         /* and erasing image if necessary            */
10394         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10395         if(p < (int) BlackPawn) { /* white drop */
10396              p -= (int)WhitePawn;
10397                  p = PieceToNumber((ChessSquare)p);
10398              if(p >= gameInfo.holdingsSize) p = 0;
10399              if(--board[p][BOARD_WIDTH-2] <= 0)
10400                   board[p][BOARD_WIDTH-1] = EmptySquare;
10401              if((int)board[p][BOARD_WIDTH-2] < 0)
10402                         board[p][BOARD_WIDTH-2] = 0;
10403         } else {                  /* black drop */
10404              p -= (int)BlackPawn;
10405                  p = PieceToNumber((ChessSquare)p);
10406              if(p >= gameInfo.holdingsSize) p = 0;
10407              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10408                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10409              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10410                         board[BOARD_HEIGHT-1-p][1] = 0;
10411         }
10412       }
10413       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10414           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10415         /* [HGM] holdings: Add to holdings, if holdings exist */
10416         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10417                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10418                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10419         }
10420         p = (int) captured;
10421         if (p >= (int) BlackPawn) {
10422           p -= (int)BlackPawn;
10423           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10424                   /* Restore shogi-promoted piece to its original  first */
10425                   captured = (ChessSquare) (DEMOTED(captured));
10426                   p = DEMOTED(p);
10427           }
10428           p = PieceToNumber((ChessSquare)p);
10429           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10430           board[p][BOARD_WIDTH-2]++;
10431           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10432         } else {
10433           p -= (int)WhitePawn;
10434           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10435                   captured = (ChessSquare) (DEMOTED(captured));
10436                   p = DEMOTED(p);
10437           }
10438           p = PieceToNumber((ChessSquare)p);
10439           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10440           board[BOARD_HEIGHT-1-p][1]++;
10441           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10442         }
10443       }
10444     } else if (gameInfo.variant == VariantAtomic) {
10445       if (captured != EmptySquare) {
10446         int y, x;
10447         for (y = toY-1; y <= toY+1; y++) {
10448           for (x = toX-1; x <= toX+1; x++) {
10449             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10450                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10451               board[y][x] = EmptySquare;
10452             }
10453           }
10454         }
10455         board[toY][toX] = EmptySquare;
10456       }
10457     }
10458
10459     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10460         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10461     } else
10462     if(promoChar == '+') {
10463         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10464         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10465         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10466           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10467     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10468         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10469         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10470            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10471         board[toY][toX] = newPiece;
10472     }
10473     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10474                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10475         // [HGM] superchess: take promotion piece out of holdings
10476         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10477         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10478             if(!--board[k][BOARD_WIDTH-2])
10479                 board[k][BOARD_WIDTH-1] = EmptySquare;
10480         } else {
10481             if(!--board[BOARD_HEIGHT-1-k][1])
10482                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10483         }
10484     }
10485 }
10486
10487 /* Updates forwardMostMove */
10488 void
10489 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10490 {
10491     int x = toX, y = toY;
10492     char *s = parseList[forwardMostMove];
10493     ChessSquare p = boards[forwardMostMove][toY][toX];
10494 //    forwardMostMove++; // [HGM] bare: moved downstream
10495
10496     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10497     (void) CoordsToAlgebraic(boards[forwardMostMove],
10498                              PosFlags(forwardMostMove),
10499                              fromY, fromX, y, x, (killX < 0)*promoChar,
10500                              s);
10501     if(killX >= 0 && killY >= 0)
10502         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10503
10504     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10505         int timeLeft; static int lastLoadFlag=0; int king, piece;
10506         piece = boards[forwardMostMove][fromY][fromX];
10507         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10508         if(gameInfo.variant == VariantKnightmate)
10509             king += (int) WhiteUnicorn - (int) WhiteKing;
10510         if(forwardMostMove == 0) {
10511             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10512                 fprintf(serverMoves, "%s;", UserName());
10513             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10514                 fprintf(serverMoves, "%s;", second.tidy);
10515             fprintf(serverMoves, "%s;", first.tidy);
10516             if(gameMode == MachinePlaysWhite)
10517                 fprintf(serverMoves, "%s;", UserName());
10518             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10519                 fprintf(serverMoves, "%s;", second.tidy);
10520         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10521         lastLoadFlag = loadFlag;
10522         // print base move
10523         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10524         // print castling suffix
10525         if( toY == fromY && piece == king ) {
10526             if(toX-fromX > 1)
10527                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10528             if(fromX-toX >1)
10529                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10530         }
10531         // e.p. suffix
10532         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10533              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10534              boards[forwardMostMove][toY][toX] == EmptySquare
10535              && fromX != toX && fromY != toY)
10536                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10537         // promotion suffix
10538         if(promoChar != NULLCHAR) {
10539             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10540                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10541                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10542             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10543         }
10544         if(!loadFlag) {
10545                 char buf[MOVE_LEN*2], *p; int len;
10546             fprintf(serverMoves, "/%d/%d",
10547                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10548             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10549             else                      timeLeft = blackTimeRemaining/1000;
10550             fprintf(serverMoves, "/%d", timeLeft);
10551                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10552                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10553                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10554                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10555             fprintf(serverMoves, "/%s", buf);
10556         }
10557         fflush(serverMoves);
10558     }
10559
10560     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10561         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10562       return;
10563     }
10564     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10565     if (commentList[forwardMostMove+1] != NULL) {
10566         free(commentList[forwardMostMove+1]);
10567         commentList[forwardMostMove+1] = NULL;
10568     }
10569     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10570     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10571     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10572     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10573     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10574     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10575     adjustedClock = FALSE;
10576     gameInfo.result = GameUnfinished;
10577     if (gameInfo.resultDetails != NULL) {
10578         free(gameInfo.resultDetails);
10579         gameInfo.resultDetails = NULL;
10580     }
10581     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10582                               moveList[forwardMostMove - 1]);
10583     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10584       case MT_NONE:
10585       case MT_STALEMATE:
10586       default:
10587         break;
10588       case MT_CHECK:
10589         if(!IS_SHOGI(gameInfo.variant))
10590             strcat(parseList[forwardMostMove - 1], "+");
10591         break;
10592       case MT_CHECKMATE:
10593       case MT_STAINMATE:
10594         strcat(parseList[forwardMostMove - 1], "#");
10595         break;
10596     }
10597 }
10598
10599 /* Updates currentMove if not pausing */
10600 void
10601 ShowMove (int fromX, int fromY, int toX, int toY)
10602 {
10603     int instant = (gameMode == PlayFromGameFile) ?
10604         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10605     if(appData.noGUI) return;
10606     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10607         if (!instant) {
10608             if (forwardMostMove == currentMove + 1) {
10609                 AnimateMove(boards[forwardMostMove - 1],
10610                             fromX, fromY, toX, toY);
10611             }
10612         }
10613         currentMove = forwardMostMove;
10614     }
10615
10616     killX = killY = -1; // [HGM] lion: used up
10617
10618     if (instant) return;
10619
10620     DisplayMove(currentMove - 1);
10621     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10622             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10623                 SetHighlights(fromX, fromY, toX, toY);
10624             }
10625     }
10626     DrawPosition(FALSE, boards[currentMove]);
10627     DisplayBothClocks();
10628     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10629 }
10630
10631 void
10632 SendEgtPath (ChessProgramState *cps)
10633 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10634         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10635
10636         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10637
10638         while(*p) {
10639             char c, *q = name+1, *r, *s;
10640
10641             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10642             while(*p && *p != ',') *q++ = *p++;
10643             *q++ = ':'; *q = 0;
10644             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10645                 strcmp(name, ",nalimov:") == 0 ) {
10646                 // take nalimov path from the menu-changeable option first, if it is defined
10647               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10648                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10649             } else
10650             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10651                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10652                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10653                 s = r = StrStr(s, ":") + 1; // beginning of path info
10654                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10655                 c = *r; *r = 0;             // temporarily null-terminate path info
10656                     *--q = 0;               // strip of trailig ':' from name
10657                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10658                 *r = c;
10659                 SendToProgram(buf,cps);     // send egtbpath command for this format
10660             }
10661             if(*p == ',') p++; // read away comma to position for next format name
10662         }
10663 }
10664
10665 static int
10666 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10667 {
10668       int width = 8, height = 8, holdings = 0;             // most common sizes
10669       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10670       // correct the deviations default for each variant
10671       if( v == VariantXiangqi ) width = 9,  height = 10;
10672       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10673       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10674       if( v == VariantCapablanca || v == VariantCapaRandom ||
10675           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10676                                 width = 10;
10677       if( v == VariantCourier ) width = 12;
10678       if( v == VariantSuper )                            holdings = 8;
10679       if( v == VariantGreat )   width = 10,              holdings = 8;
10680       if( v == VariantSChess )                           holdings = 7;
10681       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10682       if( v == VariantChuChess) width = 10, height = 10;
10683       if( v == VariantChu )     width = 12, height = 12;
10684       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10685              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10686              holdingsSize >= 0 && holdingsSize != holdings;
10687 }
10688
10689 char variantError[MSG_SIZ];
10690
10691 char *
10692 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10693 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10694       char *p, *variant = VariantName(v);
10695       static char b[MSG_SIZ];
10696       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10697            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10698                                                holdingsSize, variant); // cook up sized variant name
10699            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10700            if(StrStr(list, b) == NULL) {
10701                // specific sized variant not known, check if general sizing allowed
10702                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10703                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10704                             boardWidth, boardHeight, holdingsSize, engine);
10705                    return NULL;
10706                }
10707                /* [HGM] here we really should compare with the maximum supported board size */
10708            }
10709       } else snprintf(b, MSG_SIZ,"%s", variant);
10710       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10711       p = StrStr(list, b);
10712       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10713       if(p == NULL) {
10714           // occurs not at all in list, or only as sub-string
10715           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10716           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10717               int l = strlen(variantError);
10718               char *q;
10719               while(p != list && p[-1] != ',') p--;
10720               q = strchr(p, ',');
10721               if(q) *q = NULLCHAR;
10722               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10723               if(q) *q= ',';
10724           }
10725           return NULL;
10726       }
10727       return b;
10728 }
10729
10730 void
10731 InitChessProgram (ChessProgramState *cps, int setup)
10732 /* setup needed to setup FRC opening position */
10733 {
10734     char buf[MSG_SIZ], *b;
10735     if (appData.noChessProgram) return;
10736     hintRequested = FALSE;
10737     bookRequested = FALSE;
10738
10739     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10740     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10741     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10742     if(cps->memSize) { /* [HGM] memory */
10743       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10744         SendToProgram(buf, cps);
10745     }
10746     SendEgtPath(cps); /* [HGM] EGT */
10747     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10748       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10749         SendToProgram(buf, cps);
10750     }
10751
10752     setboardSpoiledMachineBlack = FALSE;
10753     SendToProgram(cps->initString, cps);
10754     if (gameInfo.variant != VariantNormal &&
10755         gameInfo.variant != VariantLoadable
10756         /* [HGM] also send variant if board size non-standard */
10757         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10758
10759       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10760                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10761       if (b == NULL) {
10762         VariantClass v;
10763         char c, *q = cps->variants, *p = strchr(q, ',');
10764         if(p) *p = NULLCHAR;
10765         v = StringToVariant(q);
10766         DisplayError(variantError, 0);
10767         if(v != VariantUnknown && cps == &first) {
10768             int w, h, s;
10769             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10770                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10771             ASSIGN(appData.variant, q);
10772             Reset(TRUE, FALSE);
10773         }
10774         if(p) *p = ',';
10775         return;
10776       }
10777
10778       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10779       SendToProgram(buf, cps);
10780     }
10781     currentlyInitializedVariant = gameInfo.variant;
10782
10783     /* [HGM] send opening position in FRC to first engine */
10784     if(setup) {
10785           SendToProgram("force\n", cps);
10786           SendBoard(cps, 0);
10787           /* engine is now in force mode! Set flag to wake it up after first move. */
10788           setboardSpoiledMachineBlack = 1;
10789     }
10790
10791     if (cps->sendICS) {
10792       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10793       SendToProgram(buf, cps);
10794     }
10795     cps->maybeThinking = FALSE;
10796     cps->offeredDraw = 0;
10797     if (!appData.icsActive) {
10798         SendTimeControl(cps, movesPerSession, timeControl,
10799                         timeIncrement, appData.searchDepth,
10800                         searchTime);
10801     }
10802     if (appData.showThinking
10803         // [HGM] thinking: four options require thinking output to be sent
10804         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10805                                 ) {
10806         SendToProgram("post\n", cps);
10807     }
10808     SendToProgram("hard\n", cps);
10809     if (!appData.ponderNextMove) {
10810         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10811            it without being sure what state we are in first.  "hard"
10812            is not a toggle, so that one is OK.
10813          */
10814         SendToProgram("easy\n", cps);
10815     }
10816     if (cps->usePing) {
10817       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10818       SendToProgram(buf, cps);
10819     }
10820     cps->initDone = TRUE;
10821     ClearEngineOutputPane(cps == &second);
10822 }
10823
10824
10825 void
10826 ResendOptions (ChessProgramState *cps)
10827 { // send the stored value of the options
10828   int i;
10829   char buf[MSG_SIZ];
10830   Option *opt = cps->option;
10831   for(i=0; i<cps->nrOptions; i++, opt++) {
10832       switch(opt->type) {
10833         case Spin:
10834         case Slider:
10835         case CheckBox:
10836             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10837           break;
10838         case ComboBox:
10839           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10840           break;
10841         default:
10842             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10843           break;
10844         case Button:
10845         case SaveButton:
10846           continue;
10847       }
10848       SendToProgram(buf, cps);
10849   }
10850 }
10851
10852 void
10853 StartChessProgram (ChessProgramState *cps)
10854 {
10855     char buf[MSG_SIZ];
10856     int err;
10857
10858     if (appData.noChessProgram) return;
10859     cps->initDone = FALSE;
10860
10861     if (strcmp(cps->host, "localhost") == 0) {
10862         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10863     } else if (*appData.remoteShell == NULLCHAR) {
10864         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10865     } else {
10866         if (*appData.remoteUser == NULLCHAR) {
10867           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10868                     cps->program);
10869         } else {
10870           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10871                     cps->host, appData.remoteUser, cps->program);
10872         }
10873         err = StartChildProcess(buf, "", &cps->pr);
10874     }
10875
10876     if (err != 0) {
10877       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10878         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10879         if(cps != &first) return;
10880         appData.noChessProgram = TRUE;
10881         ThawUI();
10882         SetNCPMode();
10883 //      DisplayFatalError(buf, err, 1);
10884 //      cps->pr = NoProc;
10885 //      cps->isr = NULL;
10886         return;
10887     }
10888
10889     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10890     if (cps->protocolVersion > 1) {
10891       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10892       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10893         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10894         cps->comboCnt = 0;  //                and values of combo boxes
10895       }
10896       SendToProgram(buf, cps);
10897       if(cps->reload) ResendOptions(cps);
10898     } else {
10899       SendToProgram("xboard\n", cps);
10900     }
10901 }
10902
10903 void
10904 TwoMachinesEventIfReady P((void))
10905 {
10906   static int curMess = 0;
10907   if (first.lastPing != first.lastPong) {
10908     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10909     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10910     return;
10911   }
10912   if (second.lastPing != second.lastPong) {
10913     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10914     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10915     return;
10916   }
10917   DisplayMessage("", ""); curMess = 0;
10918   TwoMachinesEvent();
10919 }
10920
10921 char *
10922 MakeName (char *template)
10923 {
10924     time_t clock;
10925     struct tm *tm;
10926     static char buf[MSG_SIZ];
10927     char *p = buf;
10928     int i;
10929
10930     clock = time((time_t *)NULL);
10931     tm = localtime(&clock);
10932
10933     while(*p++ = *template++) if(p[-1] == '%') {
10934         switch(*template++) {
10935           case 0:   *p = 0; return buf;
10936           case 'Y': i = tm->tm_year+1900; break;
10937           case 'y': i = tm->tm_year-100; break;
10938           case 'M': i = tm->tm_mon+1; break;
10939           case 'd': i = tm->tm_mday; break;
10940           case 'h': i = tm->tm_hour; break;
10941           case 'm': i = tm->tm_min; break;
10942           case 's': i = tm->tm_sec; break;
10943           default:  i = 0;
10944         }
10945         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10946     }
10947     return buf;
10948 }
10949
10950 int
10951 CountPlayers (char *p)
10952 {
10953     int n = 0;
10954     while(p = strchr(p, '\n')) p++, n++; // count participants
10955     return n;
10956 }
10957
10958 FILE *
10959 WriteTourneyFile (char *results, FILE *f)
10960 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10961     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10962     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10963         // create a file with tournament description
10964         fprintf(f, "-participants {%s}\n", appData.participants);
10965         fprintf(f, "-seedBase %d\n", appData.seedBase);
10966         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10967         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10968         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10969         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10970         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10971         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10972         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10973         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10974         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10975         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10976         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10977         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10978         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10979         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10980         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10981         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10982         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10983         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10984         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10985         fprintf(f, "-smpCores %d\n", appData.smpCores);
10986         if(searchTime > 0)
10987                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10988         else {
10989                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10990                 fprintf(f, "-tc %s\n", appData.timeControl);
10991                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10992         }
10993         fprintf(f, "-results \"%s\"\n", results);
10994     }
10995     return f;
10996 }
10997
10998 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10999
11000 void
11001 Substitute (char *participants, int expunge)
11002 {
11003     int i, changed, changes=0, nPlayers=0;
11004     char *p, *q, *r, buf[MSG_SIZ];
11005     if(participants == NULL) return;
11006     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11007     r = p = participants; q = appData.participants;
11008     while(*p && *p == *q) {
11009         if(*p == '\n') r = p+1, nPlayers++;
11010         p++; q++;
11011     }
11012     if(*p) { // difference
11013         while(*p && *p++ != '\n');
11014         while(*q && *q++ != '\n');
11015       changed = nPlayers;
11016         changes = 1 + (strcmp(p, q) != 0);
11017     }
11018     if(changes == 1) { // a single engine mnemonic was changed
11019         q = r; while(*q) nPlayers += (*q++ == '\n');
11020         p = buf; while(*r && (*p = *r++) != '\n') p++;
11021         *p = NULLCHAR;
11022         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11023         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11024         if(mnemonic[i]) { // The substitute is valid
11025             FILE *f;
11026             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11027                 flock(fileno(f), LOCK_EX);
11028                 ParseArgsFromFile(f);
11029                 fseek(f, 0, SEEK_SET);
11030                 FREE(appData.participants); appData.participants = participants;
11031                 if(expunge) { // erase results of replaced engine
11032                     int len = strlen(appData.results), w, b, dummy;
11033                     for(i=0; i<len; i++) {
11034                         Pairing(i, nPlayers, &w, &b, &dummy);
11035                         if((w == changed || b == changed) && appData.results[i] == '*') {
11036                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11037                             fclose(f);
11038                             return;
11039                         }
11040                     }
11041                     for(i=0; i<len; i++) {
11042                         Pairing(i, nPlayers, &w, &b, &dummy);
11043                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11044                     }
11045                 }
11046                 WriteTourneyFile(appData.results, f);
11047                 fclose(f); // release lock
11048                 return;
11049             }
11050         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11051     }
11052     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11053     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11054     free(participants);
11055     return;
11056 }
11057
11058 int
11059 CheckPlayers (char *participants)
11060 {
11061         int i;
11062         char buf[MSG_SIZ], *p;
11063         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11064         while(p = strchr(participants, '\n')) {
11065             *p = NULLCHAR;
11066             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11067             if(!mnemonic[i]) {
11068                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11069                 *p = '\n';
11070                 DisplayError(buf, 0);
11071                 return 1;
11072             }
11073             *p = '\n';
11074             participants = p + 1;
11075         }
11076         return 0;
11077 }
11078
11079 int
11080 CreateTourney (char *name)
11081 {
11082         FILE *f;
11083         if(matchMode && strcmp(name, appData.tourneyFile)) {
11084              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11085         }
11086         if(name[0] == NULLCHAR) {
11087             if(appData.participants[0])
11088                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11089             return 0;
11090         }
11091         f = fopen(name, "r");
11092         if(f) { // file exists
11093             ASSIGN(appData.tourneyFile, name);
11094             ParseArgsFromFile(f); // parse it
11095         } else {
11096             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11097             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11098                 DisplayError(_("Not enough participants"), 0);
11099                 return 0;
11100             }
11101             if(CheckPlayers(appData.participants)) return 0;
11102             ASSIGN(appData.tourneyFile, name);
11103             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11104             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11105         }
11106         fclose(f);
11107         appData.noChessProgram = FALSE;
11108         appData.clockMode = TRUE;
11109         SetGNUMode();
11110         return 1;
11111 }
11112
11113 int
11114 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11115 {
11116     char buf[MSG_SIZ], *p, *q;
11117     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11118     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11119     skip = !all && group[0]; // if group requested, we start in skip mode
11120     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11121         p = names; q = buf; header = 0;
11122         while(*p && *p != '\n') *q++ = *p++;
11123         *q = 0;
11124         if(*p == '\n') p++;
11125         if(buf[0] == '#') {
11126             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11127             depth++; // we must be entering a new group
11128             if(all) continue; // suppress printing group headers when complete list requested
11129             header = 1;
11130             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11131         }
11132         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11133         if(engineList[i]) free(engineList[i]);
11134         engineList[i] = strdup(buf);
11135         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11136         if(engineMnemonic[i]) free(engineMnemonic[i]);
11137         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11138             strcat(buf, " (");
11139             sscanf(q + 8, "%s", buf + strlen(buf));
11140             strcat(buf, ")");
11141         }
11142         engineMnemonic[i] = strdup(buf);
11143         i++;
11144     }
11145     engineList[i] = engineMnemonic[i] = NULL;
11146     return i;
11147 }
11148
11149 // following implemented as macro to avoid type limitations
11150 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11151
11152 void
11153 SwapEngines (int n)
11154 {   // swap settings for first engine and other engine (so far only some selected options)
11155     int h;
11156     char *p;
11157     if(n == 0) return;
11158     SWAP(directory, p)
11159     SWAP(chessProgram, p)
11160     SWAP(isUCI, h)
11161     SWAP(hasOwnBookUCI, h)
11162     SWAP(protocolVersion, h)
11163     SWAP(reuse, h)
11164     SWAP(scoreIsAbsolute, h)
11165     SWAP(timeOdds, h)
11166     SWAP(logo, p)
11167     SWAP(pgnName, p)
11168     SWAP(pvSAN, h)
11169     SWAP(engOptions, p)
11170     SWAP(engInitString, p)
11171     SWAP(computerString, p)
11172     SWAP(features, p)
11173     SWAP(fenOverride, p)
11174     SWAP(NPS, h)
11175     SWAP(accumulateTC, h)
11176     SWAP(drawDepth, h)
11177     SWAP(host, p)
11178     SWAP(pseudo, h)
11179 }
11180
11181 int
11182 GetEngineLine (char *s, int n)
11183 {
11184     int i;
11185     char buf[MSG_SIZ];
11186     extern char *icsNames;
11187     if(!s || !*s) return 0;
11188     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11189     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11190     if(!mnemonic[i]) return 0;
11191     if(n == 11) return 1; // just testing if there was a match
11192     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11193     if(n == 1) SwapEngines(n);
11194     ParseArgsFromString(buf);
11195     if(n == 1) SwapEngines(n);
11196     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11197         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11198         ParseArgsFromString(buf);
11199     }
11200     return 1;
11201 }
11202
11203 int
11204 SetPlayer (int player, char *p)
11205 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11206     int i;
11207     char buf[MSG_SIZ], *engineName;
11208     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11209     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11210     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11211     if(mnemonic[i]) {
11212         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11213         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11214         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11215         ParseArgsFromString(buf);
11216     } else { // no engine with this nickname is installed!
11217         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11218         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11219         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11220         ModeHighlight();
11221         DisplayError(buf, 0);
11222         return 0;
11223     }
11224     free(engineName);
11225     return i;
11226 }
11227
11228 char *recentEngines;
11229
11230 void
11231 RecentEngineEvent (int nr)
11232 {
11233     int n;
11234 //    SwapEngines(1); // bump first to second
11235 //    ReplaceEngine(&second, 1); // and load it there
11236     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11237     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11238     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11239         ReplaceEngine(&first, 0);
11240         FloatToFront(&appData.recentEngineList, command[n]);
11241     }
11242 }
11243
11244 int
11245 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11246 {   // determine players from game number
11247     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11248
11249     if(appData.tourneyType == 0) {
11250         roundsPerCycle = (nPlayers - 1) | 1;
11251         pairingsPerRound = nPlayers / 2;
11252     } else if(appData.tourneyType > 0) {
11253         roundsPerCycle = nPlayers - appData.tourneyType;
11254         pairingsPerRound = appData.tourneyType;
11255     }
11256     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11257     gamesPerCycle = gamesPerRound * roundsPerCycle;
11258     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11259     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11260     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11261     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11262     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11263     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11264
11265     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11266     if(appData.roundSync) *syncInterval = gamesPerRound;
11267
11268     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11269
11270     if(appData.tourneyType == 0) {
11271         if(curPairing == (nPlayers-1)/2 ) {
11272             *whitePlayer = curRound;
11273             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11274         } else {
11275             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11276             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11277             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11278             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11279         }
11280     } else if(appData.tourneyType > 1) {
11281         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11282         *whitePlayer = curRound + appData.tourneyType;
11283     } else if(appData.tourneyType > 0) {
11284         *whitePlayer = curPairing;
11285         *blackPlayer = curRound + appData.tourneyType;
11286     }
11287
11288     // take care of white/black alternation per round.
11289     // For cycles and games this is already taken care of by default, derived from matchGame!
11290     return curRound & 1;
11291 }
11292
11293 int
11294 NextTourneyGame (int nr, int *swapColors)
11295 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11296     char *p, *q;
11297     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11298     FILE *tf;
11299     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11300     tf = fopen(appData.tourneyFile, "r");
11301     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11302     ParseArgsFromFile(tf); fclose(tf);
11303     InitTimeControls(); // TC might be altered from tourney file
11304
11305     nPlayers = CountPlayers(appData.participants); // count participants
11306     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11307     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11308
11309     if(syncInterval) {
11310         p = q = appData.results;
11311         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11312         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11313             DisplayMessage(_("Waiting for other game(s)"),"");
11314             waitingForGame = TRUE;
11315             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11316             return 0;
11317         }
11318         waitingForGame = FALSE;
11319     }
11320
11321     if(appData.tourneyType < 0) {
11322         if(nr>=0 && !pairingReceived) {
11323             char buf[1<<16];
11324             if(pairing.pr == NoProc) {
11325                 if(!appData.pairingEngine[0]) {
11326                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11327                     return 0;
11328                 }
11329                 StartChessProgram(&pairing); // starts the pairing engine
11330             }
11331             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11332             SendToProgram(buf, &pairing);
11333             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11334             SendToProgram(buf, &pairing);
11335             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11336         }
11337         pairingReceived = 0;                              // ... so we continue here
11338         *swapColors = 0;
11339         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11340         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11341         matchGame = 1; roundNr = nr / syncInterval + 1;
11342     }
11343
11344     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11345
11346     // redefine engines, engine dir, etc.
11347     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11348     if(first.pr == NoProc) {
11349       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11350       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11351     }
11352     if(second.pr == NoProc) {
11353       SwapEngines(1);
11354       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11355       SwapEngines(1);         // and make that valid for second engine by swapping
11356       InitEngine(&second, 1);
11357     }
11358     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11359     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11360     return OK;
11361 }
11362
11363 void
11364 NextMatchGame ()
11365 {   // performs game initialization that does not invoke engines, and then tries to start the game
11366     int res, firstWhite, swapColors = 0;
11367     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11368     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
11369         char buf[MSG_SIZ];
11370         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11371         if(strcmp(buf, currentDebugFile)) { // name has changed
11372             FILE *f = fopen(buf, "w");
11373             if(f) { // if opening the new file failed, just keep using the old one
11374                 ASSIGN(currentDebugFile, buf);
11375                 fclose(debugFP);
11376                 debugFP = f;
11377             }
11378             if(appData.serverFileName) {
11379                 if(serverFP) fclose(serverFP);
11380                 serverFP = fopen(appData.serverFileName, "w");
11381                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11382                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11383             }
11384         }
11385     }
11386     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11387     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11388     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11389     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11390     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11391     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11392     Reset(FALSE, first.pr != NoProc);
11393     res = LoadGameOrPosition(matchGame); // setup game
11394     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11395     if(!res) return; // abort when bad game/pos file
11396     TwoMachinesEvent();
11397 }
11398
11399 void
11400 UserAdjudicationEvent (int result)
11401 {
11402     ChessMove gameResult = GameIsDrawn;
11403
11404     if( result > 0 ) {
11405         gameResult = WhiteWins;
11406     }
11407     else if( result < 0 ) {
11408         gameResult = BlackWins;
11409     }
11410
11411     if( gameMode == TwoMachinesPlay ) {
11412         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11413     }
11414 }
11415
11416
11417 // [HGM] save: calculate checksum of game to make games easily identifiable
11418 int
11419 StringCheckSum (char *s)
11420 {
11421         int i = 0;
11422         if(s==NULL) return 0;
11423         while(*s) i = i*259 + *s++;
11424         return i;
11425 }
11426
11427 int
11428 GameCheckSum ()
11429 {
11430         int i, sum=0;
11431         for(i=backwardMostMove; i<forwardMostMove; i++) {
11432                 sum += pvInfoList[i].depth;
11433                 sum += StringCheckSum(parseList[i]);
11434                 sum += StringCheckSum(commentList[i]);
11435                 sum *= 261;
11436         }
11437         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11438         return sum + StringCheckSum(commentList[i]);
11439 } // end of save patch
11440
11441 void
11442 GameEnds (ChessMove result, char *resultDetails, int whosays)
11443 {
11444     GameMode nextGameMode;
11445     int isIcsGame;
11446     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11447
11448     if(endingGame) return; /* [HGM] crash: forbid recursion */
11449     endingGame = 1;
11450     if(twoBoards) { // [HGM] dual: switch back to one board
11451         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11452         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11453     }
11454     if (appData.debugMode) {
11455       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11456               result, resultDetails ? resultDetails : "(null)", whosays);
11457     }
11458
11459     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11460
11461     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11462
11463     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11464         /* If we are playing on ICS, the server decides when the
11465            game is over, but the engine can offer to draw, claim
11466            a draw, or resign.
11467          */
11468 #if ZIPPY
11469         if (appData.zippyPlay && first.initDone) {
11470             if (result == GameIsDrawn) {
11471                 /* In case draw still needs to be claimed */
11472                 SendToICS(ics_prefix);
11473                 SendToICS("draw\n");
11474             } else if (StrCaseStr(resultDetails, "resign")) {
11475                 SendToICS(ics_prefix);
11476                 SendToICS("resign\n");
11477             }
11478         }
11479 #endif
11480         endingGame = 0; /* [HGM] crash */
11481         return;
11482     }
11483
11484     /* If we're loading the game from a file, stop */
11485     if (whosays == GE_FILE) {
11486       (void) StopLoadGameTimer();
11487       gameFileFP = NULL;
11488     }
11489
11490     /* Cancel draw offers */
11491     first.offeredDraw = second.offeredDraw = 0;
11492
11493     /* If this is an ICS game, only ICS can really say it's done;
11494        if not, anyone can. */
11495     isIcsGame = (gameMode == IcsPlayingWhite ||
11496                  gameMode == IcsPlayingBlack ||
11497                  gameMode == IcsObserving    ||
11498                  gameMode == IcsExamining);
11499
11500     if (!isIcsGame || whosays == GE_ICS) {
11501         /* OK -- not an ICS game, or ICS said it was done */
11502         StopClocks();
11503         if (!isIcsGame && !appData.noChessProgram)
11504           SetUserThinkingEnables();
11505
11506         /* [HGM] if a machine claims the game end we verify this claim */
11507         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11508             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11509                 char claimer;
11510                 ChessMove trueResult = (ChessMove) -1;
11511
11512                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11513                                             first.twoMachinesColor[0] :
11514                                             second.twoMachinesColor[0] ;
11515
11516                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11517                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11518                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11519                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11520                 } else
11521                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11522                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11523                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11524                 } else
11525                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11526                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11527                 }
11528
11529                 // now verify win claims, but not in drop games, as we don't understand those yet
11530                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11531                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11532                     (result == WhiteWins && claimer == 'w' ||
11533                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11534                       if (appData.debugMode) {
11535                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11536                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11537                       }
11538                       if(result != trueResult) {
11539                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11540                               result = claimer == 'w' ? BlackWins : WhiteWins;
11541                               resultDetails = buf;
11542                       }
11543                 } else
11544                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11545                     && (forwardMostMove <= backwardMostMove ||
11546                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11547                         (claimer=='b')==(forwardMostMove&1))
11548                                                                                   ) {
11549                       /* [HGM] verify: draws that were not flagged are false claims */
11550                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11551                       result = claimer == 'w' ? BlackWins : WhiteWins;
11552                       resultDetails = buf;
11553                 }
11554                 /* (Claiming a loss is accepted no questions asked!) */
11555             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11556                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11557                 result = GameUnfinished;
11558                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11559             }
11560             /* [HGM] bare: don't allow bare King to win */
11561             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11562                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11563                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11564                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11565                && result != GameIsDrawn)
11566             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11567                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11568                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11569                         if(p >= 0 && p <= (int)WhiteKing) k++;
11570                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11571                 }
11572                 if (appData.debugMode) {
11573                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11574                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11575                 }
11576                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11577                         result = GameIsDrawn;
11578                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11579                         resultDetails = buf;
11580                 }
11581             }
11582         }
11583
11584
11585         if(serverMoves != NULL && !loadFlag) { char c = '=';
11586             if(result==WhiteWins) c = '+';
11587             if(result==BlackWins) c = '-';
11588             if(resultDetails != NULL)
11589                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11590         }
11591         if (resultDetails != NULL) {
11592             gameInfo.result = result;
11593             gameInfo.resultDetails = StrSave(resultDetails);
11594
11595             /* display last move only if game was not loaded from file */
11596             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11597                 DisplayMove(currentMove - 1);
11598
11599             if (forwardMostMove != 0) {
11600                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11601                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11602                                                                 ) {
11603                     if (*appData.saveGameFile != NULLCHAR) {
11604                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11605                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11606                         else
11607                         SaveGameToFile(appData.saveGameFile, TRUE);
11608                     } else if (appData.autoSaveGames) {
11609                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11610                     }
11611                     if (*appData.savePositionFile != NULLCHAR) {
11612                         SavePositionToFile(appData.savePositionFile);
11613                     }
11614                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11615                 }
11616             }
11617
11618             /* Tell program how game ended in case it is learning */
11619             /* [HGM] Moved this to after saving the PGN, just in case */
11620             /* engine died and we got here through time loss. In that */
11621             /* case we will get a fatal error writing the pipe, which */
11622             /* would otherwise lose us the PGN.                       */
11623             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11624             /* output during GameEnds should never be fatal anymore   */
11625             if (gameMode == MachinePlaysWhite ||
11626                 gameMode == MachinePlaysBlack ||
11627                 gameMode == TwoMachinesPlay ||
11628                 gameMode == IcsPlayingWhite ||
11629                 gameMode == IcsPlayingBlack ||
11630                 gameMode == BeginningOfGame) {
11631                 char buf[MSG_SIZ];
11632                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11633                         resultDetails);
11634                 if (first.pr != NoProc) {
11635                     SendToProgram(buf, &first);
11636                 }
11637                 if (second.pr != NoProc &&
11638                     gameMode == TwoMachinesPlay) {
11639                     SendToProgram(buf, &second);
11640                 }
11641             }
11642         }
11643
11644         if (appData.icsActive) {
11645             if (appData.quietPlay &&
11646                 (gameMode == IcsPlayingWhite ||
11647                  gameMode == IcsPlayingBlack)) {
11648                 SendToICS(ics_prefix);
11649                 SendToICS("set shout 1\n");
11650             }
11651             nextGameMode = IcsIdle;
11652             ics_user_moved = FALSE;
11653             /* clean up premove.  It's ugly when the game has ended and the
11654              * premove highlights are still on the board.
11655              */
11656             if (gotPremove) {
11657               gotPremove = FALSE;
11658               ClearPremoveHighlights();
11659               DrawPosition(FALSE, boards[currentMove]);
11660             }
11661             if (whosays == GE_ICS) {
11662                 switch (result) {
11663                 case WhiteWins:
11664                     if (gameMode == IcsPlayingWhite)
11665                         PlayIcsWinSound();
11666                     else if(gameMode == IcsPlayingBlack)
11667                         PlayIcsLossSound();
11668                     break;
11669                 case BlackWins:
11670                     if (gameMode == IcsPlayingBlack)
11671                         PlayIcsWinSound();
11672                     else if(gameMode == IcsPlayingWhite)
11673                         PlayIcsLossSound();
11674                     break;
11675                 case GameIsDrawn:
11676                     PlayIcsDrawSound();
11677                     break;
11678                 default:
11679                     PlayIcsUnfinishedSound();
11680                 }
11681             }
11682             if(appData.quitNext) { ExitEvent(0); return; }
11683         } else if (gameMode == EditGame ||
11684                    gameMode == PlayFromGameFile ||
11685                    gameMode == AnalyzeMode ||
11686                    gameMode == AnalyzeFile) {
11687             nextGameMode = gameMode;
11688         } else {
11689             nextGameMode = EndOfGame;
11690         }
11691         pausing = FALSE;
11692         ModeHighlight();
11693     } else {
11694         nextGameMode = gameMode;
11695     }
11696
11697     if (appData.noChessProgram) {
11698         gameMode = nextGameMode;
11699         ModeHighlight();
11700         endingGame = 0; /* [HGM] crash */
11701         return;
11702     }
11703
11704     if (first.reuse) {
11705         /* Put first chess program into idle state */
11706         if (first.pr != NoProc &&
11707             (gameMode == MachinePlaysWhite ||
11708              gameMode == MachinePlaysBlack ||
11709              gameMode == TwoMachinesPlay ||
11710              gameMode == IcsPlayingWhite ||
11711              gameMode == IcsPlayingBlack ||
11712              gameMode == BeginningOfGame)) {
11713             SendToProgram("force\n", &first);
11714             if (first.usePing) {
11715               char buf[MSG_SIZ];
11716               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11717               SendToProgram(buf, &first);
11718             }
11719         }
11720     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11721         /* Kill off first chess program */
11722         if (first.isr != NULL)
11723           RemoveInputSource(first.isr);
11724         first.isr = NULL;
11725
11726         if (first.pr != NoProc) {
11727             ExitAnalyzeMode();
11728             DoSleep( appData.delayBeforeQuit );
11729             SendToProgram("quit\n", &first);
11730             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11731             first.reload = TRUE;
11732         }
11733         first.pr = NoProc;
11734     }
11735     if (second.reuse) {
11736         /* Put second chess program into idle state */
11737         if (second.pr != NoProc &&
11738             gameMode == TwoMachinesPlay) {
11739             SendToProgram("force\n", &second);
11740             if (second.usePing) {
11741               char buf[MSG_SIZ];
11742               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11743               SendToProgram(buf, &second);
11744             }
11745         }
11746     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11747         /* Kill off second chess program */
11748         if (second.isr != NULL)
11749           RemoveInputSource(second.isr);
11750         second.isr = NULL;
11751
11752         if (second.pr != NoProc) {
11753             DoSleep( appData.delayBeforeQuit );
11754             SendToProgram("quit\n", &second);
11755             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11756             second.reload = TRUE;
11757         }
11758         second.pr = NoProc;
11759     }
11760
11761     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11762         char resChar = '=';
11763         switch (result) {
11764         case WhiteWins:
11765           resChar = '+';
11766           if (first.twoMachinesColor[0] == 'w') {
11767             first.matchWins++;
11768           } else {
11769             second.matchWins++;
11770           }
11771           break;
11772         case BlackWins:
11773           resChar = '-';
11774           if (first.twoMachinesColor[0] == 'b') {
11775             first.matchWins++;
11776           } else {
11777             second.matchWins++;
11778           }
11779           break;
11780         case GameUnfinished:
11781           resChar = ' ';
11782         default:
11783           break;
11784         }
11785
11786         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11787         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11788             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11789             ReserveGame(nextGame, resChar); // sets nextGame
11790             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11791             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11792         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11793
11794         if (nextGame <= appData.matchGames && !abortMatch) {
11795             gameMode = nextGameMode;
11796             matchGame = nextGame; // this will be overruled in tourney mode!
11797             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11798             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11799             endingGame = 0; /* [HGM] crash */
11800             return;
11801         } else {
11802             gameMode = nextGameMode;
11803             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11804                      first.tidy, second.tidy,
11805                      first.matchWins, second.matchWins,
11806                      appData.matchGames - (first.matchWins + second.matchWins));
11807             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11808             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11809             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11810             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11811                 first.twoMachinesColor = "black\n";
11812                 second.twoMachinesColor = "white\n";
11813             } else {
11814                 first.twoMachinesColor = "white\n";
11815                 second.twoMachinesColor = "black\n";
11816             }
11817         }
11818     }
11819     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11820         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11821       ExitAnalyzeMode();
11822     gameMode = nextGameMode;
11823     ModeHighlight();
11824     endingGame = 0;  /* [HGM] crash */
11825     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11826         if(matchMode == TRUE) { // match through command line: exit with or without popup
11827             if(ranking) {
11828                 ToNrEvent(forwardMostMove);
11829                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11830                 else ExitEvent(0);
11831             } else DisplayFatalError(buf, 0, 0);
11832         } else { // match through menu; just stop, with or without popup
11833             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11834             ModeHighlight();
11835             if(ranking){
11836                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11837             } else DisplayNote(buf);
11838       }
11839       if(ranking) free(ranking);
11840     }
11841 }
11842
11843 /* Assumes program was just initialized (initString sent).
11844    Leaves program in force mode. */
11845 void
11846 FeedMovesToProgram (ChessProgramState *cps, int upto)
11847 {
11848     int i;
11849
11850     if (appData.debugMode)
11851       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11852               startedFromSetupPosition ? "position and " : "",
11853               backwardMostMove, upto, cps->which);
11854     if(currentlyInitializedVariant != gameInfo.variant) {
11855       char buf[MSG_SIZ];
11856         // [HGM] variantswitch: make engine aware of new variant
11857         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11858                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11859                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11860         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11861         SendToProgram(buf, cps);
11862         currentlyInitializedVariant = gameInfo.variant;
11863     }
11864     SendToProgram("force\n", cps);
11865     if (startedFromSetupPosition) {
11866         SendBoard(cps, backwardMostMove);
11867     if (appData.debugMode) {
11868         fprintf(debugFP, "feedMoves\n");
11869     }
11870     }
11871     for (i = backwardMostMove; i < upto; i++) {
11872         SendMoveToProgram(i, cps);
11873     }
11874 }
11875
11876
11877 int
11878 ResurrectChessProgram ()
11879 {
11880      /* The chess program may have exited.
11881         If so, restart it and feed it all the moves made so far. */
11882     static int doInit = 0;
11883
11884     if (appData.noChessProgram) return 1;
11885
11886     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11887         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11888         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11889         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11890     } else {
11891         if (first.pr != NoProc) return 1;
11892         StartChessProgram(&first);
11893     }
11894     InitChessProgram(&first, FALSE);
11895     FeedMovesToProgram(&first, currentMove);
11896
11897     if (!first.sendTime) {
11898         /* can't tell gnuchess what its clock should read,
11899            so we bow to its notion. */
11900         ResetClocks();
11901         timeRemaining[0][currentMove] = whiteTimeRemaining;
11902         timeRemaining[1][currentMove] = blackTimeRemaining;
11903     }
11904
11905     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11906                 appData.icsEngineAnalyze) && first.analysisSupport) {
11907       SendToProgram("analyze\n", &first);
11908       first.analyzing = TRUE;
11909     }
11910     return 1;
11911 }
11912
11913 /*
11914  * Button procedures
11915  */
11916 void
11917 Reset (int redraw, int init)
11918 {
11919     int i;
11920
11921     if (appData.debugMode) {
11922         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11923                 redraw, init, gameMode);
11924     }
11925     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11926     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11927     CleanupTail(); // [HGM] vari: delete any stored variations
11928     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11929     pausing = pauseExamInvalid = FALSE;
11930     startedFromSetupPosition = blackPlaysFirst = FALSE;
11931     firstMove = TRUE;
11932     whiteFlag = blackFlag = FALSE;
11933     userOfferedDraw = FALSE;
11934     hintRequested = bookRequested = FALSE;
11935     first.maybeThinking = FALSE;
11936     second.maybeThinking = FALSE;
11937     first.bookSuspend = FALSE; // [HGM] book
11938     second.bookSuspend = FALSE;
11939     thinkOutput[0] = NULLCHAR;
11940     lastHint[0] = NULLCHAR;
11941     ClearGameInfo(&gameInfo);
11942     gameInfo.variant = StringToVariant(appData.variant);
11943     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11944     ics_user_moved = ics_clock_paused = FALSE;
11945     ics_getting_history = H_FALSE;
11946     ics_gamenum = -1;
11947     white_holding[0] = black_holding[0] = NULLCHAR;
11948     ClearProgramStats();
11949     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11950
11951     ResetFrontEnd();
11952     ClearHighlights();
11953     flipView = appData.flipView;
11954     ClearPremoveHighlights();
11955     gotPremove = FALSE;
11956     alarmSounded = FALSE;
11957     killX = killY = -1; // [HGM] lion
11958
11959     GameEnds(EndOfFile, NULL, GE_PLAYER);
11960     if(appData.serverMovesName != NULL) {
11961         /* [HGM] prepare to make moves file for broadcasting */
11962         clock_t t = clock();
11963         if(serverMoves != NULL) fclose(serverMoves);
11964         serverMoves = fopen(appData.serverMovesName, "r");
11965         if(serverMoves != NULL) {
11966             fclose(serverMoves);
11967             /* delay 15 sec before overwriting, so all clients can see end */
11968             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11969         }
11970         serverMoves = fopen(appData.serverMovesName, "w");
11971     }
11972
11973     ExitAnalyzeMode();
11974     gameMode = BeginningOfGame;
11975     ModeHighlight();
11976     if(appData.icsActive) gameInfo.variant = VariantNormal;
11977     currentMove = forwardMostMove = backwardMostMove = 0;
11978     MarkTargetSquares(1);
11979     InitPosition(redraw);
11980     for (i = 0; i < MAX_MOVES; i++) {
11981         if (commentList[i] != NULL) {
11982             free(commentList[i]);
11983             commentList[i] = NULL;
11984         }
11985     }
11986     ResetClocks();
11987     timeRemaining[0][0] = whiteTimeRemaining;
11988     timeRemaining[1][0] = blackTimeRemaining;
11989
11990     if (first.pr == NoProc) {
11991         StartChessProgram(&first);
11992     }
11993     if (init) {
11994             InitChessProgram(&first, startedFromSetupPosition);
11995     }
11996     DisplayTitle("");
11997     DisplayMessage("", "");
11998     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11999     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12000     ClearMap();        // [HGM] exclude: invalidate map
12001 }
12002
12003 void
12004 AutoPlayGameLoop ()
12005 {
12006     for (;;) {
12007         if (!AutoPlayOneMove())
12008           return;
12009         if (matchMode || appData.timeDelay == 0)
12010           continue;
12011         if (appData.timeDelay < 0)
12012           return;
12013         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12014         break;
12015     }
12016 }
12017
12018 void
12019 AnalyzeNextGame()
12020 {
12021     ReloadGame(1); // next game
12022 }
12023
12024 int
12025 AutoPlayOneMove ()
12026 {
12027     int fromX, fromY, toX, toY;
12028
12029     if (appData.debugMode) {
12030       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12031     }
12032
12033     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12034       return FALSE;
12035
12036     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12037       pvInfoList[currentMove].depth = programStats.depth;
12038       pvInfoList[currentMove].score = programStats.score;
12039       pvInfoList[currentMove].time  = 0;
12040       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12041       else { // append analysis of final position as comment
12042         char buf[MSG_SIZ];
12043         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12044         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12045       }
12046       programStats.depth = 0;
12047     }
12048
12049     if (currentMove >= forwardMostMove) {
12050       if(gameMode == AnalyzeFile) {
12051           if(appData.loadGameIndex == -1) {
12052             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12053           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12054           } else {
12055           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12056         }
12057       }
12058 //      gameMode = EndOfGame;
12059 //      ModeHighlight();
12060
12061       /* [AS] Clear current move marker at the end of a game */
12062       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12063
12064       return FALSE;
12065     }
12066
12067     toX = moveList[currentMove][2] - AAA;
12068     toY = moveList[currentMove][3] - ONE;
12069
12070     if (moveList[currentMove][1] == '@') {
12071         if (appData.highlightLastMove) {
12072             SetHighlights(-1, -1, toX, toY);
12073         }
12074     } else {
12075         int viaX = moveList[currentMove][5] - AAA;
12076         int viaY = moveList[currentMove][6] - ONE;
12077         fromX = moveList[currentMove][0] - AAA;
12078         fromY = moveList[currentMove][1] - ONE;
12079
12080         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12081
12082         if(moveList[currentMove][4] == ';') { // multi-leg
12083             ChessSquare piece = boards[currentMove][viaY][viaX];
12084             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12085             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12086             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12087             boards[currentMove][viaY][viaX] = piece;
12088         } else
12089         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12090
12091         if (appData.highlightLastMove) {
12092             SetHighlights(fromX, fromY, toX, toY);
12093         }
12094     }
12095     DisplayMove(currentMove);
12096     SendMoveToProgram(currentMove++, &first);
12097     DisplayBothClocks();
12098     DrawPosition(FALSE, boards[currentMove]);
12099     // [HGM] PV info: always display, routine tests if empty
12100     DisplayComment(currentMove - 1, commentList[currentMove]);
12101     return TRUE;
12102 }
12103
12104
12105 int
12106 LoadGameOneMove (ChessMove readAhead)
12107 {
12108     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12109     char promoChar = NULLCHAR;
12110     ChessMove moveType;
12111     char move[MSG_SIZ];
12112     char *p, *q;
12113
12114     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12115         gameMode != AnalyzeMode && gameMode != Training) {
12116         gameFileFP = NULL;
12117         return FALSE;
12118     }
12119
12120     yyboardindex = forwardMostMove;
12121     if (readAhead != EndOfFile) {
12122       moveType = readAhead;
12123     } else {
12124       if (gameFileFP == NULL)
12125           return FALSE;
12126       moveType = (ChessMove) Myylex();
12127     }
12128
12129     done = FALSE;
12130     switch (moveType) {
12131       case Comment:
12132         if (appData.debugMode)
12133           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12134         p = yy_text;
12135
12136         /* append the comment but don't display it */
12137         AppendComment(currentMove, p, FALSE);
12138         return TRUE;
12139
12140       case WhiteCapturesEnPassant:
12141       case BlackCapturesEnPassant:
12142       case WhitePromotion:
12143       case BlackPromotion:
12144       case WhiteNonPromotion:
12145       case BlackNonPromotion:
12146       case NormalMove:
12147       case FirstLeg:
12148       case WhiteKingSideCastle:
12149       case WhiteQueenSideCastle:
12150       case BlackKingSideCastle:
12151       case BlackQueenSideCastle:
12152       case WhiteKingSideCastleWild:
12153       case WhiteQueenSideCastleWild:
12154       case BlackKingSideCastleWild:
12155       case BlackQueenSideCastleWild:
12156       /* PUSH Fabien */
12157       case WhiteHSideCastleFR:
12158       case WhiteASideCastleFR:
12159       case BlackHSideCastleFR:
12160       case BlackASideCastleFR:
12161       /* POP Fabien */
12162         if (appData.debugMode)
12163           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12164         fromX = currentMoveString[0] - AAA;
12165         fromY = currentMoveString[1] - ONE;
12166         toX = currentMoveString[2] - AAA;
12167         toY = currentMoveString[3] - ONE;
12168         promoChar = currentMoveString[4];
12169         if(promoChar == ';') promoChar = currentMoveString[7];
12170         break;
12171
12172       case WhiteDrop:
12173       case BlackDrop:
12174         if (appData.debugMode)
12175           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12176         fromX = moveType == WhiteDrop ?
12177           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12178         (int) CharToPiece(ToLower(currentMoveString[0]));
12179         fromY = DROP_RANK;
12180         toX = currentMoveString[2] - AAA;
12181         toY = currentMoveString[3] - ONE;
12182         break;
12183
12184       case WhiteWins:
12185       case BlackWins:
12186       case GameIsDrawn:
12187       case GameUnfinished:
12188         if (appData.debugMode)
12189           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12190         p = strchr(yy_text, '{');
12191         if (p == NULL) p = strchr(yy_text, '(');
12192         if (p == NULL) {
12193             p = yy_text;
12194             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12195         } else {
12196             q = strchr(p, *p == '{' ? '}' : ')');
12197             if (q != NULL) *q = NULLCHAR;
12198             p++;
12199         }
12200         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12201         GameEnds(moveType, p, GE_FILE);
12202         done = TRUE;
12203         if (cmailMsgLoaded) {
12204             ClearHighlights();
12205             flipView = WhiteOnMove(currentMove);
12206             if (moveType == GameUnfinished) flipView = !flipView;
12207             if (appData.debugMode)
12208               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12209         }
12210         break;
12211
12212       case EndOfFile:
12213         if (appData.debugMode)
12214           fprintf(debugFP, "Parser hit end of file\n");
12215         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12216           case MT_NONE:
12217           case MT_CHECK:
12218             break;
12219           case MT_CHECKMATE:
12220           case MT_STAINMATE:
12221             if (WhiteOnMove(currentMove)) {
12222                 GameEnds(BlackWins, "Black mates", GE_FILE);
12223             } else {
12224                 GameEnds(WhiteWins, "White mates", GE_FILE);
12225             }
12226             break;
12227           case MT_STALEMATE:
12228             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12229             break;
12230         }
12231         done = TRUE;
12232         break;
12233
12234       case MoveNumberOne:
12235         if (lastLoadGameStart == GNUChessGame) {
12236             /* GNUChessGames have numbers, but they aren't move numbers */
12237             if (appData.debugMode)
12238               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12239                       yy_text, (int) moveType);
12240             return LoadGameOneMove(EndOfFile); /* tail recursion */
12241         }
12242         /* else fall thru */
12243
12244       case XBoardGame:
12245       case GNUChessGame:
12246       case PGNTag:
12247         /* Reached start of next game in file */
12248         if (appData.debugMode)
12249           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12250         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12251           case MT_NONE:
12252           case MT_CHECK:
12253             break;
12254           case MT_CHECKMATE:
12255           case MT_STAINMATE:
12256             if (WhiteOnMove(currentMove)) {
12257                 GameEnds(BlackWins, "Black mates", GE_FILE);
12258             } else {
12259                 GameEnds(WhiteWins, "White mates", GE_FILE);
12260             }
12261             break;
12262           case MT_STALEMATE:
12263             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12264             break;
12265         }
12266         done = TRUE;
12267         break;
12268
12269       case PositionDiagram:     /* should not happen; ignore */
12270       case ElapsedTime:         /* ignore */
12271       case NAG:                 /* ignore */
12272         if (appData.debugMode)
12273           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12274                   yy_text, (int) moveType);
12275         return LoadGameOneMove(EndOfFile); /* tail recursion */
12276
12277       case IllegalMove:
12278         if (appData.testLegality) {
12279             if (appData.debugMode)
12280               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12281             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12282                     (forwardMostMove / 2) + 1,
12283                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12284             DisplayError(move, 0);
12285             done = TRUE;
12286         } else {
12287             if (appData.debugMode)
12288               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12289                       yy_text, currentMoveString);
12290             if(currentMoveString[1] == '@') {
12291                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12292                 fromY = DROP_RANK;
12293             } else {
12294                 fromX = currentMoveString[0] - AAA;
12295                 fromY = currentMoveString[1] - ONE;
12296             }
12297             toX = currentMoveString[2] - AAA;
12298             toY = currentMoveString[3] - ONE;
12299             promoChar = currentMoveString[4];
12300         }
12301         break;
12302
12303       case AmbiguousMove:
12304         if (appData.debugMode)
12305           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12306         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12307                 (forwardMostMove / 2) + 1,
12308                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12309         DisplayError(move, 0);
12310         done = TRUE;
12311         break;
12312
12313       default:
12314       case ImpossibleMove:
12315         if (appData.debugMode)
12316           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12317         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12318                 (forwardMostMove / 2) + 1,
12319                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12320         DisplayError(move, 0);
12321         done = TRUE;
12322         break;
12323     }
12324
12325     if (done) {
12326         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12327             DrawPosition(FALSE, boards[currentMove]);
12328             DisplayBothClocks();
12329             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12330               DisplayComment(currentMove - 1, commentList[currentMove]);
12331         }
12332         (void) StopLoadGameTimer();
12333         gameFileFP = NULL;
12334         cmailOldMove = forwardMostMove;
12335         return FALSE;
12336     } else {
12337         /* currentMoveString is set as a side-effect of yylex */
12338
12339         thinkOutput[0] = NULLCHAR;
12340         MakeMove(fromX, fromY, toX, toY, promoChar);
12341         killX = killY = -1; // [HGM] lion: used up
12342         currentMove = forwardMostMove;
12343         return TRUE;
12344     }
12345 }
12346
12347 /* Load the nth game from the given file */
12348 int
12349 LoadGameFromFile (char *filename, int n, char *title, int useList)
12350 {
12351     FILE *f;
12352     char buf[MSG_SIZ];
12353
12354     if (strcmp(filename, "-") == 0) {
12355         f = stdin;
12356         title = "stdin";
12357     } else {
12358         f = fopen(filename, "rb");
12359         if (f == NULL) {
12360           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12361             DisplayError(buf, errno);
12362             return FALSE;
12363         }
12364     }
12365     if (fseek(f, 0, 0) == -1) {
12366         /* f is not seekable; probably a pipe */
12367         useList = FALSE;
12368     }
12369     if (useList && n == 0) {
12370         int error = GameListBuild(f);
12371         if (error) {
12372             DisplayError(_("Cannot build game list"), error);
12373         } else if (!ListEmpty(&gameList) &&
12374                    ((ListGame *) gameList.tailPred)->number > 1) {
12375             GameListPopUp(f, title);
12376             return TRUE;
12377         }
12378         GameListDestroy();
12379         n = 1;
12380     }
12381     if (n == 0) n = 1;
12382     return LoadGame(f, n, title, FALSE);
12383 }
12384
12385
12386 void
12387 MakeRegisteredMove ()
12388 {
12389     int fromX, fromY, toX, toY;
12390     char promoChar;
12391     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12392         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12393           case CMAIL_MOVE:
12394           case CMAIL_DRAW:
12395             if (appData.debugMode)
12396               fprintf(debugFP, "Restoring %s for game %d\n",
12397                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12398
12399             thinkOutput[0] = NULLCHAR;
12400             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12401             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12402             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12403             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12404             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12405             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12406             MakeMove(fromX, fromY, toX, toY, promoChar);
12407             ShowMove(fromX, fromY, toX, toY);
12408
12409             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12410               case MT_NONE:
12411               case MT_CHECK:
12412                 break;
12413
12414               case MT_CHECKMATE:
12415               case MT_STAINMATE:
12416                 if (WhiteOnMove(currentMove)) {
12417                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12418                 } else {
12419                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12420                 }
12421                 break;
12422
12423               case MT_STALEMATE:
12424                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12425                 break;
12426             }
12427
12428             break;
12429
12430           case CMAIL_RESIGN:
12431             if (WhiteOnMove(currentMove)) {
12432                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12433             } else {
12434                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12435             }
12436             break;
12437
12438           case CMAIL_ACCEPT:
12439             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12440             break;
12441
12442           default:
12443             break;
12444         }
12445     }
12446
12447     return;
12448 }
12449
12450 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12451 int
12452 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12453 {
12454     int retVal;
12455
12456     if (gameNumber > nCmailGames) {
12457         DisplayError(_("No more games in this message"), 0);
12458         return FALSE;
12459     }
12460     if (f == lastLoadGameFP) {
12461         int offset = gameNumber - lastLoadGameNumber;
12462         if (offset == 0) {
12463             cmailMsg[0] = NULLCHAR;
12464             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12465                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12466                 nCmailMovesRegistered--;
12467             }
12468             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12469             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12470                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12471             }
12472         } else {
12473             if (! RegisterMove()) return FALSE;
12474         }
12475     }
12476
12477     retVal = LoadGame(f, gameNumber, title, useList);
12478
12479     /* Make move registered during previous look at this game, if any */
12480     MakeRegisteredMove();
12481
12482     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12483         commentList[currentMove]
12484           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12485         DisplayComment(currentMove - 1, commentList[currentMove]);
12486     }
12487
12488     return retVal;
12489 }
12490
12491 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12492 int
12493 ReloadGame (int offset)
12494 {
12495     int gameNumber = lastLoadGameNumber + offset;
12496     if (lastLoadGameFP == NULL) {
12497         DisplayError(_("No game has been loaded yet"), 0);
12498         return FALSE;
12499     }
12500     if (gameNumber <= 0) {
12501         DisplayError(_("Can't back up any further"), 0);
12502         return FALSE;
12503     }
12504     if (cmailMsgLoaded) {
12505         return CmailLoadGame(lastLoadGameFP, gameNumber,
12506                              lastLoadGameTitle, lastLoadGameUseList);
12507     } else {
12508         return LoadGame(lastLoadGameFP, gameNumber,
12509                         lastLoadGameTitle, lastLoadGameUseList);
12510     }
12511 }
12512
12513 int keys[EmptySquare+1];
12514
12515 int
12516 PositionMatches (Board b1, Board b2)
12517 {
12518     int r, f, sum=0;
12519     switch(appData.searchMode) {
12520         case 1: return CompareWithRights(b1, b2);
12521         case 2:
12522             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12523                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12524             }
12525             return TRUE;
12526         case 3:
12527             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12528               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12529                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12530             }
12531             return sum==0;
12532         case 4:
12533             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12534                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12535             }
12536             return sum==0;
12537     }
12538     return TRUE;
12539 }
12540
12541 #define Q_PROMO  4
12542 #define Q_EP     3
12543 #define Q_BCASTL 2
12544 #define Q_WCASTL 1
12545
12546 int pieceList[256], quickBoard[256];
12547 ChessSquare pieceType[256] = { EmptySquare };
12548 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12549 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12550 int soughtTotal, turn;
12551 Boolean epOK, flipSearch;
12552
12553 typedef struct {
12554     unsigned char piece, to;
12555 } Move;
12556
12557 #define DSIZE (250000)
12558
12559 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12560 Move *moveDatabase = initialSpace;
12561 unsigned int movePtr, dataSize = DSIZE;
12562
12563 int
12564 MakePieceList (Board board, int *counts)
12565 {
12566     int r, f, n=Q_PROMO, total=0;
12567     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12568     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12569         int sq = f + (r<<4);
12570         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12571             quickBoard[sq] = ++n;
12572             pieceList[n] = sq;
12573             pieceType[n] = board[r][f];
12574             counts[board[r][f]]++;
12575             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12576             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12577             total++;
12578         }
12579     }
12580     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12581     return total;
12582 }
12583
12584 void
12585 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12586 {
12587     int sq = fromX + (fromY<<4);
12588     int piece = quickBoard[sq], rook;
12589     quickBoard[sq] = 0;
12590     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12591     if(piece == pieceList[1] && fromY == toY) {
12592       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12593         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12594         moveDatabase[movePtr++].piece = Q_WCASTL;
12595         quickBoard[sq] = piece;
12596         piece = quickBoard[from]; quickBoard[from] = 0;
12597         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12598       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12599         quickBoard[sq] = 0; // remove Rook
12600         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12601         moveDatabase[movePtr++].piece = Q_WCASTL;
12602         quickBoard[sq] = pieceList[1]; // put King
12603         piece = rook;
12604         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12605       }
12606     } else
12607     if(piece == pieceList[2] && fromY == toY) {
12608       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12609         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12610         moveDatabase[movePtr++].piece = Q_BCASTL;
12611         quickBoard[sq] = piece;
12612         piece = quickBoard[from]; quickBoard[from] = 0;
12613         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12614       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12615         quickBoard[sq] = 0; // remove Rook
12616         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12617         moveDatabase[movePtr++].piece = Q_BCASTL;
12618         quickBoard[sq] = pieceList[2]; // put King
12619         piece = rook;
12620         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12621       }
12622     } else
12623     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12624         quickBoard[(fromY<<4)+toX] = 0;
12625         moveDatabase[movePtr].piece = Q_EP;
12626         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12627         moveDatabase[movePtr].to = sq;
12628     } else
12629     if(promoPiece != pieceType[piece]) {
12630         moveDatabase[movePtr++].piece = Q_PROMO;
12631         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12632     }
12633     moveDatabase[movePtr].piece = piece;
12634     quickBoard[sq] = piece;
12635     movePtr++;
12636 }
12637
12638 int
12639 PackGame (Board board)
12640 {
12641     Move *newSpace = NULL;
12642     moveDatabase[movePtr].piece = 0; // terminate previous game
12643     if(movePtr > dataSize) {
12644         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12645         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12646         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12647         if(newSpace) {
12648             int i;
12649             Move *p = moveDatabase, *q = newSpace;
12650             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12651             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12652             moveDatabase = newSpace;
12653         } else { // calloc failed, we must be out of memory. Too bad...
12654             dataSize = 0; // prevent calloc events for all subsequent games
12655             return 0;     // and signal this one isn't cached
12656         }
12657     }
12658     movePtr++;
12659     MakePieceList(board, counts);
12660     return movePtr;
12661 }
12662
12663 int
12664 QuickCompare (Board board, int *minCounts, int *maxCounts)
12665 {   // compare according to search mode
12666     int r, f;
12667     switch(appData.searchMode)
12668     {
12669       case 1: // exact position match
12670         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12671         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12672             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12673         }
12674         break;
12675       case 2: // can have extra material on empty squares
12676         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12677             if(board[r][f] == EmptySquare) continue;
12678             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12679         }
12680         break;
12681       case 3: // material with exact Pawn structure
12682         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12683             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12684             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12685         } // fall through to material comparison
12686       case 4: // exact material
12687         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12688         break;
12689       case 6: // material range with given imbalance
12690         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12691         // fall through to range comparison
12692       case 5: // material range
12693         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12694     }
12695     return TRUE;
12696 }
12697
12698 int
12699 QuickScan (Board board, Move *move)
12700 {   // reconstruct game,and compare all positions in it
12701     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12702     do {
12703         int piece = move->piece;
12704         int to = move->to, from = pieceList[piece];
12705         if(found < 0) { // if already found just scan to game end for final piece count
12706           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12707            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12708            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12709                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12710             ) {
12711             static int lastCounts[EmptySquare+1];
12712             int i;
12713             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12714             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12715           } else stretch = 0;
12716           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12717           if(found >= 0 && !appData.minPieces) return found;
12718         }
12719         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12720           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12721           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12722             piece = (++move)->piece;
12723             from = pieceList[piece];
12724             counts[pieceType[piece]]--;
12725             pieceType[piece] = (ChessSquare) move->to;
12726             counts[move->to]++;
12727           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12728             counts[pieceType[quickBoard[to]]]--;
12729             quickBoard[to] = 0; total--;
12730             move++;
12731             continue;
12732           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12733             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12734             from  = pieceList[piece]; // so this must be King
12735             quickBoard[from] = 0;
12736             pieceList[piece] = to;
12737             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12738             quickBoard[from] = 0; // rook
12739             quickBoard[to] = piece;
12740             to = move->to; piece = move->piece;
12741             goto aftercastle;
12742           }
12743         }
12744         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12745         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12746         quickBoard[from] = 0;
12747       aftercastle:
12748         quickBoard[to] = piece;
12749         pieceList[piece] = to;
12750         cnt++; turn ^= 3;
12751         move++;
12752     } while(1);
12753 }
12754
12755 void
12756 InitSearch ()
12757 {
12758     int r, f;
12759     flipSearch = FALSE;
12760     CopyBoard(soughtBoard, boards[currentMove]);
12761     soughtTotal = MakePieceList(soughtBoard, maxSought);
12762     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12763     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12764     CopyBoard(reverseBoard, boards[currentMove]);
12765     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12766         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12767         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12768         reverseBoard[r][f] = piece;
12769     }
12770     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12771     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12772     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12773                  || (boards[currentMove][CASTLING][2] == NoRights ||
12774                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12775                  && (boards[currentMove][CASTLING][5] == NoRights ||
12776                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12777       ) {
12778         flipSearch = TRUE;
12779         CopyBoard(flipBoard, soughtBoard);
12780         CopyBoard(rotateBoard, reverseBoard);
12781         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12782             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12783             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12784         }
12785     }
12786     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12787     if(appData.searchMode >= 5) {
12788         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12789         MakePieceList(soughtBoard, minSought);
12790         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12791     }
12792     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12793         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12794 }
12795
12796 GameInfo dummyInfo;
12797 static int creatingBook;
12798
12799 int
12800 GameContainsPosition (FILE *f, ListGame *lg)
12801 {
12802     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12803     int fromX, fromY, toX, toY;
12804     char promoChar;
12805     static int initDone=FALSE;
12806
12807     // weed out games based on numerical tag comparison
12808     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12809     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12810     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12811     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12812     if(!initDone) {
12813         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12814         initDone = TRUE;
12815     }
12816     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12817     else CopyBoard(boards[scratch], initialPosition); // default start position
12818     if(lg->moves) {
12819         turn = btm + 1;
12820         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12821         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12822     }
12823     if(btm) plyNr++;
12824     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12825     fseek(f, lg->offset, 0);
12826     yynewfile(f);
12827     while(1) {
12828         yyboardindex = scratch;
12829         quickFlag = plyNr+1;
12830         next = Myylex();
12831         quickFlag = 0;
12832         switch(next) {
12833             case PGNTag:
12834                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12835             default:
12836                 continue;
12837
12838             case XBoardGame:
12839             case GNUChessGame:
12840                 if(plyNr) return -1; // after we have seen moves, this is for new game
12841               continue;
12842
12843             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12844             case ImpossibleMove:
12845             case WhiteWins: // game ends here with these four
12846             case BlackWins:
12847             case GameIsDrawn:
12848             case GameUnfinished:
12849                 return -1;
12850
12851             case IllegalMove:
12852                 if(appData.testLegality) return -1;
12853             case WhiteCapturesEnPassant:
12854             case BlackCapturesEnPassant:
12855             case WhitePromotion:
12856             case BlackPromotion:
12857             case WhiteNonPromotion:
12858             case BlackNonPromotion:
12859             case NormalMove:
12860             case FirstLeg:
12861             case WhiteKingSideCastle:
12862             case WhiteQueenSideCastle:
12863             case BlackKingSideCastle:
12864             case BlackQueenSideCastle:
12865             case WhiteKingSideCastleWild:
12866             case WhiteQueenSideCastleWild:
12867             case BlackKingSideCastleWild:
12868             case BlackQueenSideCastleWild:
12869             case WhiteHSideCastleFR:
12870             case WhiteASideCastleFR:
12871             case BlackHSideCastleFR:
12872             case BlackASideCastleFR:
12873                 fromX = currentMoveString[0] - AAA;
12874                 fromY = currentMoveString[1] - ONE;
12875                 toX = currentMoveString[2] - AAA;
12876                 toY = currentMoveString[3] - ONE;
12877                 promoChar = currentMoveString[4];
12878                 break;
12879             case WhiteDrop:
12880             case BlackDrop:
12881                 fromX = next == WhiteDrop ?
12882                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12883                   (int) CharToPiece(ToLower(currentMoveString[0]));
12884                 fromY = DROP_RANK;
12885                 toX = currentMoveString[2] - AAA;
12886                 toY = currentMoveString[3] - ONE;
12887                 promoChar = 0;
12888                 break;
12889         }
12890         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12891         plyNr++;
12892         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12893         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12894         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12895         if(appData.findMirror) {
12896             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12897             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12898         }
12899     }
12900 }
12901
12902 /* Load the nth game from open file f */
12903 int
12904 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12905 {
12906     ChessMove cm;
12907     char buf[MSG_SIZ];
12908     int gn = gameNumber;
12909     ListGame *lg = NULL;
12910     int numPGNTags = 0;
12911     int err, pos = -1;
12912     GameMode oldGameMode;
12913     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12914     char oldName[MSG_SIZ];
12915
12916     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12917
12918     if (appData.debugMode)
12919         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12920
12921     if (gameMode == Training )
12922         SetTrainingModeOff();
12923
12924     oldGameMode = gameMode;
12925     if (gameMode != BeginningOfGame) {
12926       Reset(FALSE, TRUE);
12927     }
12928     killX = killY = -1; // [HGM] lion: in case we did not Reset
12929
12930     gameFileFP = f;
12931     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12932         fclose(lastLoadGameFP);
12933     }
12934
12935     if (useList) {
12936         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12937
12938         if (lg) {
12939             fseek(f, lg->offset, 0);
12940             GameListHighlight(gameNumber);
12941             pos = lg->position;
12942             gn = 1;
12943         }
12944         else {
12945             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12946               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12947             else
12948             DisplayError(_("Game number out of range"), 0);
12949             return FALSE;
12950         }
12951     } else {
12952         GameListDestroy();
12953         if (fseek(f, 0, 0) == -1) {
12954             if (f == lastLoadGameFP ?
12955                 gameNumber == lastLoadGameNumber + 1 :
12956                 gameNumber == 1) {
12957                 gn = 1;
12958             } else {
12959                 DisplayError(_("Can't seek on game file"), 0);
12960                 return FALSE;
12961             }
12962         }
12963     }
12964     lastLoadGameFP = f;
12965     lastLoadGameNumber = gameNumber;
12966     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12967     lastLoadGameUseList = useList;
12968
12969     yynewfile(f);
12970
12971     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12972       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12973                 lg->gameInfo.black);
12974             DisplayTitle(buf);
12975     } else if (*title != NULLCHAR) {
12976         if (gameNumber > 1) {
12977           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12978             DisplayTitle(buf);
12979         } else {
12980             DisplayTitle(title);
12981         }
12982     }
12983
12984     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12985         gameMode = PlayFromGameFile;
12986         ModeHighlight();
12987     }
12988
12989     currentMove = forwardMostMove = backwardMostMove = 0;
12990     CopyBoard(boards[0], initialPosition);
12991     StopClocks();
12992
12993     /*
12994      * Skip the first gn-1 games in the file.
12995      * Also skip over anything that precedes an identifiable
12996      * start of game marker, to avoid being confused by
12997      * garbage at the start of the file.  Currently
12998      * recognized start of game markers are the move number "1",
12999      * the pattern "gnuchess .* game", the pattern
13000      * "^[#;%] [^ ]* game file", and a PGN tag block.
13001      * A game that starts with one of the latter two patterns
13002      * will also have a move number 1, possibly
13003      * following a position diagram.
13004      * 5-4-02: Let's try being more lenient and allowing a game to
13005      * start with an unnumbered move.  Does that break anything?
13006      */
13007     cm = lastLoadGameStart = EndOfFile;
13008     while (gn > 0) {
13009         yyboardindex = forwardMostMove;
13010         cm = (ChessMove) Myylex();
13011         switch (cm) {
13012           case EndOfFile:
13013             if (cmailMsgLoaded) {
13014                 nCmailGames = CMAIL_MAX_GAMES - gn;
13015             } else {
13016                 Reset(TRUE, TRUE);
13017                 DisplayError(_("Game not found in file"), 0);
13018             }
13019             return FALSE;
13020
13021           case GNUChessGame:
13022           case XBoardGame:
13023             gn--;
13024             lastLoadGameStart = cm;
13025             break;
13026
13027           case MoveNumberOne:
13028             switch (lastLoadGameStart) {
13029               case GNUChessGame:
13030               case XBoardGame:
13031               case PGNTag:
13032                 break;
13033               case MoveNumberOne:
13034               case EndOfFile:
13035                 gn--;           /* count this game */
13036                 lastLoadGameStart = cm;
13037                 break;
13038               default:
13039                 /* impossible */
13040                 break;
13041             }
13042             break;
13043
13044           case PGNTag:
13045             switch (lastLoadGameStart) {
13046               case GNUChessGame:
13047               case PGNTag:
13048               case MoveNumberOne:
13049               case EndOfFile:
13050                 gn--;           /* count this game */
13051                 lastLoadGameStart = cm;
13052                 break;
13053               case XBoardGame:
13054                 lastLoadGameStart = cm; /* game counted already */
13055                 break;
13056               default:
13057                 /* impossible */
13058                 break;
13059             }
13060             if (gn > 0) {
13061                 do {
13062                     yyboardindex = forwardMostMove;
13063                     cm = (ChessMove) Myylex();
13064                 } while (cm == PGNTag || cm == Comment);
13065             }
13066             break;
13067
13068           case WhiteWins:
13069           case BlackWins:
13070           case GameIsDrawn:
13071             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13072                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13073                     != CMAIL_OLD_RESULT) {
13074                     nCmailResults ++ ;
13075                     cmailResult[  CMAIL_MAX_GAMES
13076                                 - gn - 1] = CMAIL_OLD_RESULT;
13077                 }
13078             }
13079             break;
13080
13081           case NormalMove:
13082           case FirstLeg:
13083             /* Only a NormalMove can be at the start of a game
13084              * without a position diagram. */
13085             if (lastLoadGameStart == EndOfFile ) {
13086               gn--;
13087               lastLoadGameStart = MoveNumberOne;
13088             }
13089             break;
13090
13091           default:
13092             break;
13093         }
13094     }
13095
13096     if (appData.debugMode)
13097       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13098
13099     if (cm == XBoardGame) {
13100         /* Skip any header junk before position diagram and/or move 1 */
13101         for (;;) {
13102             yyboardindex = forwardMostMove;
13103             cm = (ChessMove) Myylex();
13104
13105             if (cm == EndOfFile ||
13106                 cm == GNUChessGame || cm == XBoardGame) {
13107                 /* Empty game; pretend end-of-file and handle later */
13108                 cm = EndOfFile;
13109                 break;
13110             }
13111
13112             if (cm == MoveNumberOne || cm == PositionDiagram ||
13113                 cm == PGNTag || cm == Comment)
13114               break;
13115         }
13116     } else if (cm == GNUChessGame) {
13117         if (gameInfo.event != NULL) {
13118             free(gameInfo.event);
13119         }
13120         gameInfo.event = StrSave(yy_text);
13121     }
13122
13123     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13124     while (cm == PGNTag) {
13125         if (appData.debugMode)
13126           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13127         err = ParsePGNTag(yy_text, &gameInfo);
13128         if (!err) numPGNTags++;
13129
13130         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13131         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13132             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13133             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13134             InitPosition(TRUE);
13135             oldVariant = gameInfo.variant;
13136             if (appData.debugMode)
13137               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13138         }
13139
13140
13141         if (gameInfo.fen != NULL) {
13142           Board initial_position;
13143           startedFromSetupPosition = TRUE;
13144           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13145             Reset(TRUE, TRUE);
13146             DisplayError(_("Bad FEN position in file"), 0);
13147             return FALSE;
13148           }
13149           CopyBoard(boards[0], initial_position);
13150           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13151             CopyBoard(initialPosition, initial_position);
13152           if (blackPlaysFirst) {
13153             currentMove = forwardMostMove = backwardMostMove = 1;
13154             CopyBoard(boards[1], initial_position);
13155             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13156             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13157             timeRemaining[0][1] = whiteTimeRemaining;
13158             timeRemaining[1][1] = blackTimeRemaining;
13159             if (commentList[0] != NULL) {
13160               commentList[1] = commentList[0];
13161               commentList[0] = NULL;
13162             }
13163           } else {
13164             currentMove = forwardMostMove = backwardMostMove = 0;
13165           }
13166           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13167           {   int i;
13168               initialRulePlies = FENrulePlies;
13169               for( i=0; i< nrCastlingRights; i++ )
13170                   initialRights[i] = initial_position[CASTLING][i];
13171           }
13172           yyboardindex = forwardMostMove;
13173           free(gameInfo.fen);
13174           gameInfo.fen = NULL;
13175         }
13176
13177         yyboardindex = forwardMostMove;
13178         cm = (ChessMove) Myylex();
13179
13180         /* Handle comments interspersed among the tags */
13181         while (cm == Comment) {
13182             char *p;
13183             if (appData.debugMode)
13184               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13185             p = yy_text;
13186             AppendComment(currentMove, p, FALSE);
13187             yyboardindex = forwardMostMove;
13188             cm = (ChessMove) Myylex();
13189         }
13190     }
13191
13192     /* don't rely on existence of Event tag since if game was
13193      * pasted from clipboard the Event tag may not exist
13194      */
13195     if (numPGNTags > 0){
13196         char *tags;
13197         if (gameInfo.variant == VariantNormal) {
13198           VariantClass v = StringToVariant(gameInfo.event);
13199           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13200           if(v < VariantShogi) gameInfo.variant = v;
13201         }
13202         if (!matchMode) {
13203           if( appData.autoDisplayTags ) {
13204             tags = PGNTags(&gameInfo);
13205             TagsPopUp(tags, CmailMsg());
13206             free(tags);
13207           }
13208         }
13209     } else {
13210         /* Make something up, but don't display it now */
13211         SetGameInfo();
13212         TagsPopDown();
13213     }
13214
13215     if (cm == PositionDiagram) {
13216         int i, j;
13217         char *p;
13218         Board initial_position;
13219
13220         if (appData.debugMode)
13221           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13222
13223         if (!startedFromSetupPosition) {
13224             p = yy_text;
13225             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13226               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13227                 switch (*p) {
13228                   case '{':
13229                   case '[':
13230                   case '-':
13231                   case ' ':
13232                   case '\t':
13233                   case '\n':
13234                   case '\r':
13235                     break;
13236                   default:
13237                     initial_position[i][j++] = CharToPiece(*p);
13238                     break;
13239                 }
13240             while (*p == ' ' || *p == '\t' ||
13241                    *p == '\n' || *p == '\r') p++;
13242
13243             if (strncmp(p, "black", strlen("black"))==0)
13244               blackPlaysFirst = TRUE;
13245             else
13246               blackPlaysFirst = FALSE;
13247             startedFromSetupPosition = TRUE;
13248
13249             CopyBoard(boards[0], initial_position);
13250             if (blackPlaysFirst) {
13251                 currentMove = forwardMostMove = backwardMostMove = 1;
13252                 CopyBoard(boards[1], initial_position);
13253                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13254                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13255                 timeRemaining[0][1] = whiteTimeRemaining;
13256                 timeRemaining[1][1] = blackTimeRemaining;
13257                 if (commentList[0] != NULL) {
13258                     commentList[1] = commentList[0];
13259                     commentList[0] = NULL;
13260                 }
13261             } else {
13262                 currentMove = forwardMostMove = backwardMostMove = 0;
13263             }
13264         }
13265         yyboardindex = forwardMostMove;
13266         cm = (ChessMove) Myylex();
13267     }
13268
13269   if(!creatingBook) {
13270     if (first.pr == NoProc) {
13271         StartChessProgram(&first);
13272     }
13273     InitChessProgram(&first, FALSE);
13274     if(gameInfo.variant == VariantUnknown && *oldName) {
13275         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13276         gameInfo.variant = v;
13277     }
13278     SendToProgram("force\n", &first);
13279     if (startedFromSetupPosition) {
13280         SendBoard(&first, forwardMostMove);
13281     if (appData.debugMode) {
13282         fprintf(debugFP, "Load Game\n");
13283     }
13284         DisplayBothClocks();
13285     }
13286   }
13287
13288     /* [HGM] server: flag to write setup moves in broadcast file as one */
13289     loadFlag = appData.suppressLoadMoves;
13290
13291     while (cm == Comment) {
13292         char *p;
13293         if (appData.debugMode)
13294           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13295         p = yy_text;
13296         AppendComment(currentMove, p, FALSE);
13297         yyboardindex = forwardMostMove;
13298         cm = (ChessMove) Myylex();
13299     }
13300
13301     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13302         cm == WhiteWins || cm == BlackWins ||
13303         cm == GameIsDrawn || cm == GameUnfinished) {
13304         DisplayMessage("", _("No moves in game"));
13305         if (cmailMsgLoaded) {
13306             if (appData.debugMode)
13307               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13308             ClearHighlights();
13309             flipView = FALSE;
13310         }
13311         DrawPosition(FALSE, boards[currentMove]);
13312         DisplayBothClocks();
13313         gameMode = EditGame;
13314         ModeHighlight();
13315         gameFileFP = NULL;
13316         cmailOldMove = 0;
13317         return TRUE;
13318     }
13319
13320     // [HGM] PV info: routine tests if comment empty
13321     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13322         DisplayComment(currentMove - 1, commentList[currentMove]);
13323     }
13324     if (!matchMode && appData.timeDelay != 0)
13325       DrawPosition(FALSE, boards[currentMove]);
13326
13327     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13328       programStats.ok_to_send = 1;
13329     }
13330
13331     /* if the first token after the PGN tags is a move
13332      * and not move number 1, retrieve it from the parser
13333      */
13334     if (cm != MoveNumberOne)
13335         LoadGameOneMove(cm);
13336
13337     /* load the remaining moves from the file */
13338     while (LoadGameOneMove(EndOfFile)) {
13339       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13340       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13341     }
13342
13343     /* rewind to the start of the game */
13344     currentMove = backwardMostMove;
13345
13346     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13347
13348     if (oldGameMode == AnalyzeFile) {
13349       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13350       AnalyzeFileEvent();
13351     } else
13352     if (oldGameMode == AnalyzeMode) {
13353       AnalyzeFileEvent();
13354     }
13355
13356     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13357         long int w, b; // [HGM] adjourn: restore saved clock times
13358         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13359         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13360             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13361             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13362         }
13363     }
13364
13365     if(creatingBook) return TRUE;
13366     if (!matchMode && pos > 0) {
13367         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13368     } else
13369     if (matchMode || appData.timeDelay == 0) {
13370       ToEndEvent();
13371     } else if (appData.timeDelay > 0) {
13372       AutoPlayGameLoop();
13373     }
13374
13375     if (appData.debugMode)
13376         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13377
13378     loadFlag = 0; /* [HGM] true game starts */
13379     return TRUE;
13380 }
13381
13382 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13383 int
13384 ReloadPosition (int offset)
13385 {
13386     int positionNumber = lastLoadPositionNumber + offset;
13387     if (lastLoadPositionFP == NULL) {
13388         DisplayError(_("No position has been loaded yet"), 0);
13389         return FALSE;
13390     }
13391     if (positionNumber <= 0) {
13392         DisplayError(_("Can't back up any further"), 0);
13393         return FALSE;
13394     }
13395     return LoadPosition(lastLoadPositionFP, positionNumber,
13396                         lastLoadPositionTitle);
13397 }
13398
13399 /* Load the nth position from the given file */
13400 int
13401 LoadPositionFromFile (char *filename, int n, char *title)
13402 {
13403     FILE *f;
13404     char buf[MSG_SIZ];
13405
13406     if (strcmp(filename, "-") == 0) {
13407         return LoadPosition(stdin, n, "stdin");
13408     } else {
13409         f = fopen(filename, "rb");
13410         if (f == NULL) {
13411             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13412             DisplayError(buf, errno);
13413             return FALSE;
13414         } else {
13415             return LoadPosition(f, n, title);
13416         }
13417     }
13418 }
13419
13420 /* Load the nth position from the given open file, and close it */
13421 int
13422 LoadPosition (FILE *f, int positionNumber, char *title)
13423 {
13424     char *p, line[MSG_SIZ];
13425     Board initial_position;
13426     int i, j, fenMode, pn;
13427
13428     if (gameMode == Training )
13429         SetTrainingModeOff();
13430
13431     if (gameMode != BeginningOfGame) {
13432         Reset(FALSE, TRUE);
13433     }
13434     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13435         fclose(lastLoadPositionFP);
13436     }
13437     if (positionNumber == 0) positionNumber = 1;
13438     lastLoadPositionFP = f;
13439     lastLoadPositionNumber = positionNumber;
13440     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13441     if (first.pr == NoProc && !appData.noChessProgram) {
13442       StartChessProgram(&first);
13443       InitChessProgram(&first, FALSE);
13444     }
13445     pn = positionNumber;
13446     if (positionNumber < 0) {
13447         /* Negative position number means to seek to that byte offset */
13448         if (fseek(f, -positionNumber, 0) == -1) {
13449             DisplayError(_("Can't seek on position file"), 0);
13450             return FALSE;
13451         };
13452         pn = 1;
13453     } else {
13454         if (fseek(f, 0, 0) == -1) {
13455             if (f == lastLoadPositionFP ?
13456                 positionNumber == lastLoadPositionNumber + 1 :
13457                 positionNumber == 1) {
13458                 pn = 1;
13459             } else {
13460                 DisplayError(_("Can't seek on position file"), 0);
13461                 return FALSE;
13462             }
13463         }
13464     }
13465     /* See if this file is FEN or old-style xboard */
13466     if (fgets(line, MSG_SIZ, f) == NULL) {
13467         DisplayError(_("Position not found in file"), 0);
13468         return FALSE;
13469     }
13470     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13471     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13472
13473     if (pn >= 2) {
13474         if (fenMode || line[0] == '#') pn--;
13475         while (pn > 0) {
13476             /* skip positions before number pn */
13477             if (fgets(line, MSG_SIZ, f) == NULL) {
13478                 Reset(TRUE, TRUE);
13479                 DisplayError(_("Position not found in file"), 0);
13480                 return FALSE;
13481             }
13482             if (fenMode || line[0] == '#') pn--;
13483         }
13484     }
13485
13486     if (fenMode) {
13487         char *p;
13488         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13489             DisplayError(_("Bad FEN position in file"), 0);
13490             return FALSE;
13491         }
13492         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13493             sscanf(p+3, "%s", bestMove);
13494         } else *bestMove = NULLCHAR;
13495     } else {
13496         (void) fgets(line, MSG_SIZ, f);
13497         (void) fgets(line, MSG_SIZ, f);
13498
13499         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13500             (void) fgets(line, MSG_SIZ, f);
13501             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13502                 if (*p == ' ')
13503                   continue;
13504                 initial_position[i][j++] = CharToPiece(*p);
13505             }
13506         }
13507
13508         blackPlaysFirst = FALSE;
13509         if (!feof(f)) {
13510             (void) fgets(line, MSG_SIZ, f);
13511             if (strncmp(line, "black", strlen("black"))==0)
13512               blackPlaysFirst = TRUE;
13513         }
13514     }
13515     startedFromSetupPosition = TRUE;
13516
13517     CopyBoard(boards[0], initial_position);
13518     if (blackPlaysFirst) {
13519         currentMove = forwardMostMove = backwardMostMove = 1;
13520         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13521         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13522         CopyBoard(boards[1], initial_position);
13523         DisplayMessage("", _("Black to play"));
13524     } else {
13525         currentMove = forwardMostMove = backwardMostMove = 0;
13526         DisplayMessage("", _("White to play"));
13527     }
13528     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13529     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13530         SendToProgram("force\n", &first);
13531         SendBoard(&first, forwardMostMove);
13532     }
13533     if (appData.debugMode) {
13534 int i, j;
13535   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13536   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13537         fprintf(debugFP, "Load Position\n");
13538     }
13539
13540     if (positionNumber > 1) {
13541       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13542         DisplayTitle(line);
13543     } else {
13544         DisplayTitle(title);
13545     }
13546     gameMode = EditGame;
13547     ModeHighlight();
13548     ResetClocks();
13549     timeRemaining[0][1] = whiteTimeRemaining;
13550     timeRemaining[1][1] = blackTimeRemaining;
13551     DrawPosition(FALSE, boards[currentMove]);
13552
13553     return TRUE;
13554 }
13555
13556
13557 void
13558 CopyPlayerNameIntoFileName (char **dest, char *src)
13559 {
13560     while (*src != NULLCHAR && *src != ',') {
13561         if (*src == ' ') {
13562             *(*dest)++ = '_';
13563             src++;
13564         } else {
13565             *(*dest)++ = *src++;
13566         }
13567     }
13568 }
13569
13570 char *
13571 DefaultFileName (char *ext)
13572 {
13573     static char def[MSG_SIZ];
13574     char *p;
13575
13576     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13577         p = def;
13578         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13579         *p++ = '-';
13580         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13581         *p++ = '.';
13582         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13583     } else {
13584         def[0] = NULLCHAR;
13585     }
13586     return def;
13587 }
13588
13589 /* Save the current game to the given file */
13590 int
13591 SaveGameToFile (char *filename, int append)
13592 {
13593     FILE *f;
13594     char buf[MSG_SIZ];
13595     int result, i, t,tot=0;
13596
13597     if (strcmp(filename, "-") == 0) {
13598         return SaveGame(stdout, 0, NULL);
13599     } else {
13600         for(i=0; i<10; i++) { // upto 10 tries
13601              f = fopen(filename, append ? "a" : "w");
13602              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13603              if(f || errno != 13) break;
13604              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13605              tot += t;
13606         }
13607         if (f == NULL) {
13608             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13609             DisplayError(buf, errno);
13610             return FALSE;
13611         } else {
13612             safeStrCpy(buf, lastMsg, MSG_SIZ);
13613             DisplayMessage(_("Waiting for access to save file"), "");
13614             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13615             DisplayMessage(_("Saving game"), "");
13616             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13617             result = SaveGame(f, 0, NULL);
13618             DisplayMessage(buf, "");
13619             return result;
13620         }
13621     }
13622 }
13623
13624 char *
13625 SavePart (char *str)
13626 {
13627     static char buf[MSG_SIZ];
13628     char *p;
13629
13630     p = strchr(str, ' ');
13631     if (p == NULL) return str;
13632     strncpy(buf, str, p - str);
13633     buf[p - str] = NULLCHAR;
13634     return buf;
13635 }
13636
13637 #define PGN_MAX_LINE 75
13638
13639 #define PGN_SIDE_WHITE  0
13640 #define PGN_SIDE_BLACK  1
13641
13642 static int
13643 FindFirstMoveOutOfBook (int side)
13644 {
13645     int result = -1;
13646
13647     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13648         int index = backwardMostMove;
13649         int has_book_hit = 0;
13650
13651         if( (index % 2) != side ) {
13652             index++;
13653         }
13654
13655         while( index < forwardMostMove ) {
13656             /* Check to see if engine is in book */
13657             int depth = pvInfoList[index].depth;
13658             int score = pvInfoList[index].score;
13659             int in_book = 0;
13660
13661             if( depth <= 2 ) {
13662                 in_book = 1;
13663             }
13664             else if( score == 0 && depth == 63 ) {
13665                 in_book = 1; /* Zappa */
13666             }
13667             else if( score == 2 && depth == 99 ) {
13668                 in_book = 1; /* Abrok */
13669             }
13670
13671             has_book_hit += in_book;
13672
13673             if( ! in_book ) {
13674                 result = index;
13675
13676                 break;
13677             }
13678
13679             index += 2;
13680         }
13681     }
13682
13683     return result;
13684 }
13685
13686 void
13687 GetOutOfBookInfo (char * buf)
13688 {
13689     int oob[2];
13690     int i;
13691     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13692
13693     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13694     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13695
13696     *buf = '\0';
13697
13698     if( oob[0] >= 0 || oob[1] >= 0 ) {
13699         for( i=0; i<2; i++ ) {
13700             int idx = oob[i];
13701
13702             if( idx >= 0 ) {
13703                 if( i > 0 && oob[0] >= 0 ) {
13704                     strcat( buf, "   " );
13705                 }
13706
13707                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13708                 sprintf( buf+strlen(buf), "%s%.2f",
13709                     pvInfoList[idx].score >= 0 ? "+" : "",
13710                     pvInfoList[idx].score / 100.0 );
13711             }
13712         }
13713     }
13714 }
13715
13716 /* Save game in PGN style */
13717 static void
13718 SaveGamePGN2 (FILE *f)
13719 {
13720     int i, offset, linelen, newblock;
13721 //    char *movetext;
13722     char numtext[32];
13723     int movelen, numlen, blank;
13724     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13725
13726     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13727
13728     PrintPGNTags(f, &gameInfo);
13729
13730     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13731
13732     if (backwardMostMove > 0 || startedFromSetupPosition) {
13733         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13734         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13735         fprintf(f, "\n{--------------\n");
13736         PrintPosition(f, backwardMostMove);
13737         fprintf(f, "--------------}\n");
13738         free(fen);
13739     }
13740     else {
13741         /* [AS] Out of book annotation */
13742         if( appData.saveOutOfBookInfo ) {
13743             char buf[64];
13744
13745             GetOutOfBookInfo( buf );
13746
13747             if( buf[0] != '\0' ) {
13748                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13749             }
13750         }
13751
13752         fprintf(f, "\n");
13753     }
13754
13755     i = backwardMostMove;
13756     linelen = 0;
13757     newblock = TRUE;
13758
13759     while (i < forwardMostMove) {
13760         /* Print comments preceding this move */
13761         if (commentList[i] != NULL) {
13762             if (linelen > 0) fprintf(f, "\n");
13763             fprintf(f, "%s", commentList[i]);
13764             linelen = 0;
13765             newblock = TRUE;
13766         }
13767
13768         /* Format move number */
13769         if ((i % 2) == 0)
13770           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13771         else
13772           if (newblock)
13773             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13774           else
13775             numtext[0] = NULLCHAR;
13776
13777         numlen = strlen(numtext);
13778         newblock = FALSE;
13779
13780         /* Print move number */
13781         blank = linelen > 0 && numlen > 0;
13782         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13783             fprintf(f, "\n");
13784             linelen = 0;
13785             blank = 0;
13786         }
13787         if (blank) {
13788             fprintf(f, " ");
13789             linelen++;
13790         }
13791         fprintf(f, "%s", numtext);
13792         linelen += numlen;
13793
13794         /* Get move */
13795         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13796         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13797
13798         /* Print move */
13799         blank = linelen > 0 && movelen > 0;
13800         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13801             fprintf(f, "\n");
13802             linelen = 0;
13803             blank = 0;
13804         }
13805         if (blank) {
13806             fprintf(f, " ");
13807             linelen++;
13808         }
13809         fprintf(f, "%s", move_buffer);
13810         linelen += movelen;
13811
13812         /* [AS] Add PV info if present */
13813         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13814             /* [HGM] add time */
13815             char buf[MSG_SIZ]; int seconds;
13816
13817             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13818
13819             if( seconds <= 0)
13820               buf[0] = 0;
13821             else
13822               if( seconds < 30 )
13823                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13824               else
13825                 {
13826                   seconds = (seconds + 4)/10; // round to full seconds
13827                   if( seconds < 60 )
13828                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13829                   else
13830                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13831                 }
13832
13833             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13834                       pvInfoList[i].score >= 0 ? "+" : "",
13835                       pvInfoList[i].score / 100.0,
13836                       pvInfoList[i].depth,
13837                       buf );
13838
13839             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13840
13841             /* Print score/depth */
13842             blank = linelen > 0 && movelen > 0;
13843             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13844                 fprintf(f, "\n");
13845                 linelen = 0;
13846                 blank = 0;
13847             }
13848             if (blank) {
13849                 fprintf(f, " ");
13850                 linelen++;
13851             }
13852             fprintf(f, "%s", move_buffer);
13853             linelen += movelen;
13854         }
13855
13856         i++;
13857     }
13858
13859     /* Start a new line */
13860     if (linelen > 0) fprintf(f, "\n");
13861
13862     /* Print comments after last move */
13863     if (commentList[i] != NULL) {
13864         fprintf(f, "%s\n", commentList[i]);
13865     }
13866
13867     /* Print result */
13868     if (gameInfo.resultDetails != NULL &&
13869         gameInfo.resultDetails[0] != NULLCHAR) {
13870         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13871         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13872            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13873             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13874         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13875     } else {
13876         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13877     }
13878 }
13879
13880 /* Save game in PGN style and close the file */
13881 int
13882 SaveGamePGN (FILE *f)
13883 {
13884     SaveGamePGN2(f);
13885     fclose(f);
13886     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13887     return TRUE;
13888 }
13889
13890 /* Save game in old style and close the file */
13891 int
13892 SaveGameOldStyle (FILE *f)
13893 {
13894     int i, offset;
13895     time_t tm;
13896
13897     tm = time((time_t *) NULL);
13898
13899     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13900     PrintOpponents(f);
13901
13902     if (backwardMostMove > 0 || startedFromSetupPosition) {
13903         fprintf(f, "\n[--------------\n");
13904         PrintPosition(f, backwardMostMove);
13905         fprintf(f, "--------------]\n");
13906     } else {
13907         fprintf(f, "\n");
13908     }
13909
13910     i = backwardMostMove;
13911     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13912
13913     while (i < forwardMostMove) {
13914         if (commentList[i] != NULL) {
13915             fprintf(f, "[%s]\n", commentList[i]);
13916         }
13917
13918         if ((i % 2) == 1) {
13919             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13920             i++;
13921         } else {
13922             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13923             i++;
13924             if (commentList[i] != NULL) {
13925                 fprintf(f, "\n");
13926                 continue;
13927             }
13928             if (i >= forwardMostMove) {
13929                 fprintf(f, "\n");
13930                 break;
13931             }
13932             fprintf(f, "%s\n", parseList[i]);
13933             i++;
13934         }
13935     }
13936
13937     if (commentList[i] != NULL) {
13938         fprintf(f, "[%s]\n", commentList[i]);
13939     }
13940
13941     /* This isn't really the old style, but it's close enough */
13942     if (gameInfo.resultDetails != NULL &&
13943         gameInfo.resultDetails[0] != NULLCHAR) {
13944         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13945                 gameInfo.resultDetails);
13946     } else {
13947         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13948     }
13949
13950     fclose(f);
13951     return TRUE;
13952 }
13953
13954 /* Save the current game to open file f and close the file */
13955 int
13956 SaveGame (FILE *f, int dummy, char *dummy2)
13957 {
13958     if (gameMode == EditPosition) EditPositionDone(TRUE);
13959     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13960     if (appData.oldSaveStyle)
13961       return SaveGameOldStyle(f);
13962     else
13963       return SaveGamePGN(f);
13964 }
13965
13966 /* Save the current position to the given file */
13967 int
13968 SavePositionToFile (char *filename)
13969 {
13970     FILE *f;
13971     char buf[MSG_SIZ];
13972
13973     if (strcmp(filename, "-") == 0) {
13974         return SavePosition(stdout, 0, NULL);
13975     } else {
13976         f = fopen(filename, "a");
13977         if (f == NULL) {
13978             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13979             DisplayError(buf, errno);
13980             return FALSE;
13981         } else {
13982             safeStrCpy(buf, lastMsg, MSG_SIZ);
13983             DisplayMessage(_("Waiting for access to save file"), "");
13984             flock(fileno(f), LOCK_EX); // [HGM] lock
13985             DisplayMessage(_("Saving position"), "");
13986             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13987             SavePosition(f, 0, NULL);
13988             DisplayMessage(buf, "");
13989             return TRUE;
13990         }
13991     }
13992 }
13993
13994 /* Save the current position to the given open file and close the file */
13995 int
13996 SavePosition (FILE *f, int dummy, char *dummy2)
13997 {
13998     time_t tm;
13999     char *fen;
14000
14001     if (gameMode == EditPosition) EditPositionDone(TRUE);
14002     if (appData.oldSaveStyle) {
14003         tm = time((time_t *) NULL);
14004
14005         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14006         PrintOpponents(f);
14007         fprintf(f, "[--------------\n");
14008         PrintPosition(f, currentMove);
14009         fprintf(f, "--------------]\n");
14010     } else {
14011         fen = PositionToFEN(currentMove, NULL, 1);
14012         fprintf(f, "%s\n", fen);
14013         free(fen);
14014     }
14015     fclose(f);
14016     return TRUE;
14017 }
14018
14019 void
14020 ReloadCmailMsgEvent (int unregister)
14021 {
14022 #if !WIN32
14023     static char *inFilename = NULL;
14024     static char *outFilename;
14025     int i;
14026     struct stat inbuf, outbuf;
14027     int status;
14028
14029     /* Any registered moves are unregistered if unregister is set, */
14030     /* i.e. invoked by the signal handler */
14031     if (unregister) {
14032         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14033             cmailMoveRegistered[i] = FALSE;
14034             if (cmailCommentList[i] != NULL) {
14035                 free(cmailCommentList[i]);
14036                 cmailCommentList[i] = NULL;
14037             }
14038         }
14039         nCmailMovesRegistered = 0;
14040     }
14041
14042     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14043         cmailResult[i] = CMAIL_NOT_RESULT;
14044     }
14045     nCmailResults = 0;
14046
14047     if (inFilename == NULL) {
14048         /* Because the filenames are static they only get malloced once  */
14049         /* and they never get freed                                      */
14050         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14051         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14052
14053         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14054         sprintf(outFilename, "%s.out", appData.cmailGameName);
14055     }
14056
14057     status = stat(outFilename, &outbuf);
14058     if (status < 0) {
14059         cmailMailedMove = FALSE;
14060     } else {
14061         status = stat(inFilename, &inbuf);
14062         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14063     }
14064
14065     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14066        counts the games, notes how each one terminated, etc.
14067
14068        It would be nice to remove this kludge and instead gather all
14069        the information while building the game list.  (And to keep it
14070        in the game list nodes instead of having a bunch of fixed-size
14071        parallel arrays.)  Note this will require getting each game's
14072        termination from the PGN tags, as the game list builder does
14073        not process the game moves.  --mann
14074        */
14075     cmailMsgLoaded = TRUE;
14076     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14077
14078     /* Load first game in the file or popup game menu */
14079     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14080
14081 #endif /* !WIN32 */
14082     return;
14083 }
14084
14085 int
14086 RegisterMove ()
14087 {
14088     FILE *f;
14089     char string[MSG_SIZ];
14090
14091     if (   cmailMailedMove
14092         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14093         return TRUE;            /* Allow free viewing  */
14094     }
14095
14096     /* Unregister move to ensure that we don't leave RegisterMove        */
14097     /* with the move registered when the conditions for registering no   */
14098     /* longer hold                                                       */
14099     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14100         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14101         nCmailMovesRegistered --;
14102
14103         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14104           {
14105               free(cmailCommentList[lastLoadGameNumber - 1]);
14106               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14107           }
14108     }
14109
14110     if (cmailOldMove == -1) {
14111         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14112         return FALSE;
14113     }
14114
14115     if (currentMove > cmailOldMove + 1) {
14116         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14117         return FALSE;
14118     }
14119
14120     if (currentMove < cmailOldMove) {
14121         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14122         return FALSE;
14123     }
14124
14125     if (forwardMostMove > currentMove) {
14126         /* Silently truncate extra moves */
14127         TruncateGame();
14128     }
14129
14130     if (   (currentMove == cmailOldMove + 1)
14131         || (   (currentMove == cmailOldMove)
14132             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14133                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14134         if (gameInfo.result != GameUnfinished) {
14135             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14136         }
14137
14138         if (commentList[currentMove] != NULL) {
14139             cmailCommentList[lastLoadGameNumber - 1]
14140               = StrSave(commentList[currentMove]);
14141         }
14142         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14143
14144         if (appData.debugMode)
14145           fprintf(debugFP, "Saving %s for game %d\n",
14146                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14147
14148         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14149
14150         f = fopen(string, "w");
14151         if (appData.oldSaveStyle) {
14152             SaveGameOldStyle(f); /* also closes the file */
14153
14154             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14155             f = fopen(string, "w");
14156             SavePosition(f, 0, NULL); /* also closes the file */
14157         } else {
14158             fprintf(f, "{--------------\n");
14159             PrintPosition(f, currentMove);
14160             fprintf(f, "--------------}\n\n");
14161
14162             SaveGame(f, 0, NULL); /* also closes the file*/
14163         }
14164
14165         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14166         nCmailMovesRegistered ++;
14167     } else if (nCmailGames == 1) {
14168         DisplayError(_("You have not made a move yet"), 0);
14169         return FALSE;
14170     }
14171
14172     return TRUE;
14173 }
14174
14175 void
14176 MailMoveEvent ()
14177 {
14178 #if !WIN32
14179     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14180     FILE *commandOutput;
14181     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14182     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14183     int nBuffers;
14184     int i;
14185     int archived;
14186     char *arcDir;
14187
14188     if (! cmailMsgLoaded) {
14189         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14190         return;
14191     }
14192
14193     if (nCmailGames == nCmailResults) {
14194         DisplayError(_("No unfinished games"), 0);
14195         return;
14196     }
14197
14198 #if CMAIL_PROHIBIT_REMAIL
14199     if (cmailMailedMove) {
14200       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);
14201         DisplayError(msg, 0);
14202         return;
14203     }
14204 #endif
14205
14206     if (! (cmailMailedMove || RegisterMove())) return;
14207
14208     if (   cmailMailedMove
14209         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14210       snprintf(string, MSG_SIZ, partCommandString,
14211                appData.debugMode ? " -v" : "", appData.cmailGameName);
14212         commandOutput = popen(string, "r");
14213
14214         if (commandOutput == NULL) {
14215             DisplayError(_("Failed to invoke cmail"), 0);
14216         } else {
14217             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14218                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14219             }
14220             if (nBuffers > 1) {
14221                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14222                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14223                 nBytes = MSG_SIZ - 1;
14224             } else {
14225                 (void) memcpy(msg, buffer, nBytes);
14226             }
14227             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14228
14229             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14230                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14231
14232                 archived = TRUE;
14233                 for (i = 0; i < nCmailGames; i ++) {
14234                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14235                         archived = FALSE;
14236                     }
14237                 }
14238                 if (   archived
14239                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14240                         != NULL)) {
14241                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14242                            arcDir,
14243                            appData.cmailGameName,
14244                            gameInfo.date);
14245                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14246                     cmailMsgLoaded = FALSE;
14247                 }
14248             }
14249
14250             DisplayInformation(msg);
14251             pclose(commandOutput);
14252         }
14253     } else {
14254         if ((*cmailMsg) != '\0') {
14255             DisplayInformation(cmailMsg);
14256         }
14257     }
14258
14259     return;
14260 #endif /* !WIN32 */
14261 }
14262
14263 char *
14264 CmailMsg ()
14265 {
14266 #if WIN32
14267     return NULL;
14268 #else
14269     int  prependComma = 0;
14270     char number[5];
14271     char string[MSG_SIZ];       /* Space for game-list */
14272     int  i;
14273
14274     if (!cmailMsgLoaded) return "";
14275
14276     if (cmailMailedMove) {
14277       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14278     } else {
14279         /* Create a list of games left */
14280       snprintf(string, MSG_SIZ, "[");
14281         for (i = 0; i < nCmailGames; i ++) {
14282             if (! (   cmailMoveRegistered[i]
14283                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14284                 if (prependComma) {
14285                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14286                 } else {
14287                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14288                     prependComma = 1;
14289                 }
14290
14291                 strcat(string, number);
14292             }
14293         }
14294         strcat(string, "]");
14295
14296         if (nCmailMovesRegistered + nCmailResults == 0) {
14297             switch (nCmailGames) {
14298               case 1:
14299                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14300                 break;
14301
14302               case 2:
14303                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14304                 break;
14305
14306               default:
14307                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14308                          nCmailGames);
14309                 break;
14310             }
14311         } else {
14312             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14313               case 1:
14314                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14315                          string);
14316                 break;
14317
14318               case 0:
14319                 if (nCmailResults == nCmailGames) {
14320                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14321                 } else {
14322                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14323                 }
14324                 break;
14325
14326               default:
14327                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14328                          string);
14329             }
14330         }
14331     }
14332     return cmailMsg;
14333 #endif /* WIN32 */
14334 }
14335
14336 void
14337 ResetGameEvent ()
14338 {
14339     if (gameMode == Training)
14340       SetTrainingModeOff();
14341
14342     Reset(TRUE, TRUE);
14343     cmailMsgLoaded = FALSE;
14344     if (appData.icsActive) {
14345       SendToICS(ics_prefix);
14346       SendToICS("refresh\n");
14347     }
14348 }
14349
14350 void
14351 ExitEvent (int status)
14352 {
14353     exiting++;
14354     if (exiting > 2) {
14355       /* Give up on clean exit */
14356       exit(status);
14357     }
14358     if (exiting > 1) {
14359       /* Keep trying for clean exit */
14360       return;
14361     }
14362
14363     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14364     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14365
14366     if (telnetISR != NULL) {
14367       RemoveInputSource(telnetISR);
14368     }
14369     if (icsPR != NoProc) {
14370       DestroyChildProcess(icsPR, TRUE);
14371     }
14372
14373     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14374     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14375
14376     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14377     /* make sure this other one finishes before killing it!                  */
14378     if(endingGame) { int count = 0;
14379         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14380         while(endingGame && count++ < 10) DoSleep(1);
14381         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14382     }
14383
14384     /* Kill off chess programs */
14385     if (first.pr != NoProc) {
14386         ExitAnalyzeMode();
14387
14388         DoSleep( appData.delayBeforeQuit );
14389         SendToProgram("quit\n", &first);
14390         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14391     }
14392     if (second.pr != NoProc) {
14393         DoSleep( appData.delayBeforeQuit );
14394         SendToProgram("quit\n", &second);
14395         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14396     }
14397     if (first.isr != NULL) {
14398         RemoveInputSource(first.isr);
14399     }
14400     if (second.isr != NULL) {
14401         RemoveInputSource(second.isr);
14402     }
14403
14404     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14405     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14406
14407     ShutDownFrontEnd();
14408     exit(status);
14409 }
14410
14411 void
14412 PauseEngine (ChessProgramState *cps)
14413 {
14414     SendToProgram("pause\n", cps);
14415     cps->pause = 2;
14416 }
14417
14418 void
14419 UnPauseEngine (ChessProgramState *cps)
14420 {
14421     SendToProgram("resume\n", cps);
14422     cps->pause = 1;
14423 }
14424
14425 void
14426 PauseEvent ()
14427 {
14428     if (appData.debugMode)
14429         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14430     if (pausing) {
14431         pausing = FALSE;
14432         ModeHighlight();
14433         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14434             StartClocks();
14435             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14436                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14437                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14438             }
14439             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14440             HandleMachineMove(stashedInputMove, stalledEngine);
14441             stalledEngine = NULL;
14442             return;
14443         }
14444         if (gameMode == MachinePlaysWhite ||
14445             gameMode == TwoMachinesPlay   ||
14446             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14447             if(first.pause)  UnPauseEngine(&first);
14448             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14449             if(second.pause) UnPauseEngine(&second);
14450             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14451             StartClocks();
14452         } else {
14453             DisplayBothClocks();
14454         }
14455         if (gameMode == PlayFromGameFile) {
14456             if (appData.timeDelay >= 0)
14457                 AutoPlayGameLoop();
14458         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14459             Reset(FALSE, TRUE);
14460             SendToICS(ics_prefix);
14461             SendToICS("refresh\n");
14462         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14463             ForwardInner(forwardMostMove);
14464         }
14465         pauseExamInvalid = FALSE;
14466     } else {
14467         switch (gameMode) {
14468           default:
14469             return;
14470           case IcsExamining:
14471             pauseExamForwardMostMove = forwardMostMove;
14472             pauseExamInvalid = FALSE;
14473             /* fall through */
14474           case IcsObserving:
14475           case IcsPlayingWhite:
14476           case IcsPlayingBlack:
14477             pausing = TRUE;
14478             ModeHighlight();
14479             return;
14480           case PlayFromGameFile:
14481             (void) StopLoadGameTimer();
14482             pausing = TRUE;
14483             ModeHighlight();
14484             break;
14485           case BeginningOfGame:
14486             if (appData.icsActive) return;
14487             /* else fall through */
14488           case MachinePlaysWhite:
14489           case MachinePlaysBlack:
14490           case TwoMachinesPlay:
14491             if (forwardMostMove == 0)
14492               return;           /* don't pause if no one has moved */
14493             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14494                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14495                 if(onMove->pause) {           // thinking engine can be paused
14496                     PauseEngine(onMove);      // do it
14497                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14498                         PauseEngine(onMove->other);
14499                     else
14500                         SendToProgram("easy\n", onMove->other);
14501                     StopClocks();
14502                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14503             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14504                 if(first.pause) {
14505                     PauseEngine(&first);
14506                     StopClocks();
14507                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14508             } else { // human on move, pause pondering by either method
14509                 if(first.pause)
14510                     PauseEngine(&first);
14511                 else if(appData.ponderNextMove)
14512                     SendToProgram("easy\n", &first);
14513                 StopClocks();
14514             }
14515             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14516           case AnalyzeMode:
14517             pausing = TRUE;
14518             ModeHighlight();
14519             break;
14520         }
14521     }
14522 }
14523
14524 void
14525 EditCommentEvent ()
14526 {
14527     char title[MSG_SIZ];
14528
14529     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14530       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14531     } else {
14532       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14533                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14534                parseList[currentMove - 1]);
14535     }
14536
14537     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14538 }
14539
14540
14541 void
14542 EditTagsEvent ()
14543 {
14544     char *tags = PGNTags(&gameInfo);
14545     bookUp = FALSE;
14546     EditTagsPopUp(tags, NULL);
14547     free(tags);
14548 }
14549
14550 void
14551 ToggleSecond ()
14552 {
14553   if(second.analyzing) {
14554     SendToProgram("exit\n", &second);
14555     second.analyzing = FALSE;
14556   } else {
14557     if (second.pr == NoProc) StartChessProgram(&second);
14558     InitChessProgram(&second, FALSE);
14559     FeedMovesToProgram(&second, currentMove);
14560
14561     SendToProgram("analyze\n", &second);
14562     second.analyzing = TRUE;
14563   }
14564 }
14565
14566 /* Toggle ShowThinking */
14567 void
14568 ToggleShowThinking()
14569 {
14570   appData.showThinking = !appData.showThinking;
14571   ShowThinkingEvent();
14572 }
14573
14574 int
14575 AnalyzeModeEvent ()
14576 {
14577     char buf[MSG_SIZ];
14578
14579     if (!first.analysisSupport) {
14580       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14581       DisplayError(buf, 0);
14582       return 0;
14583     }
14584     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14585     if (appData.icsActive) {
14586         if (gameMode != IcsObserving) {
14587           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14588             DisplayError(buf, 0);
14589             /* secure check */
14590             if (appData.icsEngineAnalyze) {
14591                 if (appData.debugMode)
14592                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14593                 ExitAnalyzeMode();
14594                 ModeHighlight();
14595             }
14596             return 0;
14597         }
14598         /* if enable, user wants to disable icsEngineAnalyze */
14599         if (appData.icsEngineAnalyze) {
14600                 ExitAnalyzeMode();
14601                 ModeHighlight();
14602                 return 0;
14603         }
14604         appData.icsEngineAnalyze = TRUE;
14605         if (appData.debugMode)
14606             fprintf(debugFP, "ICS engine analyze starting... \n");
14607     }
14608
14609     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14610     if (appData.noChessProgram || gameMode == AnalyzeMode)
14611       return 0;
14612
14613     if (gameMode != AnalyzeFile) {
14614         if (!appData.icsEngineAnalyze) {
14615                EditGameEvent();
14616                if (gameMode != EditGame) return 0;
14617         }
14618         if (!appData.showThinking) ToggleShowThinking();
14619         ResurrectChessProgram();
14620         SendToProgram("analyze\n", &first);
14621         first.analyzing = TRUE;
14622         /*first.maybeThinking = TRUE;*/
14623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14624         EngineOutputPopUp();
14625     }
14626     if (!appData.icsEngineAnalyze) {
14627         gameMode = AnalyzeMode;
14628         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14629     }
14630     pausing = FALSE;
14631     ModeHighlight();
14632     SetGameInfo();
14633
14634     StartAnalysisClock();
14635     GetTimeMark(&lastNodeCountTime);
14636     lastNodeCount = 0;
14637     return 1;
14638 }
14639
14640 void
14641 AnalyzeFileEvent ()
14642 {
14643     if (appData.noChessProgram || gameMode == AnalyzeFile)
14644       return;
14645
14646     if (!first.analysisSupport) {
14647       char buf[MSG_SIZ];
14648       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14649       DisplayError(buf, 0);
14650       return;
14651     }
14652
14653     if (gameMode != AnalyzeMode) {
14654         keepInfo = 1; // mere annotating should not alter PGN tags
14655         EditGameEvent();
14656         keepInfo = 0;
14657         if (gameMode != EditGame) return;
14658         if (!appData.showThinking) ToggleShowThinking();
14659         ResurrectChessProgram();
14660         SendToProgram("analyze\n", &first);
14661         first.analyzing = TRUE;
14662         /*first.maybeThinking = TRUE;*/
14663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14664         EngineOutputPopUp();
14665     }
14666     gameMode = AnalyzeFile;
14667     pausing = FALSE;
14668     ModeHighlight();
14669
14670     StartAnalysisClock();
14671     GetTimeMark(&lastNodeCountTime);
14672     lastNodeCount = 0;
14673     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14674     AnalysisPeriodicEvent(1);
14675 }
14676
14677 void
14678 MachineWhiteEvent ()
14679 {
14680     char buf[MSG_SIZ];
14681     char *bookHit = NULL;
14682
14683     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14684       return;
14685
14686
14687     if (gameMode == PlayFromGameFile ||
14688         gameMode == TwoMachinesPlay  ||
14689         gameMode == Training         ||
14690         gameMode == AnalyzeMode      ||
14691         gameMode == EndOfGame)
14692         EditGameEvent();
14693
14694     if (gameMode == EditPosition)
14695         EditPositionDone(TRUE);
14696
14697     if (!WhiteOnMove(currentMove)) {
14698         DisplayError(_("It is not White's turn"), 0);
14699         return;
14700     }
14701
14702     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14703       ExitAnalyzeMode();
14704
14705     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14706         gameMode == AnalyzeFile)
14707         TruncateGame();
14708
14709     ResurrectChessProgram();    /* in case it isn't running */
14710     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14711         gameMode = MachinePlaysWhite;
14712         ResetClocks();
14713     } else
14714     gameMode = MachinePlaysWhite;
14715     pausing = FALSE;
14716     ModeHighlight();
14717     SetGameInfo();
14718     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14719     DisplayTitle(buf);
14720     if (first.sendName) {
14721       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14722       SendToProgram(buf, &first);
14723     }
14724     if (first.sendTime) {
14725       if (first.useColors) {
14726         SendToProgram("black\n", &first); /*gnu kludge*/
14727       }
14728       SendTimeRemaining(&first, TRUE);
14729     }
14730     if (first.useColors) {
14731       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14732     }
14733     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14734     SetMachineThinkingEnables();
14735     first.maybeThinking = TRUE;
14736     StartClocks();
14737     firstMove = FALSE;
14738
14739     if (appData.autoFlipView && !flipView) {
14740       flipView = !flipView;
14741       DrawPosition(FALSE, NULL);
14742       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14743     }
14744
14745     if(bookHit) { // [HGM] book: simulate book reply
14746         static char bookMove[MSG_SIZ]; // a bit generous?
14747
14748         programStats.nodes = programStats.depth = programStats.time =
14749         programStats.score = programStats.got_only_move = 0;
14750         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14751
14752         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14753         strcat(bookMove, bookHit);
14754         HandleMachineMove(bookMove, &first);
14755     }
14756 }
14757
14758 void
14759 MachineBlackEvent ()
14760 {
14761   char buf[MSG_SIZ];
14762   char *bookHit = NULL;
14763
14764     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14765         return;
14766
14767
14768     if (gameMode == PlayFromGameFile ||
14769         gameMode == TwoMachinesPlay  ||
14770         gameMode == Training         ||
14771         gameMode == AnalyzeMode      ||
14772         gameMode == EndOfGame)
14773         EditGameEvent();
14774
14775     if (gameMode == EditPosition)
14776         EditPositionDone(TRUE);
14777
14778     if (WhiteOnMove(currentMove)) {
14779         DisplayError(_("It is not Black's turn"), 0);
14780         return;
14781     }
14782
14783     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14784       ExitAnalyzeMode();
14785
14786     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14787         gameMode == AnalyzeFile)
14788         TruncateGame();
14789
14790     ResurrectChessProgram();    /* in case it isn't running */
14791     gameMode = MachinePlaysBlack;
14792     pausing = FALSE;
14793     ModeHighlight();
14794     SetGameInfo();
14795     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14796     DisplayTitle(buf);
14797     if (first.sendName) {
14798       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14799       SendToProgram(buf, &first);
14800     }
14801     if (first.sendTime) {
14802       if (first.useColors) {
14803         SendToProgram("white\n", &first); /*gnu kludge*/
14804       }
14805       SendTimeRemaining(&first, FALSE);
14806     }
14807     if (first.useColors) {
14808       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14809     }
14810     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14811     SetMachineThinkingEnables();
14812     first.maybeThinking = TRUE;
14813     StartClocks();
14814
14815     if (appData.autoFlipView && flipView) {
14816       flipView = !flipView;
14817       DrawPosition(FALSE, NULL);
14818       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14819     }
14820     if(bookHit) { // [HGM] book: simulate book reply
14821         static char bookMove[MSG_SIZ]; // a bit generous?
14822
14823         programStats.nodes = programStats.depth = programStats.time =
14824         programStats.score = programStats.got_only_move = 0;
14825         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14826
14827         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14828         strcat(bookMove, bookHit);
14829         HandleMachineMove(bookMove, &first);
14830     }
14831 }
14832
14833
14834 void
14835 DisplayTwoMachinesTitle ()
14836 {
14837     char buf[MSG_SIZ];
14838     if (appData.matchGames > 0) {
14839         if(appData.tourneyFile[0]) {
14840           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14841                    gameInfo.white, _("vs."), gameInfo.black,
14842                    nextGame+1, appData.matchGames+1,
14843                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14844         } else
14845         if (first.twoMachinesColor[0] == 'w') {
14846           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14847                    gameInfo.white, _("vs."),  gameInfo.black,
14848                    first.matchWins, second.matchWins,
14849                    matchGame - 1 - (first.matchWins + second.matchWins));
14850         } else {
14851           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14852                    gameInfo.white, _("vs."), gameInfo.black,
14853                    second.matchWins, first.matchWins,
14854                    matchGame - 1 - (first.matchWins + second.matchWins));
14855         }
14856     } else {
14857       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14858     }
14859     DisplayTitle(buf);
14860 }
14861
14862 void
14863 SettingsMenuIfReady ()
14864 {
14865   if (second.lastPing != second.lastPong) {
14866     DisplayMessage("", _("Waiting for second chess program"));
14867     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14868     return;
14869   }
14870   ThawUI();
14871   DisplayMessage("", "");
14872   SettingsPopUp(&second);
14873 }
14874
14875 int
14876 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14877 {
14878     char buf[MSG_SIZ];
14879     if (cps->pr == NoProc) {
14880         StartChessProgram(cps);
14881         if (cps->protocolVersion == 1) {
14882           retry();
14883           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14884         } else {
14885           /* kludge: allow timeout for initial "feature" command */
14886           if(retry != TwoMachinesEventIfReady) FreezeUI();
14887           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14888           DisplayMessage("", buf);
14889           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14890         }
14891         return 1;
14892     }
14893     return 0;
14894 }
14895
14896 void
14897 TwoMachinesEvent P((void))
14898 {
14899     int i;
14900     char buf[MSG_SIZ];
14901     ChessProgramState *onmove;
14902     char *bookHit = NULL;
14903     static int stalling = 0;
14904     TimeMark now;
14905     long wait;
14906
14907     if (appData.noChessProgram) return;
14908
14909     switch (gameMode) {
14910       case TwoMachinesPlay:
14911         return;
14912       case MachinePlaysWhite:
14913       case MachinePlaysBlack:
14914         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14915             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14916             return;
14917         }
14918         /* fall through */
14919       case BeginningOfGame:
14920       case PlayFromGameFile:
14921       case EndOfGame:
14922         EditGameEvent();
14923         if (gameMode != EditGame) return;
14924         break;
14925       case EditPosition:
14926         EditPositionDone(TRUE);
14927         break;
14928       case AnalyzeMode:
14929       case AnalyzeFile:
14930         ExitAnalyzeMode();
14931         break;
14932       case EditGame:
14933       default:
14934         break;
14935     }
14936
14937 //    forwardMostMove = currentMove;
14938     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14939     startingEngine = TRUE;
14940
14941     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14942
14943     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14944     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14945       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14946       return;
14947     }
14948     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14949
14950     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14951                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14952         startingEngine = matchMode = FALSE;
14953         DisplayError("second engine does not play this", 0);
14954         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14955         EditGameEvent(); // switch back to EditGame mode
14956         return;
14957     }
14958
14959     if(!stalling) {
14960       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14961       SendToProgram("force\n", &second);
14962       stalling = 1;
14963       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14964       return;
14965     }
14966     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14967     if(appData.matchPause>10000 || appData.matchPause<10)
14968                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14969     wait = SubtractTimeMarks(&now, &pauseStart);
14970     if(wait < appData.matchPause) {
14971         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14972         return;
14973     }
14974     // we are now committed to starting the game
14975     stalling = 0;
14976     DisplayMessage("", "");
14977     if (startedFromSetupPosition) {
14978         SendBoard(&second, backwardMostMove);
14979     if (appData.debugMode) {
14980         fprintf(debugFP, "Two Machines\n");
14981     }
14982     }
14983     for (i = backwardMostMove; i < forwardMostMove; i++) {
14984         SendMoveToProgram(i, &second);
14985     }
14986
14987     gameMode = TwoMachinesPlay;
14988     pausing = startingEngine = FALSE;
14989     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14990     SetGameInfo();
14991     DisplayTwoMachinesTitle();
14992     firstMove = TRUE;
14993     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14994         onmove = &first;
14995     } else {
14996         onmove = &second;
14997     }
14998     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14999     SendToProgram(first.computerString, &first);
15000     if (first.sendName) {
15001       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15002       SendToProgram(buf, &first);
15003     }
15004     SendToProgram(second.computerString, &second);
15005     if (second.sendName) {
15006       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15007       SendToProgram(buf, &second);
15008     }
15009
15010     ResetClocks();
15011     if (!first.sendTime || !second.sendTime) {
15012         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15013         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15014     }
15015     if (onmove->sendTime) {
15016       if (onmove->useColors) {
15017         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15018       }
15019       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15020     }
15021     if (onmove->useColors) {
15022       SendToProgram(onmove->twoMachinesColor, onmove);
15023     }
15024     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15025 //    SendToProgram("go\n", onmove);
15026     onmove->maybeThinking = TRUE;
15027     SetMachineThinkingEnables();
15028
15029     StartClocks();
15030
15031     if(bookHit) { // [HGM] book: simulate book reply
15032         static char bookMove[MSG_SIZ]; // a bit generous?
15033
15034         programStats.nodes = programStats.depth = programStats.time =
15035         programStats.score = programStats.got_only_move = 0;
15036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15037
15038         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15039         strcat(bookMove, bookHit);
15040         savedMessage = bookMove; // args for deferred call
15041         savedState = onmove;
15042         ScheduleDelayedEvent(DeferredBookMove, 1);
15043     }
15044 }
15045
15046 void
15047 TrainingEvent ()
15048 {
15049     if (gameMode == Training) {
15050       SetTrainingModeOff();
15051       gameMode = PlayFromGameFile;
15052       DisplayMessage("", _("Training mode off"));
15053     } else {
15054       gameMode = Training;
15055       animateTraining = appData.animate;
15056
15057       /* make sure we are not already at the end of the game */
15058       if (currentMove < forwardMostMove) {
15059         SetTrainingModeOn();
15060         DisplayMessage("", _("Training mode on"));
15061       } else {
15062         gameMode = PlayFromGameFile;
15063         DisplayError(_("Already at end of game"), 0);
15064       }
15065     }
15066     ModeHighlight();
15067 }
15068
15069 void
15070 IcsClientEvent ()
15071 {
15072     if (!appData.icsActive) return;
15073     switch (gameMode) {
15074       case IcsPlayingWhite:
15075       case IcsPlayingBlack:
15076       case IcsObserving:
15077       case IcsIdle:
15078       case BeginningOfGame:
15079       case IcsExamining:
15080         return;
15081
15082       case EditGame:
15083         break;
15084
15085       case EditPosition:
15086         EditPositionDone(TRUE);
15087         break;
15088
15089       case AnalyzeMode:
15090       case AnalyzeFile:
15091         ExitAnalyzeMode();
15092         break;
15093
15094       default:
15095         EditGameEvent();
15096         break;
15097     }
15098
15099     gameMode = IcsIdle;
15100     ModeHighlight();
15101     return;
15102 }
15103
15104 void
15105 EditGameEvent ()
15106 {
15107     int i;
15108
15109     switch (gameMode) {
15110       case Training:
15111         SetTrainingModeOff();
15112         break;
15113       case MachinePlaysWhite:
15114       case MachinePlaysBlack:
15115       case BeginningOfGame:
15116         SendToProgram("force\n", &first);
15117         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15118             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15119                 char buf[MSG_SIZ];
15120                 abortEngineThink = TRUE;
15121                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15122                 SendToProgram(buf, &first);
15123                 DisplayMessage("Aborting engine think", "");
15124                 FreezeUI();
15125             }
15126         }
15127         SetUserThinkingEnables();
15128         break;
15129       case PlayFromGameFile:
15130         (void) StopLoadGameTimer();
15131         if (gameFileFP != NULL) {
15132             gameFileFP = NULL;
15133         }
15134         break;
15135       case EditPosition:
15136         EditPositionDone(TRUE);
15137         break;
15138       case AnalyzeMode:
15139       case AnalyzeFile:
15140         ExitAnalyzeMode();
15141         SendToProgram("force\n", &first);
15142         break;
15143       case TwoMachinesPlay:
15144         GameEnds(EndOfFile, NULL, GE_PLAYER);
15145         ResurrectChessProgram();
15146         SetUserThinkingEnables();
15147         break;
15148       case EndOfGame:
15149         ResurrectChessProgram();
15150         break;
15151       case IcsPlayingBlack:
15152       case IcsPlayingWhite:
15153         DisplayError(_("Warning: You are still playing a game"), 0);
15154         break;
15155       case IcsObserving:
15156         DisplayError(_("Warning: You are still observing a game"), 0);
15157         break;
15158       case IcsExamining:
15159         DisplayError(_("Warning: You are still examining a game"), 0);
15160         break;
15161       case IcsIdle:
15162         break;
15163       case EditGame:
15164       default:
15165         return;
15166     }
15167
15168     pausing = FALSE;
15169     StopClocks();
15170     first.offeredDraw = second.offeredDraw = 0;
15171
15172     if (gameMode == PlayFromGameFile) {
15173         whiteTimeRemaining = timeRemaining[0][currentMove];
15174         blackTimeRemaining = timeRemaining[1][currentMove];
15175         DisplayTitle("");
15176     }
15177
15178     if (gameMode == MachinePlaysWhite ||
15179         gameMode == MachinePlaysBlack ||
15180         gameMode == TwoMachinesPlay ||
15181         gameMode == EndOfGame) {
15182         i = forwardMostMove;
15183         while (i > currentMove) {
15184             SendToProgram("undo\n", &first);
15185             i--;
15186         }
15187         if(!adjustedClock) {
15188         whiteTimeRemaining = timeRemaining[0][currentMove];
15189         blackTimeRemaining = timeRemaining[1][currentMove];
15190         DisplayBothClocks();
15191         }
15192         if (whiteFlag || blackFlag) {
15193             whiteFlag = blackFlag = 0;
15194         }
15195         DisplayTitle("");
15196     }
15197
15198     gameMode = EditGame;
15199     ModeHighlight();
15200     SetGameInfo();
15201 }
15202
15203
15204 void
15205 EditPositionEvent ()
15206 {
15207     if (gameMode == EditPosition) {
15208         EditGameEvent();
15209         return;
15210     }
15211
15212     EditGameEvent();
15213     if (gameMode != EditGame) return;
15214
15215     gameMode = EditPosition;
15216     ModeHighlight();
15217     SetGameInfo();
15218     if (currentMove > 0)
15219       CopyBoard(boards[0], boards[currentMove]);
15220
15221     blackPlaysFirst = !WhiteOnMove(currentMove);
15222     ResetClocks();
15223     currentMove = forwardMostMove = backwardMostMove = 0;
15224     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15225     DisplayMove(-1);
15226     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15227 }
15228
15229 void
15230 ExitAnalyzeMode ()
15231 {
15232     /* [DM] icsEngineAnalyze - possible call from other functions */
15233     if (appData.icsEngineAnalyze) {
15234         appData.icsEngineAnalyze = FALSE;
15235
15236         DisplayMessage("",_("Close ICS engine analyze..."));
15237     }
15238     if (first.analysisSupport && first.analyzing) {
15239       SendToBoth("exit\n");
15240       first.analyzing = second.analyzing = FALSE;
15241     }
15242     thinkOutput[0] = NULLCHAR;
15243 }
15244
15245 void
15246 EditPositionDone (Boolean fakeRights)
15247 {
15248     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15249
15250     startedFromSetupPosition = TRUE;
15251     InitChessProgram(&first, FALSE);
15252     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15253       boards[0][EP_STATUS] = EP_NONE;
15254       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15255       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15256         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15257         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15258       } else boards[0][CASTLING][2] = NoRights;
15259       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15260         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15261         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15262       } else boards[0][CASTLING][5] = NoRights;
15263       if(gameInfo.variant == VariantSChess) {
15264         int i;
15265         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15266           boards[0][VIRGIN][i] = 0;
15267           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15268           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15269         }
15270       }
15271     }
15272     SendToProgram("force\n", &first);
15273     if (blackPlaysFirst) {
15274         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15275         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15276         currentMove = forwardMostMove = backwardMostMove = 1;
15277         CopyBoard(boards[1], boards[0]);
15278     } else {
15279         currentMove = forwardMostMove = backwardMostMove = 0;
15280     }
15281     SendBoard(&first, forwardMostMove);
15282     if (appData.debugMode) {
15283         fprintf(debugFP, "EditPosDone\n");
15284     }
15285     DisplayTitle("");
15286     DisplayMessage("", "");
15287     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15288     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15289     gameMode = EditGame;
15290     ModeHighlight();
15291     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15292     ClearHighlights(); /* [AS] */
15293 }
15294
15295 /* Pause for `ms' milliseconds */
15296 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15297 void
15298 TimeDelay (long ms)
15299 {
15300     TimeMark m1, m2;
15301
15302     GetTimeMark(&m1);
15303     do {
15304         GetTimeMark(&m2);
15305     } while (SubtractTimeMarks(&m2, &m1) < ms);
15306 }
15307
15308 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15309 void
15310 SendMultiLineToICS (char *buf)
15311 {
15312     char temp[MSG_SIZ+1], *p;
15313     int len;
15314
15315     len = strlen(buf);
15316     if (len > MSG_SIZ)
15317       len = MSG_SIZ;
15318
15319     strncpy(temp, buf, len);
15320     temp[len] = 0;
15321
15322     p = temp;
15323     while (*p) {
15324         if (*p == '\n' || *p == '\r')
15325           *p = ' ';
15326         ++p;
15327     }
15328
15329     strcat(temp, "\n");
15330     SendToICS(temp);
15331     SendToPlayer(temp, strlen(temp));
15332 }
15333
15334 void
15335 SetWhiteToPlayEvent ()
15336 {
15337     if (gameMode == EditPosition) {
15338         blackPlaysFirst = FALSE;
15339         DisplayBothClocks();    /* works because currentMove is 0 */
15340     } else if (gameMode == IcsExamining) {
15341         SendToICS(ics_prefix);
15342         SendToICS("tomove white\n");
15343     }
15344 }
15345
15346 void
15347 SetBlackToPlayEvent ()
15348 {
15349     if (gameMode == EditPosition) {
15350         blackPlaysFirst = TRUE;
15351         currentMove = 1;        /* kludge */
15352         DisplayBothClocks();
15353         currentMove = 0;
15354     } else if (gameMode == IcsExamining) {
15355         SendToICS(ics_prefix);
15356         SendToICS("tomove black\n");
15357     }
15358 }
15359
15360 void
15361 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15362 {
15363     char buf[MSG_SIZ];
15364     ChessSquare piece = boards[0][y][x];
15365     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15366     static int lastVariant;
15367
15368     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15369
15370     switch (selection) {
15371       case ClearBoard:
15372         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15373         MarkTargetSquares(1);
15374         CopyBoard(currentBoard, boards[0]);
15375         CopyBoard(menuBoard, initialPosition);
15376         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15377             SendToICS(ics_prefix);
15378             SendToICS("bsetup clear\n");
15379         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15380             SendToICS(ics_prefix);
15381             SendToICS("clearboard\n");
15382         } else {
15383             int nonEmpty = 0;
15384             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15385                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15386                 for (y = 0; y < BOARD_HEIGHT; y++) {
15387                     if (gameMode == IcsExamining) {
15388                         if (boards[currentMove][y][x] != EmptySquare) {
15389                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15390                                     AAA + x, ONE + y);
15391                             SendToICS(buf);
15392                         }
15393                     } else if(boards[0][y][x] != DarkSquare) {
15394                         if(boards[0][y][x] != p) nonEmpty++;
15395                         boards[0][y][x] = p;
15396                     }
15397                 }
15398             }
15399             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15400                 int r;
15401                 for(r = 0; r < BOARD_HEIGHT; r++) {
15402                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15403                     ChessSquare p = menuBoard[r][x];
15404                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15405                   }
15406                 }
15407                 DisplayMessage("Clicking clock again restores position", "");
15408                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15409                 if(!nonEmpty) { // asked to clear an empty board
15410                     CopyBoard(boards[0], menuBoard);
15411                 } else
15412                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15413                     CopyBoard(boards[0], initialPosition);
15414                 } else
15415                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15416                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15417                     CopyBoard(boards[0], erasedBoard);
15418                 } else
15419                     CopyBoard(erasedBoard, currentBoard);
15420
15421             }
15422         }
15423         if (gameMode == EditPosition) {
15424             DrawPosition(FALSE, boards[0]);
15425         }
15426         break;
15427
15428       case WhitePlay:
15429         SetWhiteToPlayEvent();
15430         break;
15431
15432       case BlackPlay:
15433         SetBlackToPlayEvent();
15434         break;
15435
15436       case EmptySquare:
15437         if (gameMode == IcsExamining) {
15438             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15439             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15440             SendToICS(buf);
15441         } else {
15442             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15443                 if(x == BOARD_LEFT-2) {
15444                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15445                     boards[0][y][1] = 0;
15446                 } else
15447                 if(x == BOARD_RGHT+1) {
15448                     if(y >= gameInfo.holdingsSize) break;
15449                     boards[0][y][BOARD_WIDTH-2] = 0;
15450                 } else break;
15451             }
15452             boards[0][y][x] = EmptySquare;
15453             DrawPosition(FALSE, boards[0]);
15454         }
15455         break;
15456
15457       case PromotePiece:
15458         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15459            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15460             selection = (ChessSquare) (PROMOTED(piece));
15461         } else if(piece == EmptySquare) selection = WhiteSilver;
15462         else selection = (ChessSquare)((int)piece - 1);
15463         goto defaultlabel;
15464
15465       case DemotePiece:
15466         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15467            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15468             selection = (ChessSquare) (DEMOTED(piece));
15469         } else if(piece == EmptySquare) selection = BlackSilver;
15470         else selection = (ChessSquare)((int)piece + 1);
15471         goto defaultlabel;
15472
15473       case WhiteQueen:
15474       case BlackQueen:
15475         if(gameInfo.variant == VariantShatranj ||
15476            gameInfo.variant == VariantXiangqi  ||
15477            gameInfo.variant == VariantCourier  ||
15478            gameInfo.variant == VariantASEAN    ||
15479            gameInfo.variant == VariantMakruk     )
15480             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15481         goto defaultlabel;
15482
15483       case WhiteKing:
15484       case BlackKing:
15485         if(gameInfo.variant == VariantXiangqi)
15486             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15487         if(gameInfo.variant == VariantKnightmate)
15488             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15489       default:
15490         defaultlabel:
15491         if (gameMode == IcsExamining) {
15492             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15493             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15494                      PieceToChar(selection), AAA + x, ONE + y);
15495             SendToICS(buf);
15496         } else {
15497             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15498                 int n;
15499                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15500                     n = PieceToNumber(selection - BlackPawn);
15501                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15502                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15503                     boards[0][BOARD_HEIGHT-1-n][1]++;
15504                 } else
15505                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15506                     n = PieceToNumber(selection);
15507                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15508                     boards[0][n][BOARD_WIDTH-1] = selection;
15509                     boards[0][n][BOARD_WIDTH-2]++;
15510                 }
15511             } else
15512             boards[0][y][x] = selection;
15513             DrawPosition(TRUE, boards[0]);
15514             ClearHighlights();
15515             fromX = fromY = -1;
15516         }
15517         break;
15518     }
15519 }
15520
15521
15522 void
15523 DropMenuEvent (ChessSquare selection, int x, int y)
15524 {
15525     ChessMove moveType;
15526
15527     switch (gameMode) {
15528       case IcsPlayingWhite:
15529       case MachinePlaysBlack:
15530         if (!WhiteOnMove(currentMove)) {
15531             DisplayMoveError(_("It is Black's turn"));
15532             return;
15533         }
15534         moveType = WhiteDrop;
15535         break;
15536       case IcsPlayingBlack:
15537       case MachinePlaysWhite:
15538         if (WhiteOnMove(currentMove)) {
15539             DisplayMoveError(_("It is White's turn"));
15540             return;
15541         }
15542         moveType = BlackDrop;
15543         break;
15544       case EditGame:
15545         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15546         break;
15547       default:
15548         return;
15549     }
15550
15551     if (moveType == BlackDrop && selection < BlackPawn) {
15552       selection = (ChessSquare) ((int) selection
15553                                  + (int) BlackPawn - (int) WhitePawn);
15554     }
15555     if (boards[currentMove][y][x] != EmptySquare) {
15556         DisplayMoveError(_("That square is occupied"));
15557         return;
15558     }
15559
15560     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15561 }
15562
15563 void
15564 AcceptEvent ()
15565 {
15566     /* Accept a pending offer of any kind from opponent */
15567
15568     if (appData.icsActive) {
15569         SendToICS(ics_prefix);
15570         SendToICS("accept\n");
15571     } else if (cmailMsgLoaded) {
15572         if (currentMove == cmailOldMove &&
15573             commentList[cmailOldMove] != NULL &&
15574             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15575                    "Black offers a draw" : "White offers a draw")) {
15576             TruncateGame();
15577             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15578             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15579         } else {
15580             DisplayError(_("There is no pending offer on this move"), 0);
15581             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15582         }
15583     } else {
15584         /* Not used for offers from chess program */
15585     }
15586 }
15587
15588 void
15589 DeclineEvent ()
15590 {
15591     /* Decline a pending offer of any kind from opponent */
15592
15593     if (appData.icsActive) {
15594         SendToICS(ics_prefix);
15595         SendToICS("decline\n");
15596     } else if (cmailMsgLoaded) {
15597         if (currentMove == cmailOldMove &&
15598             commentList[cmailOldMove] != NULL &&
15599             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15600                    "Black offers a draw" : "White offers a draw")) {
15601 #ifdef NOTDEF
15602             AppendComment(cmailOldMove, "Draw declined", TRUE);
15603             DisplayComment(cmailOldMove - 1, "Draw declined");
15604 #endif /*NOTDEF*/
15605         } else {
15606             DisplayError(_("There is no pending offer on this move"), 0);
15607         }
15608     } else {
15609         /* Not used for offers from chess program */
15610     }
15611 }
15612
15613 void
15614 RematchEvent ()
15615 {
15616     /* Issue ICS rematch command */
15617     if (appData.icsActive) {
15618         SendToICS(ics_prefix);
15619         SendToICS("rematch\n");
15620     }
15621 }
15622
15623 void
15624 CallFlagEvent ()
15625 {
15626     /* Call your opponent's flag (claim a win on time) */
15627     if (appData.icsActive) {
15628         SendToICS(ics_prefix);
15629         SendToICS("flag\n");
15630     } else {
15631         switch (gameMode) {
15632           default:
15633             return;
15634           case MachinePlaysWhite:
15635             if (whiteFlag) {
15636                 if (blackFlag)
15637                   GameEnds(GameIsDrawn, "Both players ran out of time",
15638                            GE_PLAYER);
15639                 else
15640                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15641             } else {
15642                 DisplayError(_("Your opponent is not out of time"), 0);
15643             }
15644             break;
15645           case MachinePlaysBlack:
15646             if (blackFlag) {
15647                 if (whiteFlag)
15648                   GameEnds(GameIsDrawn, "Both players ran out of time",
15649                            GE_PLAYER);
15650                 else
15651                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15652             } else {
15653                 DisplayError(_("Your opponent is not out of time"), 0);
15654             }
15655             break;
15656         }
15657     }
15658 }
15659
15660 void
15661 ClockClick (int which)
15662 {       // [HGM] code moved to back-end from winboard.c
15663         if(which) { // black clock
15664           if (gameMode == EditPosition || gameMode == IcsExamining) {
15665             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15666             SetBlackToPlayEvent();
15667           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15668                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15669           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15670           } else if (shiftKey) {
15671             AdjustClock(which, -1);
15672           } else if (gameMode == IcsPlayingWhite ||
15673                      gameMode == MachinePlaysBlack) {
15674             CallFlagEvent();
15675           }
15676         } else { // white clock
15677           if (gameMode == EditPosition || gameMode == IcsExamining) {
15678             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15679             SetWhiteToPlayEvent();
15680           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15681                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15682           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15683           } else if (shiftKey) {
15684             AdjustClock(which, -1);
15685           } else if (gameMode == IcsPlayingBlack ||
15686                    gameMode == MachinePlaysWhite) {
15687             CallFlagEvent();
15688           }
15689         }
15690 }
15691
15692 void
15693 DrawEvent ()
15694 {
15695     /* Offer draw or accept pending draw offer from opponent */
15696
15697     if (appData.icsActive) {
15698         /* Note: tournament rules require draw offers to be
15699            made after you make your move but before you punch
15700            your clock.  Currently ICS doesn't let you do that;
15701            instead, you immediately punch your clock after making
15702            a move, but you can offer a draw at any time. */
15703
15704         SendToICS(ics_prefix);
15705         SendToICS("draw\n");
15706         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15707     } else if (cmailMsgLoaded) {
15708         if (currentMove == cmailOldMove &&
15709             commentList[cmailOldMove] != NULL &&
15710             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15711                    "Black offers a draw" : "White offers a draw")) {
15712             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15713             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15714         } else if (currentMove == cmailOldMove + 1) {
15715             char *offer = WhiteOnMove(cmailOldMove) ?
15716               "White offers a draw" : "Black offers a draw";
15717             AppendComment(currentMove, offer, TRUE);
15718             DisplayComment(currentMove - 1, offer);
15719             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15720         } else {
15721             DisplayError(_("You must make your move before offering a draw"), 0);
15722             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15723         }
15724     } else if (first.offeredDraw) {
15725         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15726     } else {
15727         if (first.sendDrawOffers) {
15728             SendToProgram("draw\n", &first);
15729             userOfferedDraw = TRUE;
15730         }
15731     }
15732 }
15733
15734 void
15735 AdjournEvent ()
15736 {
15737     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15738
15739     if (appData.icsActive) {
15740         SendToICS(ics_prefix);
15741         SendToICS("adjourn\n");
15742     } else {
15743         /* Currently GNU Chess doesn't offer or accept Adjourns */
15744     }
15745 }
15746
15747
15748 void
15749 AbortEvent ()
15750 {
15751     /* Offer Abort or accept pending Abort offer from opponent */
15752
15753     if (appData.icsActive) {
15754         SendToICS(ics_prefix);
15755         SendToICS("abort\n");
15756     } else {
15757         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15758     }
15759 }
15760
15761 void
15762 ResignEvent ()
15763 {
15764     /* Resign.  You can do this even if it's not your turn. */
15765
15766     if (appData.icsActive) {
15767         SendToICS(ics_prefix);
15768         SendToICS("resign\n");
15769     } else {
15770         switch (gameMode) {
15771           case MachinePlaysWhite:
15772             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15773             break;
15774           case MachinePlaysBlack:
15775             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15776             break;
15777           case EditGame:
15778             if (cmailMsgLoaded) {
15779                 TruncateGame();
15780                 if (WhiteOnMove(cmailOldMove)) {
15781                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15782                 } else {
15783                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15784                 }
15785                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15786             }
15787             break;
15788           default:
15789             break;
15790         }
15791     }
15792 }
15793
15794
15795 void
15796 StopObservingEvent ()
15797 {
15798     /* Stop observing current games */
15799     SendToICS(ics_prefix);
15800     SendToICS("unobserve\n");
15801 }
15802
15803 void
15804 StopExaminingEvent ()
15805 {
15806     /* Stop observing current game */
15807     SendToICS(ics_prefix);
15808     SendToICS("unexamine\n");
15809 }
15810
15811 void
15812 ForwardInner (int target)
15813 {
15814     int limit; int oldSeekGraphUp = seekGraphUp;
15815
15816     if (appData.debugMode)
15817         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15818                 target, currentMove, forwardMostMove);
15819
15820     if (gameMode == EditPosition)
15821       return;
15822
15823     seekGraphUp = FALSE;
15824     MarkTargetSquares(1);
15825     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15826
15827     if (gameMode == PlayFromGameFile && !pausing)
15828       PauseEvent();
15829
15830     if (gameMode == IcsExamining && pausing)
15831       limit = pauseExamForwardMostMove;
15832     else
15833       limit = forwardMostMove;
15834
15835     if (target > limit) target = limit;
15836
15837     if (target > 0 && moveList[target - 1][0]) {
15838         int fromX, fromY, toX, toY;
15839         toX = moveList[target - 1][2] - AAA;
15840         toY = moveList[target - 1][3] - ONE;
15841         if (moveList[target - 1][1] == '@') {
15842             if (appData.highlightLastMove) {
15843                 SetHighlights(-1, -1, toX, toY);
15844             }
15845         } else {
15846             int viaX = moveList[target - 1][5] - AAA;
15847             int viaY = moveList[target - 1][6] - ONE;
15848             fromX = moveList[target - 1][0] - AAA;
15849             fromY = moveList[target - 1][1] - ONE;
15850             if (target == currentMove + 1) {
15851                 if(moveList[target - 1][4] == ';') { // multi-leg
15852                     ChessSquare piece = boards[currentMove][viaY][viaX];
15853                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15854                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15855                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15856                     boards[currentMove][viaY][viaX] = piece;
15857                 } else
15858                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15859             }
15860             if (appData.highlightLastMove) {
15861                 SetHighlights(fromX, fromY, toX, toY);
15862             }
15863         }
15864     }
15865     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15866         gameMode == Training || gameMode == PlayFromGameFile ||
15867         gameMode == AnalyzeFile) {
15868         while (currentMove < target) {
15869             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15870             SendMoveToProgram(currentMove++, &first);
15871         }
15872     } else {
15873         currentMove = target;
15874     }
15875
15876     if (gameMode == EditGame || gameMode == EndOfGame) {
15877         whiteTimeRemaining = timeRemaining[0][currentMove];
15878         blackTimeRemaining = timeRemaining[1][currentMove];
15879     }
15880     DisplayBothClocks();
15881     DisplayMove(currentMove - 1);
15882     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15883     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15884     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15885         DisplayComment(currentMove - 1, commentList[currentMove]);
15886     }
15887     ClearMap(); // [HGM] exclude: invalidate map
15888 }
15889
15890
15891 void
15892 ForwardEvent ()
15893 {
15894     if (gameMode == IcsExamining && !pausing) {
15895         SendToICS(ics_prefix);
15896         SendToICS("forward\n");
15897     } else {
15898         ForwardInner(currentMove + 1);
15899     }
15900 }
15901
15902 void
15903 ToEndEvent ()
15904 {
15905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15906         /* to optimze, we temporarily turn off analysis mode while we feed
15907          * the remaining moves to the engine. Otherwise we get analysis output
15908          * after each move.
15909          */
15910         if (first.analysisSupport) {
15911           SendToProgram("exit\nforce\n", &first);
15912           first.analyzing = FALSE;
15913         }
15914     }
15915
15916     if (gameMode == IcsExamining && !pausing) {
15917         SendToICS(ics_prefix);
15918         SendToICS("forward 999999\n");
15919     } else {
15920         ForwardInner(forwardMostMove);
15921     }
15922
15923     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15924         /* we have fed all the moves, so reactivate analysis mode */
15925         SendToProgram("analyze\n", &first);
15926         first.analyzing = TRUE;
15927         /*first.maybeThinking = TRUE;*/
15928         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15929     }
15930 }
15931
15932 void
15933 BackwardInner (int target)
15934 {
15935     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15936
15937     if (appData.debugMode)
15938         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15939                 target, currentMove, forwardMostMove);
15940
15941     if (gameMode == EditPosition) return;
15942     seekGraphUp = FALSE;
15943     MarkTargetSquares(1);
15944     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15945     if (currentMove <= backwardMostMove) {
15946         ClearHighlights();
15947         DrawPosition(full_redraw, boards[currentMove]);
15948         return;
15949     }
15950     if (gameMode == PlayFromGameFile && !pausing)
15951       PauseEvent();
15952
15953     if (moveList[target][0]) {
15954         int fromX, fromY, toX, toY;
15955         toX = moveList[target][2] - AAA;
15956         toY = moveList[target][3] - ONE;
15957         if (moveList[target][1] == '@') {
15958             if (appData.highlightLastMove) {
15959                 SetHighlights(-1, -1, toX, toY);
15960             }
15961         } else {
15962             fromX = moveList[target][0] - AAA;
15963             fromY = moveList[target][1] - ONE;
15964             if (target == currentMove - 1) {
15965                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15966             }
15967             if (appData.highlightLastMove) {
15968                 SetHighlights(fromX, fromY, toX, toY);
15969             }
15970         }
15971     }
15972     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15973         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15974         while (currentMove > target) {
15975             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15976                 // null move cannot be undone. Reload program with move history before it.
15977                 int i;
15978                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15979                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15980                 }
15981                 SendBoard(&first, i);
15982               if(second.analyzing) SendBoard(&second, i);
15983                 for(currentMove=i; currentMove<target; currentMove++) {
15984                     SendMoveToProgram(currentMove, &first);
15985                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15986                 }
15987                 break;
15988             }
15989             SendToBoth("undo\n");
15990             currentMove--;
15991         }
15992     } else {
15993         currentMove = target;
15994     }
15995
15996     if (gameMode == EditGame || gameMode == EndOfGame) {
15997         whiteTimeRemaining = timeRemaining[0][currentMove];
15998         blackTimeRemaining = timeRemaining[1][currentMove];
15999     }
16000     DisplayBothClocks();
16001     DisplayMove(currentMove - 1);
16002     DrawPosition(full_redraw, boards[currentMove]);
16003     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16004     // [HGM] PV info: routine tests if comment empty
16005     DisplayComment(currentMove - 1, commentList[currentMove]);
16006     ClearMap(); // [HGM] exclude: invalidate map
16007 }
16008
16009 void
16010 BackwardEvent ()
16011 {
16012     if (gameMode == IcsExamining && !pausing) {
16013         SendToICS(ics_prefix);
16014         SendToICS("backward\n");
16015     } else {
16016         BackwardInner(currentMove - 1);
16017     }
16018 }
16019
16020 void
16021 ToStartEvent ()
16022 {
16023     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16024         /* to optimize, we temporarily turn off analysis mode while we undo
16025          * all the moves. Otherwise we get analysis output after each undo.
16026          */
16027         if (first.analysisSupport) {
16028           SendToProgram("exit\nforce\n", &first);
16029           first.analyzing = FALSE;
16030         }
16031     }
16032
16033     if (gameMode == IcsExamining && !pausing) {
16034         SendToICS(ics_prefix);
16035         SendToICS("backward 999999\n");
16036     } else {
16037         BackwardInner(backwardMostMove);
16038     }
16039
16040     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16041         /* we have fed all the moves, so reactivate analysis mode */
16042         SendToProgram("analyze\n", &first);
16043         first.analyzing = TRUE;
16044         /*first.maybeThinking = TRUE;*/
16045         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16046     }
16047 }
16048
16049 void
16050 ToNrEvent (int to)
16051 {
16052   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16053   if (to >= forwardMostMove) to = forwardMostMove;
16054   if (to <= backwardMostMove) to = backwardMostMove;
16055   if (to < currentMove) {
16056     BackwardInner(to);
16057   } else {
16058     ForwardInner(to);
16059   }
16060 }
16061
16062 void
16063 RevertEvent (Boolean annotate)
16064 {
16065     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16066         return;
16067     }
16068     if (gameMode != IcsExamining) {
16069         DisplayError(_("You are not examining a game"), 0);
16070         return;
16071     }
16072     if (pausing) {
16073         DisplayError(_("You can't revert while pausing"), 0);
16074         return;
16075     }
16076     SendToICS(ics_prefix);
16077     SendToICS("revert\n");
16078 }
16079
16080 void
16081 RetractMoveEvent ()
16082 {
16083     switch (gameMode) {
16084       case MachinePlaysWhite:
16085       case MachinePlaysBlack:
16086         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16087             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16088             return;
16089         }
16090         if (forwardMostMove < 2) return;
16091         currentMove = forwardMostMove = forwardMostMove - 2;
16092         whiteTimeRemaining = timeRemaining[0][currentMove];
16093         blackTimeRemaining = timeRemaining[1][currentMove];
16094         DisplayBothClocks();
16095         DisplayMove(currentMove - 1);
16096         ClearHighlights();/*!! could figure this out*/
16097         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16098         SendToProgram("remove\n", &first);
16099         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16100         break;
16101
16102       case BeginningOfGame:
16103       default:
16104         break;
16105
16106       case IcsPlayingWhite:
16107       case IcsPlayingBlack:
16108         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16109             SendToICS(ics_prefix);
16110             SendToICS("takeback 2\n");
16111         } else {
16112             SendToICS(ics_prefix);
16113             SendToICS("takeback 1\n");
16114         }
16115         break;
16116     }
16117 }
16118
16119 void
16120 MoveNowEvent ()
16121 {
16122     ChessProgramState *cps;
16123
16124     switch (gameMode) {
16125       case MachinePlaysWhite:
16126         if (!WhiteOnMove(forwardMostMove)) {
16127             DisplayError(_("It is your turn"), 0);
16128             return;
16129         }
16130         cps = &first;
16131         break;
16132       case MachinePlaysBlack:
16133         if (WhiteOnMove(forwardMostMove)) {
16134             DisplayError(_("It is your turn"), 0);
16135             return;
16136         }
16137         cps = &first;
16138         break;
16139       case TwoMachinesPlay:
16140         if (WhiteOnMove(forwardMostMove) ==
16141             (first.twoMachinesColor[0] == 'w')) {
16142             cps = &first;
16143         } else {
16144             cps = &second;
16145         }
16146         break;
16147       case BeginningOfGame:
16148       default:
16149         return;
16150     }
16151     SendToProgram("?\n", cps);
16152 }
16153
16154 void
16155 TruncateGameEvent ()
16156 {
16157     EditGameEvent();
16158     if (gameMode != EditGame) return;
16159     TruncateGame();
16160 }
16161
16162 void
16163 TruncateGame ()
16164 {
16165     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16166     if (forwardMostMove > currentMove) {
16167         if (gameInfo.resultDetails != NULL) {
16168             free(gameInfo.resultDetails);
16169             gameInfo.resultDetails = NULL;
16170             gameInfo.result = GameUnfinished;
16171         }
16172         forwardMostMove = currentMove;
16173         HistorySet(parseList, backwardMostMove, forwardMostMove,
16174                    currentMove-1);
16175     }
16176 }
16177
16178 void
16179 HintEvent ()
16180 {
16181     if (appData.noChessProgram) return;
16182     switch (gameMode) {
16183       case MachinePlaysWhite:
16184         if (WhiteOnMove(forwardMostMove)) {
16185             DisplayError(_("Wait until your turn."), 0);
16186             return;
16187         }
16188         break;
16189       case BeginningOfGame:
16190       case MachinePlaysBlack:
16191         if (!WhiteOnMove(forwardMostMove)) {
16192             DisplayError(_("Wait until your turn."), 0);
16193             return;
16194         }
16195         break;
16196       default:
16197         DisplayError(_("No hint available"), 0);
16198         return;
16199     }
16200     SendToProgram("hint\n", &first);
16201     hintRequested = TRUE;
16202 }
16203
16204 int
16205 SaveSelected (FILE *g, int dummy, char *dummy2)
16206 {
16207     ListGame * lg = (ListGame *) gameList.head;
16208     int nItem, cnt=0;
16209     FILE *f;
16210
16211     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16212         DisplayError(_("Game list not loaded or empty"), 0);
16213         return 0;
16214     }
16215
16216     creatingBook = TRUE; // suppresses stuff during load game
16217
16218     /* Get list size */
16219     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16220         if(lg->position >= 0) { // selected?
16221             LoadGame(f, nItem, "", TRUE);
16222             SaveGamePGN2(g); // leaves g open
16223             cnt++; DoEvents();
16224         }
16225         lg = (ListGame *) lg->node.succ;
16226     }
16227
16228     fclose(g);
16229     creatingBook = FALSE;
16230
16231     return cnt;
16232 }
16233
16234 void
16235 CreateBookEvent ()
16236 {
16237     ListGame * lg = (ListGame *) gameList.head;
16238     FILE *f, *g;
16239     int nItem;
16240     static int secondTime = FALSE;
16241
16242     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16243         DisplayError(_("Game list not loaded or empty"), 0);
16244         return;
16245     }
16246
16247     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16248         fclose(g);
16249         secondTime++;
16250         DisplayNote(_("Book file exists! Try again for overwrite."));
16251         return;
16252     }
16253
16254     creatingBook = TRUE;
16255     secondTime = FALSE;
16256
16257     /* Get list size */
16258     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16259         if(lg->position >= 0) {
16260             LoadGame(f, nItem, "", TRUE);
16261             AddGameToBook(TRUE);
16262             DoEvents();
16263         }
16264         lg = (ListGame *) lg->node.succ;
16265     }
16266
16267     creatingBook = FALSE;
16268     FlushBook();
16269 }
16270
16271 void
16272 BookEvent ()
16273 {
16274     if (appData.noChessProgram) return;
16275     switch (gameMode) {
16276       case MachinePlaysWhite:
16277         if (WhiteOnMove(forwardMostMove)) {
16278             DisplayError(_("Wait until your turn."), 0);
16279             return;
16280         }
16281         break;
16282       case BeginningOfGame:
16283       case MachinePlaysBlack:
16284         if (!WhiteOnMove(forwardMostMove)) {
16285             DisplayError(_("Wait until your turn."), 0);
16286             return;
16287         }
16288         break;
16289       case EditPosition:
16290         EditPositionDone(TRUE);
16291         break;
16292       case TwoMachinesPlay:
16293         return;
16294       default:
16295         break;
16296     }
16297     SendToProgram("bk\n", &first);
16298     bookOutput[0] = NULLCHAR;
16299     bookRequested = TRUE;
16300 }
16301
16302 void
16303 AboutGameEvent ()
16304 {
16305     char *tags = PGNTags(&gameInfo);
16306     TagsPopUp(tags, CmailMsg());
16307     free(tags);
16308 }
16309
16310 /* end button procedures */
16311
16312 void
16313 PrintPosition (FILE *fp, int move)
16314 {
16315     int i, j;
16316
16317     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16318         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16319             char c = PieceToChar(boards[move][i][j]);
16320             fputc(c == '?' ? '.' : c, fp);
16321             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16322         }
16323     }
16324     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16325       fprintf(fp, "white to play\n");
16326     else
16327       fprintf(fp, "black to play\n");
16328 }
16329
16330 void
16331 PrintOpponents (FILE *fp)
16332 {
16333     if (gameInfo.white != NULL) {
16334         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16335     } else {
16336         fprintf(fp, "\n");
16337     }
16338 }
16339
16340 /* Find last component of program's own name, using some heuristics */
16341 void
16342 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16343 {
16344     char *p, *q, c;
16345     int local = (strcmp(host, "localhost") == 0);
16346     while (!local && (p = strchr(prog, ';')) != NULL) {
16347         p++;
16348         while (*p == ' ') p++;
16349         prog = p;
16350     }
16351     if (*prog == '"' || *prog == '\'') {
16352         q = strchr(prog + 1, *prog);
16353     } else {
16354         q = strchr(prog, ' ');
16355     }
16356     if (q == NULL) q = prog + strlen(prog);
16357     p = q;
16358     while (p >= prog && *p != '/' && *p != '\\') p--;
16359     p++;
16360     if(p == prog && *p == '"') p++;
16361     c = *q; *q = 0;
16362     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16363     memcpy(buf, p, q - p);
16364     buf[q - p] = NULLCHAR;
16365     if (!local) {
16366         strcat(buf, "@");
16367         strcat(buf, host);
16368     }
16369 }
16370
16371 char *
16372 TimeControlTagValue ()
16373 {
16374     char buf[MSG_SIZ];
16375     if (!appData.clockMode) {
16376       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16377     } else if (movesPerSession > 0) {
16378       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16379     } else if (timeIncrement == 0) {
16380       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16381     } else {
16382       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16383     }
16384     return StrSave(buf);
16385 }
16386
16387 void
16388 SetGameInfo ()
16389 {
16390     /* This routine is used only for certain modes */
16391     VariantClass v = gameInfo.variant;
16392     ChessMove r = GameUnfinished;
16393     char *p = NULL;
16394
16395     if(keepInfo) return;
16396
16397     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16398         r = gameInfo.result;
16399         p = gameInfo.resultDetails;
16400         gameInfo.resultDetails = NULL;
16401     }
16402     ClearGameInfo(&gameInfo);
16403     gameInfo.variant = v;
16404
16405     switch (gameMode) {
16406       case MachinePlaysWhite:
16407         gameInfo.event = StrSave( appData.pgnEventHeader );
16408         gameInfo.site = StrSave(HostName());
16409         gameInfo.date = PGNDate();
16410         gameInfo.round = StrSave("-");
16411         gameInfo.white = StrSave(first.tidy);
16412         gameInfo.black = StrSave(UserName());
16413         gameInfo.timeControl = TimeControlTagValue();
16414         break;
16415
16416       case MachinePlaysBlack:
16417         gameInfo.event = StrSave( appData.pgnEventHeader );
16418         gameInfo.site = StrSave(HostName());
16419         gameInfo.date = PGNDate();
16420         gameInfo.round = StrSave("-");
16421         gameInfo.white = StrSave(UserName());
16422         gameInfo.black = StrSave(first.tidy);
16423         gameInfo.timeControl = TimeControlTagValue();
16424         break;
16425
16426       case TwoMachinesPlay:
16427         gameInfo.event = StrSave( appData.pgnEventHeader );
16428         gameInfo.site = StrSave(HostName());
16429         gameInfo.date = PGNDate();
16430         if (roundNr > 0) {
16431             char buf[MSG_SIZ];
16432             snprintf(buf, MSG_SIZ, "%d", roundNr);
16433             gameInfo.round = StrSave(buf);
16434         } else {
16435             gameInfo.round = StrSave("-");
16436         }
16437         if (first.twoMachinesColor[0] == 'w') {
16438             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16439             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16440         } else {
16441             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16442             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16443         }
16444         gameInfo.timeControl = TimeControlTagValue();
16445         break;
16446
16447       case EditGame:
16448         gameInfo.event = StrSave("Edited game");
16449         gameInfo.site = StrSave(HostName());
16450         gameInfo.date = PGNDate();
16451         gameInfo.round = StrSave("-");
16452         gameInfo.white = StrSave("-");
16453         gameInfo.black = StrSave("-");
16454         gameInfo.result = r;
16455         gameInfo.resultDetails = p;
16456         break;
16457
16458       case EditPosition:
16459         gameInfo.event = StrSave("Edited position");
16460         gameInfo.site = StrSave(HostName());
16461         gameInfo.date = PGNDate();
16462         gameInfo.round = StrSave("-");
16463         gameInfo.white = StrSave("-");
16464         gameInfo.black = StrSave("-");
16465         break;
16466
16467       case IcsPlayingWhite:
16468       case IcsPlayingBlack:
16469       case IcsObserving:
16470       case IcsExamining:
16471         break;
16472
16473       case PlayFromGameFile:
16474         gameInfo.event = StrSave("Game from non-PGN file");
16475         gameInfo.site = StrSave(HostName());
16476         gameInfo.date = PGNDate();
16477         gameInfo.round = StrSave("-");
16478         gameInfo.white = StrSave("?");
16479         gameInfo.black = StrSave("?");
16480         break;
16481
16482       default:
16483         break;
16484     }
16485 }
16486
16487 void
16488 ReplaceComment (int index, char *text)
16489 {
16490     int len;
16491     char *p;
16492     float score;
16493
16494     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16495        pvInfoList[index-1].depth == len &&
16496        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16497        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16498     while (*text == '\n') text++;
16499     len = strlen(text);
16500     while (len > 0 && text[len - 1] == '\n') len--;
16501
16502     if (commentList[index] != NULL)
16503       free(commentList[index]);
16504
16505     if (len == 0) {
16506         commentList[index] = NULL;
16507         return;
16508     }
16509   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16510       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16511       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16512     commentList[index] = (char *) malloc(len + 2);
16513     strncpy(commentList[index], text, len);
16514     commentList[index][len] = '\n';
16515     commentList[index][len + 1] = NULLCHAR;
16516   } else {
16517     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16518     char *p;
16519     commentList[index] = (char *) malloc(len + 7);
16520     safeStrCpy(commentList[index], "{\n", 3);
16521     safeStrCpy(commentList[index]+2, text, len+1);
16522     commentList[index][len+2] = NULLCHAR;
16523     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16524     strcat(commentList[index], "\n}\n");
16525   }
16526 }
16527
16528 void
16529 CrushCRs (char *text)
16530 {
16531   char *p = text;
16532   char *q = text;
16533   char ch;
16534
16535   do {
16536     ch = *p++;
16537     if (ch == '\r') continue;
16538     *q++ = ch;
16539   } while (ch != '\0');
16540 }
16541
16542 void
16543 AppendComment (int index, char *text, Boolean addBraces)
16544 /* addBraces  tells if we should add {} */
16545 {
16546     int oldlen, len;
16547     char *old;
16548
16549 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16550     if(addBraces == 3) addBraces = 0; else // force appending literally
16551     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16552
16553     CrushCRs(text);
16554     while (*text == '\n') text++;
16555     len = strlen(text);
16556     while (len > 0 && text[len - 1] == '\n') len--;
16557     text[len] = NULLCHAR;
16558
16559     if (len == 0) return;
16560
16561     if (commentList[index] != NULL) {
16562       Boolean addClosingBrace = addBraces;
16563         old = commentList[index];
16564         oldlen = strlen(old);
16565         while(commentList[index][oldlen-1] ==  '\n')
16566           commentList[index][--oldlen] = NULLCHAR;
16567         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16568         safeStrCpy(commentList[index], old, oldlen + len + 6);
16569         free(old);
16570         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16571         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16572           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16573           while (*text == '\n') { text++; len--; }
16574           commentList[index][--oldlen] = NULLCHAR;
16575       }
16576         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16577         else          strcat(commentList[index], "\n");
16578         strcat(commentList[index], text);
16579         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16580         else          strcat(commentList[index], "\n");
16581     } else {
16582         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16583         if(addBraces)
16584           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16585         else commentList[index][0] = NULLCHAR;
16586         strcat(commentList[index], text);
16587         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16588         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16589     }
16590 }
16591
16592 static char *
16593 FindStr (char * text, char * sub_text)
16594 {
16595     char * result = strstr( text, sub_text );
16596
16597     if( result != NULL ) {
16598         result += strlen( sub_text );
16599     }
16600
16601     return result;
16602 }
16603
16604 /* [AS] Try to extract PV info from PGN comment */
16605 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16606 char *
16607 GetInfoFromComment (int index, char * text)
16608 {
16609     char * sep = text, *p;
16610
16611     if( text != NULL && index > 0 ) {
16612         int score = 0;
16613         int depth = 0;
16614         int time = -1, sec = 0, deci;
16615         char * s_eval = FindStr( text, "[%eval " );
16616         char * s_emt = FindStr( text, "[%emt " );
16617 #if 0
16618         if( s_eval != NULL || s_emt != NULL ) {
16619 #else
16620         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16621 #endif
16622             /* New style */
16623             char delim;
16624
16625             if( s_eval != NULL ) {
16626                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16627                     return text;
16628                 }
16629
16630                 if( delim != ']' ) {
16631                     return text;
16632                 }
16633             }
16634
16635             if( s_emt != NULL ) {
16636             }
16637                 return text;
16638         }
16639         else {
16640             /* We expect something like: [+|-]nnn.nn/dd */
16641             int score_lo = 0;
16642
16643             if(*text != '{') return text; // [HGM] braces: must be normal comment
16644
16645             sep = strchr( text, '/' );
16646             if( sep == NULL || sep < (text+4) ) {
16647                 return text;
16648             }
16649
16650             p = text;
16651             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16652             if(p[1] == '(') { // comment starts with PV
16653                p = strchr(p, ')'); // locate end of PV
16654                if(p == NULL || sep < p+5) return text;
16655                // at this point we have something like "{(.*) +0.23/6 ..."
16656                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16657                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16658                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16659             }
16660             time = -1; sec = -1; deci = -1;
16661             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16662                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16663                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16664                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16665                 return text;
16666             }
16667
16668             if( score_lo < 0 || score_lo >= 100 ) {
16669                 return text;
16670             }
16671
16672             if(sec >= 0) time = 600*time + 10*sec; else
16673             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16674
16675             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16676
16677             /* [HGM] PV time: now locate end of PV info */
16678             while( *++sep >= '0' && *sep <= '9'); // strip depth
16679             if(time >= 0)
16680             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16681             if(sec >= 0)
16682             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16683             if(deci >= 0)
16684             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16685             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16686         }
16687
16688         if( depth <= 0 ) {
16689             return text;
16690         }
16691
16692         if( time < 0 ) {
16693             time = -1;
16694         }
16695
16696         pvInfoList[index-1].depth = depth;
16697         pvInfoList[index-1].score = score;
16698         pvInfoList[index-1].time  = 10*time; // centi-sec
16699         if(*sep == '}') *sep = 0; else *--sep = '{';
16700         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16701     }
16702     return sep;
16703 }
16704
16705 void
16706 SendToProgram (char *message, ChessProgramState *cps)
16707 {
16708     int count, outCount, error;
16709     char buf[MSG_SIZ];
16710
16711     if (cps->pr == NoProc) return;
16712     Attention(cps);
16713
16714     if (appData.debugMode) {
16715         TimeMark now;
16716         GetTimeMark(&now);
16717         fprintf(debugFP, "%ld >%-6s: %s",
16718                 SubtractTimeMarks(&now, &programStartTime),
16719                 cps->which, message);
16720         if(serverFP)
16721             fprintf(serverFP, "%ld >%-6s: %s",
16722                 SubtractTimeMarks(&now, &programStartTime),
16723                 cps->which, message), fflush(serverFP);
16724     }
16725
16726     count = strlen(message);
16727     outCount = OutputToProcess(cps->pr, message, count, &error);
16728     if (outCount < count && !exiting
16729                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16730       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16731       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16732         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16733             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16734                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16735                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16736                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16737             } else {
16738                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16739                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16740                 gameInfo.result = res;
16741             }
16742             gameInfo.resultDetails = StrSave(buf);
16743         }
16744         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16745         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16746     }
16747 }
16748
16749 void
16750 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16751 {
16752     char *end_str;
16753     char buf[MSG_SIZ];
16754     ChessProgramState *cps = (ChessProgramState *)closure;
16755
16756     if (isr != cps->isr) return; /* Killed intentionally */
16757     if (count <= 0) {
16758         if (count == 0) {
16759             RemoveInputSource(cps->isr);
16760             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16761                     _(cps->which), cps->program);
16762             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16763             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16764                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16765                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16766                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16767                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16768                 } else {
16769                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16770                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16771                     gameInfo.result = res;
16772                 }
16773                 gameInfo.resultDetails = StrSave(buf);
16774             }
16775             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16776             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16777         } else {
16778             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16779                     _(cps->which), cps->program);
16780             RemoveInputSource(cps->isr);
16781
16782             /* [AS] Program is misbehaving badly... kill it */
16783             if( count == -2 ) {
16784                 DestroyChildProcess( cps->pr, 9 );
16785                 cps->pr = NoProc;
16786             }
16787
16788             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16789         }
16790         return;
16791     }
16792
16793     if ((end_str = strchr(message, '\r')) != NULL)
16794       *end_str = NULLCHAR;
16795     if ((end_str = strchr(message, '\n')) != NULL)
16796       *end_str = NULLCHAR;
16797
16798     if (appData.debugMode) {
16799         TimeMark now; int print = 1;
16800         char *quote = ""; char c; int i;
16801
16802         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16803                 char start = message[0];
16804                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16805                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16806                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16807                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16808                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16809                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16810                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16811                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16812                    sscanf(message, "hint: %c", &c)!=1 &&
16813                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16814                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16815                     print = (appData.engineComments >= 2);
16816                 }
16817                 message[0] = start; // restore original message
16818         }
16819         if(print) {
16820                 GetTimeMark(&now);
16821                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16822                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16823                         quote,
16824                         message);
16825                 if(serverFP)
16826                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16827                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16828                         quote,
16829                         message), fflush(serverFP);
16830         }
16831     }
16832
16833     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16834     if (appData.icsEngineAnalyze) {
16835         if (strstr(message, "whisper") != NULL ||
16836              strstr(message, "kibitz") != NULL ||
16837             strstr(message, "tellics") != NULL) return;
16838     }
16839
16840     HandleMachineMove(message, cps);
16841 }
16842
16843
16844 void
16845 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16846 {
16847     char buf[MSG_SIZ];
16848     int seconds;
16849
16850     if( timeControl_2 > 0 ) {
16851         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16852             tc = timeControl_2;
16853         }
16854     }
16855     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16856     inc /= cps->timeOdds;
16857     st  /= cps->timeOdds;
16858
16859     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16860
16861     if (st > 0) {
16862       /* Set exact time per move, normally using st command */
16863       if (cps->stKludge) {
16864         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16865         seconds = st % 60;
16866         if (seconds == 0) {
16867           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16868         } else {
16869           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16870         }
16871       } else {
16872         snprintf(buf, MSG_SIZ, "st %d\n", st);
16873       }
16874     } else {
16875       /* Set conventional or incremental time control, using level command */
16876       if (seconds == 0) {
16877         /* Note old gnuchess bug -- minutes:seconds used to not work.
16878            Fixed in later versions, but still avoid :seconds
16879            when seconds is 0. */
16880         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16881       } else {
16882         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16883                  seconds, inc/1000.);
16884       }
16885     }
16886     SendToProgram(buf, cps);
16887
16888     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16889     /* Orthogonally, limit search to given depth */
16890     if (sd > 0) {
16891       if (cps->sdKludge) {
16892         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16893       } else {
16894         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16895       }
16896       SendToProgram(buf, cps);
16897     }
16898
16899     if(cps->nps >= 0) { /* [HGM] nps */
16900         if(cps->supportsNPS == FALSE)
16901           cps->nps = -1; // don't use if engine explicitly says not supported!
16902         else {
16903           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16904           SendToProgram(buf, cps);
16905         }
16906     }
16907 }
16908
16909 ChessProgramState *
16910 WhitePlayer ()
16911 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16912 {
16913     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16914        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16915         return &second;
16916     return &first;
16917 }
16918
16919 void
16920 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16921 {
16922     char message[MSG_SIZ];
16923     long time, otime;
16924
16925     /* Note: this routine must be called when the clocks are stopped
16926        or when they have *just* been set or switched; otherwise
16927        it will be off by the time since the current tick started.
16928     */
16929     if (machineWhite) {
16930         time = whiteTimeRemaining / 10;
16931         otime = blackTimeRemaining / 10;
16932     } else {
16933         time = blackTimeRemaining / 10;
16934         otime = whiteTimeRemaining / 10;
16935     }
16936     /* [HGM] translate opponent's time by time-odds factor */
16937     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16938
16939     if (time <= 0) time = 1;
16940     if (otime <= 0) otime = 1;
16941
16942     snprintf(message, MSG_SIZ, "time %ld\n", time);
16943     SendToProgram(message, cps);
16944
16945     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16946     SendToProgram(message, cps);
16947 }
16948
16949 char *
16950 EngineDefinedVariant (ChessProgramState *cps, int n)
16951 {   // return name of n-th unknown variant that engine supports
16952     static char buf[MSG_SIZ];
16953     char *p, *s = cps->variants;
16954     if(!s) return NULL;
16955     do { // parse string from variants feature
16956       VariantClass v;
16957         p = strchr(s, ',');
16958         if(p) *p = NULLCHAR;
16959       v = StringToVariant(s);
16960       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16961         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16962             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16963                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16964                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16965                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16966             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16967         }
16968         if(p) *p++ = ',';
16969         if(n < 0) return buf;
16970     } while(s = p);
16971     return NULL;
16972 }
16973
16974 int
16975 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16976 {
16977   char buf[MSG_SIZ];
16978   int len = strlen(name);
16979   int val;
16980
16981   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16982     (*p) += len + 1;
16983     sscanf(*p, "%d", &val);
16984     *loc = (val != 0);
16985     while (**p && **p != ' ')
16986       (*p)++;
16987     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16988     SendToProgram(buf, cps);
16989     return TRUE;
16990   }
16991   return FALSE;
16992 }
16993
16994 int
16995 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16996 {
16997   char buf[MSG_SIZ];
16998   int len = strlen(name);
16999   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17000     (*p) += len + 1;
17001     sscanf(*p, "%d", loc);
17002     while (**p && **p != ' ') (*p)++;
17003     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17004     SendToProgram(buf, cps);
17005     return TRUE;
17006   }
17007   return FALSE;
17008 }
17009
17010 int
17011 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17012 {
17013   char buf[MSG_SIZ];
17014   int len = strlen(name);
17015   if (strncmp((*p), name, len) == 0
17016       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17017     (*p) += len + 2;
17018     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17019     sscanf(*p, "%[^\"]", *loc);
17020     while (**p && **p != '\"') (*p)++;
17021     if (**p == '\"') (*p)++;
17022     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17023     SendToProgram(buf, cps);
17024     return TRUE;
17025   }
17026   return FALSE;
17027 }
17028
17029 int
17030 ParseOption (Option *opt, ChessProgramState *cps)
17031 // [HGM] options: process the string that defines an engine option, and determine
17032 // name, type, default value, and allowed value range
17033 {
17034         char *p, *q, buf[MSG_SIZ];
17035         int n, min = (-1)<<31, max = 1<<31, def;
17036
17037         opt->target = &opt->value;   // OK for spin/slider and checkbox
17038         if(p = strstr(opt->name, " -spin ")) {
17039             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17040             if(max < min) max = min; // enforce consistency
17041             if(def < min) def = min;
17042             if(def > max) def = max;
17043             opt->value = def;
17044             opt->min = min;
17045             opt->max = max;
17046             opt->type = Spin;
17047         } else if((p = strstr(opt->name, " -slider "))) {
17048             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17049             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17050             if(max < min) max = min; // enforce consistency
17051             if(def < min) def = min;
17052             if(def > max) def = max;
17053             opt->value = def;
17054             opt->min = min;
17055             opt->max = max;
17056             opt->type = Spin; // Slider;
17057         } else if((p = strstr(opt->name, " -string "))) {
17058             opt->textValue = p+9;
17059             opt->type = TextBox;
17060             opt->target = &opt->textValue;
17061         } else if((p = strstr(opt->name, " -file "))) {
17062             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17063             opt->target = opt->textValue = p+7;
17064             opt->type = FileName; // FileName;
17065             opt->target = &opt->textValue;
17066         } else if((p = strstr(opt->name, " -path "))) {
17067             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17068             opt->target = opt->textValue = p+7;
17069             opt->type = PathName; // PathName;
17070             opt->target = &opt->textValue;
17071         } else if(p = strstr(opt->name, " -check ")) {
17072             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17073             opt->value = (def != 0);
17074             opt->type = CheckBox;
17075         } else if(p = strstr(opt->name, " -combo ")) {
17076             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17077             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17078             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17079             opt->value = n = 0;
17080             while(q = StrStr(q, " /// ")) {
17081                 n++; *q = 0;    // count choices, and null-terminate each of them
17082                 q += 5;
17083                 if(*q == '*') { // remember default, which is marked with * prefix
17084                     q++;
17085                     opt->value = n;
17086                 }
17087                 cps->comboList[cps->comboCnt++] = q;
17088             }
17089             cps->comboList[cps->comboCnt++] = NULL;
17090             opt->max = n + 1;
17091             opt->type = ComboBox;
17092         } else if(p = strstr(opt->name, " -button")) {
17093             opt->type = Button;
17094         } else if(p = strstr(opt->name, " -save")) {
17095             opt->type = SaveButton;
17096         } else return FALSE;
17097         *p = 0; // terminate option name
17098         // now look if the command-line options define a setting for this engine option.
17099         if(cps->optionSettings && cps->optionSettings[0])
17100             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17101         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17102           snprintf(buf, MSG_SIZ, "option %s", p);
17103                 if(p = strstr(buf, ",")) *p = 0;
17104                 if(q = strchr(buf, '=')) switch(opt->type) {
17105                     case ComboBox:
17106                         for(n=0; n<opt->max; n++)
17107                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17108                         break;
17109                     case TextBox:
17110                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17111                         break;
17112                     case Spin:
17113                     case CheckBox:
17114                         opt->value = atoi(q+1);
17115                     default:
17116                         break;
17117                 }
17118                 strcat(buf, "\n");
17119                 SendToProgram(buf, cps);
17120         }
17121         return TRUE;
17122 }
17123
17124 void
17125 FeatureDone (ChessProgramState *cps, int val)
17126 {
17127   DelayedEventCallback cb = GetDelayedEvent();
17128   if ((cb == InitBackEnd3 && cps == &first) ||
17129       (cb == SettingsMenuIfReady && cps == &second) ||
17130       (cb == LoadEngine) ||
17131       (cb == TwoMachinesEventIfReady)) {
17132     CancelDelayedEvent();
17133     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17134   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17135   cps->initDone = val;
17136   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17137 }
17138
17139 /* Parse feature command from engine */
17140 void
17141 ParseFeatures (char *args, ChessProgramState *cps)
17142 {
17143   char *p = args;
17144   char *q = NULL;
17145   int val;
17146   char buf[MSG_SIZ];
17147
17148   for (;;) {
17149     while (*p == ' ') p++;
17150     if (*p == NULLCHAR) return;
17151
17152     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17153     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17154     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17155     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17156     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17157     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17158     if (BoolFeature(&p, "reuse", &val, cps)) {
17159       /* Engine can disable reuse, but can't enable it if user said no */
17160       if (!val) cps->reuse = FALSE;
17161       continue;
17162     }
17163     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17164     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17165       if (gameMode == TwoMachinesPlay) {
17166         DisplayTwoMachinesTitle();
17167       } else {
17168         DisplayTitle("");
17169       }
17170       continue;
17171     }
17172     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17173     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17174     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17175     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17176     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17177     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17178     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17179     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17180     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17181     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17182     if (IntFeature(&p, "done", &val, cps)) {
17183       FeatureDone(cps, val);
17184       continue;
17185     }
17186     /* Added by Tord: */
17187     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17188     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17189     /* End of additions by Tord */
17190
17191     /* [HGM] added features: */
17192     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17193     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17194     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17195     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17196     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17197     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17198     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17199     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17200         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17201         FREE(cps->option[cps->nrOptions].name);
17202         cps->option[cps->nrOptions].name = q; q = NULL;
17203         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17204           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17205             SendToProgram(buf, cps);
17206             continue;
17207         }
17208         if(cps->nrOptions >= MAX_OPTIONS) {
17209             cps->nrOptions--;
17210             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17211             DisplayError(buf, 0);
17212         }
17213         continue;
17214     }
17215     /* End of additions by HGM */
17216
17217     /* unknown feature: complain and skip */
17218     q = p;
17219     while (*q && *q != '=') q++;
17220     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17221     SendToProgram(buf, cps);
17222     p = q;
17223     if (*p == '=') {
17224       p++;
17225       if (*p == '\"') {
17226         p++;
17227         while (*p && *p != '\"') p++;
17228         if (*p == '\"') p++;
17229       } else {
17230         while (*p && *p != ' ') p++;
17231       }
17232     }
17233   }
17234
17235 }
17236
17237 void
17238 PeriodicUpdatesEvent (int newState)
17239 {
17240     if (newState == appData.periodicUpdates)
17241       return;
17242
17243     appData.periodicUpdates=newState;
17244
17245     /* Display type changes, so update it now */
17246 //    DisplayAnalysis();
17247
17248     /* Get the ball rolling again... */
17249     if (newState) {
17250         AnalysisPeriodicEvent(1);
17251         StartAnalysisClock();
17252     }
17253 }
17254
17255 void
17256 PonderNextMoveEvent (int newState)
17257 {
17258     if (newState == appData.ponderNextMove) return;
17259     if (gameMode == EditPosition) EditPositionDone(TRUE);
17260     if (newState) {
17261         SendToProgram("hard\n", &first);
17262         if (gameMode == TwoMachinesPlay) {
17263             SendToProgram("hard\n", &second);
17264         }
17265     } else {
17266         SendToProgram("easy\n", &first);
17267         thinkOutput[0] = NULLCHAR;
17268         if (gameMode == TwoMachinesPlay) {
17269             SendToProgram("easy\n", &second);
17270         }
17271     }
17272     appData.ponderNextMove = newState;
17273 }
17274
17275 void
17276 NewSettingEvent (int option, int *feature, char *command, int value)
17277 {
17278     char buf[MSG_SIZ];
17279
17280     if (gameMode == EditPosition) EditPositionDone(TRUE);
17281     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17282     if(feature == NULL || *feature) SendToProgram(buf, &first);
17283     if (gameMode == TwoMachinesPlay) {
17284         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17285     }
17286 }
17287
17288 void
17289 ShowThinkingEvent ()
17290 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17291 {
17292     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17293     int newState = appData.showThinking
17294         // [HGM] thinking: other features now need thinking output as well
17295         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17296
17297     if (oldState == newState) return;
17298     oldState = newState;
17299     if (gameMode == EditPosition) EditPositionDone(TRUE);
17300     if (oldState) {
17301         SendToProgram("post\n", &first);
17302         if (gameMode == TwoMachinesPlay) {
17303             SendToProgram("post\n", &second);
17304         }
17305     } else {
17306         SendToProgram("nopost\n", &first);
17307         thinkOutput[0] = NULLCHAR;
17308         if (gameMode == TwoMachinesPlay) {
17309             SendToProgram("nopost\n", &second);
17310         }
17311     }
17312 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17313 }
17314
17315 void
17316 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17317 {
17318   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17319   if (pr == NoProc) return;
17320   AskQuestion(title, question, replyPrefix, pr);
17321 }
17322
17323 void
17324 TypeInEvent (char firstChar)
17325 {
17326     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17327         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17328         gameMode == AnalyzeMode || gameMode == EditGame ||
17329         gameMode == EditPosition || gameMode == IcsExamining ||
17330         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17331         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17332                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17333                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17334         gameMode == Training) PopUpMoveDialog(firstChar);
17335 }
17336
17337 void
17338 TypeInDoneEvent (char *move)
17339 {
17340         Board board;
17341         int n, fromX, fromY, toX, toY;
17342         char promoChar;
17343         ChessMove moveType;
17344
17345         // [HGM] FENedit
17346         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17347                 EditPositionPasteFEN(move);
17348                 return;
17349         }
17350         // [HGM] movenum: allow move number to be typed in any mode
17351         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17352           ToNrEvent(2*n-1);
17353           return;
17354         }
17355         // undocumented kludge: allow command-line option to be typed in!
17356         // (potentially fatal, and does not implement the effect of the option.)
17357         // should only be used for options that are values on which future decisions will be made,
17358         // and definitely not on options that would be used during initialization.
17359         if(strstr(move, "!!! -") == move) {
17360             ParseArgsFromString(move+4);
17361             return;
17362         }
17363
17364       if (gameMode != EditGame && currentMove != forwardMostMove &&
17365         gameMode != Training) {
17366         DisplayMoveError(_("Displayed move is not current"));
17367       } else {
17368         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17369           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17370         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17371         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17372           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17373           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17374         } else {
17375           DisplayMoveError(_("Could not parse move"));
17376         }
17377       }
17378 }
17379
17380 void
17381 DisplayMove (int moveNumber)
17382 {
17383     char message[MSG_SIZ];
17384     char res[MSG_SIZ];
17385     char cpThinkOutput[MSG_SIZ];
17386
17387     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17388
17389     if (moveNumber == forwardMostMove - 1 ||
17390         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17391
17392         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17393
17394         if (strchr(cpThinkOutput, '\n')) {
17395             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17396         }
17397     } else {
17398         *cpThinkOutput = NULLCHAR;
17399     }
17400
17401     /* [AS] Hide thinking from human user */
17402     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17403         *cpThinkOutput = NULLCHAR;
17404         if( thinkOutput[0] != NULLCHAR ) {
17405             int i;
17406
17407             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17408                 cpThinkOutput[i] = '.';
17409             }
17410             cpThinkOutput[i] = NULLCHAR;
17411             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17412         }
17413     }
17414
17415     if (moveNumber == forwardMostMove - 1 &&
17416         gameInfo.resultDetails != NULL) {
17417         if (gameInfo.resultDetails[0] == NULLCHAR) {
17418           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17419         } else {
17420           snprintf(res, MSG_SIZ, " {%s} %s",
17421                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17422         }
17423     } else {
17424         res[0] = NULLCHAR;
17425     }
17426
17427     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17428         DisplayMessage(res, cpThinkOutput);
17429     } else {
17430       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17431                 WhiteOnMove(moveNumber) ? " " : ".. ",
17432                 parseList[moveNumber], res);
17433         DisplayMessage(message, cpThinkOutput);
17434     }
17435 }
17436
17437 void
17438 DisplayComment (int moveNumber, char *text)
17439 {
17440     char title[MSG_SIZ];
17441
17442     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17443       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17444     } else {
17445       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17446               WhiteOnMove(moveNumber) ? " " : ".. ",
17447               parseList[moveNumber]);
17448     }
17449     if (text != NULL && (appData.autoDisplayComment || commentUp))
17450         CommentPopUp(title, text);
17451 }
17452
17453 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17454  * might be busy thinking or pondering.  It can be omitted if your
17455  * gnuchess is configured to stop thinking immediately on any user
17456  * input.  However, that gnuchess feature depends on the FIONREAD
17457  * ioctl, which does not work properly on some flavors of Unix.
17458  */
17459 void
17460 Attention (ChessProgramState *cps)
17461 {
17462 #if ATTENTION
17463     if (!cps->useSigint) return;
17464     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17465     switch (gameMode) {
17466       case MachinePlaysWhite:
17467       case MachinePlaysBlack:
17468       case TwoMachinesPlay:
17469       case IcsPlayingWhite:
17470       case IcsPlayingBlack:
17471       case AnalyzeMode:
17472       case AnalyzeFile:
17473         /* Skip if we know it isn't thinking */
17474         if (!cps->maybeThinking) return;
17475         if (appData.debugMode)
17476           fprintf(debugFP, "Interrupting %s\n", cps->which);
17477         InterruptChildProcess(cps->pr);
17478         cps->maybeThinking = FALSE;
17479         break;
17480       default:
17481         break;
17482     }
17483 #endif /*ATTENTION*/
17484 }
17485
17486 int
17487 CheckFlags ()
17488 {
17489     if (whiteTimeRemaining <= 0) {
17490         if (!whiteFlag) {
17491             whiteFlag = TRUE;
17492             if (appData.icsActive) {
17493                 if (appData.autoCallFlag &&
17494                     gameMode == IcsPlayingBlack && !blackFlag) {
17495                   SendToICS(ics_prefix);
17496                   SendToICS("flag\n");
17497                 }
17498             } else {
17499                 if (blackFlag) {
17500                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17501                 } else {
17502                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17503                     if (appData.autoCallFlag) {
17504                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17505                         return TRUE;
17506                     }
17507                 }
17508             }
17509         }
17510     }
17511     if (blackTimeRemaining <= 0) {
17512         if (!blackFlag) {
17513             blackFlag = TRUE;
17514             if (appData.icsActive) {
17515                 if (appData.autoCallFlag &&
17516                     gameMode == IcsPlayingWhite && !whiteFlag) {
17517                   SendToICS(ics_prefix);
17518                   SendToICS("flag\n");
17519                 }
17520             } else {
17521                 if (whiteFlag) {
17522                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17523                 } else {
17524                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17525                     if (appData.autoCallFlag) {
17526                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17527                         return TRUE;
17528                     }
17529                 }
17530             }
17531         }
17532     }
17533     return FALSE;
17534 }
17535
17536 void
17537 CheckTimeControl ()
17538 {
17539     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17540         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17541
17542     /*
17543      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17544      */
17545     if ( !WhiteOnMove(forwardMostMove) ) {
17546         /* White made time control */
17547         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17548         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17549         /* [HGM] time odds: correct new time quota for time odds! */
17550                                             / WhitePlayer()->timeOdds;
17551         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17552     } else {
17553         lastBlack -= blackTimeRemaining;
17554         /* Black made time control */
17555         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17556                                             / WhitePlayer()->other->timeOdds;
17557         lastWhite = whiteTimeRemaining;
17558     }
17559 }
17560
17561 void
17562 DisplayBothClocks ()
17563 {
17564     int wom = gameMode == EditPosition ?
17565       !blackPlaysFirst : WhiteOnMove(currentMove);
17566     DisplayWhiteClock(whiteTimeRemaining, wom);
17567     DisplayBlackClock(blackTimeRemaining, !wom);
17568 }
17569
17570
17571 /* Timekeeping seems to be a portability nightmare.  I think everyone
17572    has ftime(), but I'm really not sure, so I'm including some ifdefs
17573    to use other calls if you don't.  Clocks will be less accurate if
17574    you have neither ftime nor gettimeofday.
17575 */
17576
17577 /* VS 2008 requires the #include outside of the function */
17578 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17579 #include <sys/timeb.h>
17580 #endif
17581
17582 /* Get the current time as a TimeMark */
17583 void
17584 GetTimeMark (TimeMark *tm)
17585 {
17586 #if HAVE_GETTIMEOFDAY
17587
17588     struct timeval timeVal;
17589     struct timezone timeZone;
17590
17591     gettimeofday(&timeVal, &timeZone);
17592     tm->sec = (long) timeVal.tv_sec;
17593     tm->ms = (int) (timeVal.tv_usec / 1000L);
17594
17595 #else /*!HAVE_GETTIMEOFDAY*/
17596 #if HAVE_FTIME
17597
17598 // include <sys/timeb.h> / moved to just above start of function
17599     struct timeb timeB;
17600
17601     ftime(&timeB);
17602     tm->sec = (long) timeB.time;
17603     tm->ms = (int) timeB.millitm;
17604
17605 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17606     tm->sec = (long) time(NULL);
17607     tm->ms = 0;
17608 #endif
17609 #endif
17610 }
17611
17612 /* Return the difference in milliseconds between two
17613    time marks.  We assume the difference will fit in a long!
17614 */
17615 long
17616 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17617 {
17618     return 1000L*(tm2->sec - tm1->sec) +
17619            (long) (tm2->ms - tm1->ms);
17620 }
17621
17622
17623 /*
17624  * Code to manage the game clocks.
17625  *
17626  * In tournament play, black starts the clock and then white makes a move.
17627  * We give the human user a slight advantage if he is playing white---the
17628  * clocks don't run until he makes his first move, so it takes zero time.
17629  * Also, we don't account for network lag, so we could get out of sync
17630  * with GNU Chess's clock -- but then, referees are always right.
17631  */
17632
17633 static TimeMark tickStartTM;
17634 static long intendedTickLength;
17635
17636 long
17637 NextTickLength (long timeRemaining)
17638 {
17639     long nominalTickLength, nextTickLength;
17640
17641     if (timeRemaining > 0L && timeRemaining <= 10000L)
17642       nominalTickLength = 100L;
17643     else
17644       nominalTickLength = 1000L;
17645     nextTickLength = timeRemaining % nominalTickLength;
17646     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17647
17648     return nextTickLength;
17649 }
17650
17651 /* Adjust clock one minute up or down */
17652 void
17653 AdjustClock (Boolean which, int dir)
17654 {
17655     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17656     if(which) blackTimeRemaining += 60000*dir;
17657     else      whiteTimeRemaining += 60000*dir;
17658     DisplayBothClocks();
17659     adjustedClock = TRUE;
17660 }
17661
17662 /* Stop clocks and reset to a fresh time control */
17663 void
17664 ResetClocks ()
17665 {
17666     (void) StopClockTimer();
17667     if (appData.icsActive) {
17668         whiteTimeRemaining = blackTimeRemaining = 0;
17669     } else if (searchTime) {
17670         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17671         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17672     } else { /* [HGM] correct new time quote for time odds */
17673         whiteTC = blackTC = fullTimeControlString;
17674         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17675         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17676     }
17677     if (whiteFlag || blackFlag) {
17678         DisplayTitle("");
17679         whiteFlag = blackFlag = FALSE;
17680     }
17681     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17682     DisplayBothClocks();
17683     adjustedClock = FALSE;
17684 }
17685
17686 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17687
17688 /* Decrement running clock by amount of time that has passed */
17689 void
17690 DecrementClocks ()
17691 {
17692     long timeRemaining;
17693     long lastTickLength, fudge;
17694     TimeMark now;
17695
17696     if (!appData.clockMode) return;
17697     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17698
17699     GetTimeMark(&now);
17700
17701     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17702
17703     /* Fudge if we woke up a little too soon */
17704     fudge = intendedTickLength - lastTickLength;
17705     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17706
17707     if (WhiteOnMove(forwardMostMove)) {
17708         if(whiteNPS >= 0) lastTickLength = 0;
17709         timeRemaining = whiteTimeRemaining -= lastTickLength;
17710         if(timeRemaining < 0 && !appData.icsActive) {
17711             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17712             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17713                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17714                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17715             }
17716         }
17717         DisplayWhiteClock(whiteTimeRemaining - fudge,
17718                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17719     } else {
17720         if(blackNPS >= 0) lastTickLength = 0;
17721         timeRemaining = blackTimeRemaining -= lastTickLength;
17722         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17723             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17724             if(suddenDeath) {
17725                 blackStartMove = forwardMostMove;
17726                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17727             }
17728         }
17729         DisplayBlackClock(blackTimeRemaining - fudge,
17730                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17731     }
17732     if (CheckFlags()) return;
17733
17734     if(twoBoards) { // count down secondary board's clocks as well
17735         activePartnerTime -= lastTickLength;
17736         partnerUp = 1;
17737         if(activePartner == 'W')
17738             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17739         else
17740             DisplayBlackClock(activePartnerTime, TRUE);
17741         partnerUp = 0;
17742     }
17743
17744     tickStartTM = now;
17745     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17746     StartClockTimer(intendedTickLength);
17747
17748     /* if the time remaining has fallen below the alarm threshold, sound the
17749      * alarm. if the alarm has sounded and (due to a takeback or time control
17750      * with increment) the time remaining has increased to a level above the
17751      * threshold, reset the alarm so it can sound again.
17752      */
17753
17754     if (appData.icsActive && appData.icsAlarm) {
17755
17756         /* make sure we are dealing with the user's clock */
17757         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17758                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17759            )) return;
17760
17761         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17762             alarmSounded = FALSE;
17763         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17764             PlayAlarmSound();
17765             alarmSounded = TRUE;
17766         }
17767     }
17768 }
17769
17770
17771 /* A player has just moved, so stop the previously running
17772    clock and (if in clock mode) start the other one.
17773    We redisplay both clocks in case we're in ICS mode, because
17774    ICS gives us an update to both clocks after every move.
17775    Note that this routine is called *after* forwardMostMove
17776    is updated, so the last fractional tick must be subtracted
17777    from the color that is *not* on move now.
17778 */
17779 void
17780 SwitchClocks (int newMoveNr)
17781 {
17782     long lastTickLength;
17783     TimeMark now;
17784     int flagged = FALSE;
17785
17786     GetTimeMark(&now);
17787
17788     if (StopClockTimer() && appData.clockMode) {
17789         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17790         if (!WhiteOnMove(forwardMostMove)) {
17791             if(blackNPS >= 0) lastTickLength = 0;
17792             blackTimeRemaining -= lastTickLength;
17793            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17794 //         if(pvInfoList[forwardMostMove].time == -1)
17795                  pvInfoList[forwardMostMove].time =               // use GUI time
17796                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17797         } else {
17798            if(whiteNPS >= 0) lastTickLength = 0;
17799            whiteTimeRemaining -= lastTickLength;
17800            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17801 //         if(pvInfoList[forwardMostMove].time == -1)
17802                  pvInfoList[forwardMostMove].time =
17803                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17804         }
17805         flagged = CheckFlags();
17806     }
17807     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17808     CheckTimeControl();
17809
17810     if (flagged || !appData.clockMode) return;
17811
17812     switch (gameMode) {
17813       case MachinePlaysBlack:
17814       case MachinePlaysWhite:
17815       case BeginningOfGame:
17816         if (pausing) return;
17817         break;
17818
17819       case EditGame:
17820       case PlayFromGameFile:
17821       case IcsExamining:
17822         return;
17823
17824       default:
17825         break;
17826     }
17827
17828     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17829         if(WhiteOnMove(forwardMostMove))
17830              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17831         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17832     }
17833
17834     tickStartTM = now;
17835     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17836       whiteTimeRemaining : blackTimeRemaining);
17837     StartClockTimer(intendedTickLength);
17838 }
17839
17840
17841 /* Stop both clocks */
17842 void
17843 StopClocks ()
17844 {
17845     long lastTickLength;
17846     TimeMark now;
17847
17848     if (!StopClockTimer()) return;
17849     if (!appData.clockMode) return;
17850
17851     GetTimeMark(&now);
17852
17853     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17854     if (WhiteOnMove(forwardMostMove)) {
17855         if(whiteNPS >= 0) lastTickLength = 0;
17856         whiteTimeRemaining -= lastTickLength;
17857         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17858     } else {
17859         if(blackNPS >= 0) lastTickLength = 0;
17860         blackTimeRemaining -= lastTickLength;
17861         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17862     }
17863     CheckFlags();
17864 }
17865
17866 /* Start clock of player on move.  Time may have been reset, so
17867    if clock is already running, stop and restart it. */
17868 void
17869 StartClocks ()
17870 {
17871     (void) StopClockTimer(); /* in case it was running already */
17872     DisplayBothClocks();
17873     if (CheckFlags()) return;
17874
17875     if (!appData.clockMode) return;
17876     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17877
17878     GetTimeMark(&tickStartTM);
17879     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17880       whiteTimeRemaining : blackTimeRemaining);
17881
17882    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17883     whiteNPS = blackNPS = -1;
17884     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17885        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17886         whiteNPS = first.nps;
17887     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17888        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17889         blackNPS = first.nps;
17890     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17891         whiteNPS = second.nps;
17892     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17893         blackNPS = second.nps;
17894     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17895
17896     StartClockTimer(intendedTickLength);
17897 }
17898
17899 char *
17900 TimeString (long ms)
17901 {
17902     long second, minute, hour, day;
17903     char *sign = "";
17904     static char buf[32];
17905
17906     if (ms > 0 && ms <= 9900) {
17907       /* convert milliseconds to tenths, rounding up */
17908       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17909
17910       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17911       return buf;
17912     }
17913
17914     /* convert milliseconds to seconds, rounding up */
17915     /* use floating point to avoid strangeness of integer division
17916        with negative dividends on many machines */
17917     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17918
17919     if (second < 0) {
17920         sign = "-";
17921         second = -second;
17922     }
17923
17924     day = second / (60 * 60 * 24);
17925     second = second % (60 * 60 * 24);
17926     hour = second / (60 * 60);
17927     second = second % (60 * 60);
17928     minute = second / 60;
17929     second = second % 60;
17930
17931     if (day > 0)
17932       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17933               sign, day, hour, minute, second);
17934     else if (hour > 0)
17935       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17936     else
17937       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17938
17939     return buf;
17940 }
17941
17942
17943 /*
17944  * This is necessary because some C libraries aren't ANSI C compliant yet.
17945  */
17946 char *
17947 StrStr (char *string, char *match)
17948 {
17949     int i, length;
17950
17951     length = strlen(match);
17952
17953     for (i = strlen(string) - length; i >= 0; i--, string++)
17954       if (!strncmp(match, string, length))
17955         return string;
17956
17957     return NULL;
17958 }
17959
17960 char *
17961 StrCaseStr (char *string, char *match)
17962 {
17963     int i, j, length;
17964
17965     length = strlen(match);
17966
17967     for (i = strlen(string) - length; i >= 0; i--, string++) {
17968         for (j = 0; j < length; j++) {
17969             if (ToLower(match[j]) != ToLower(string[j]))
17970               break;
17971         }
17972         if (j == length) return string;
17973     }
17974
17975     return NULL;
17976 }
17977
17978 #ifndef _amigados
17979 int
17980 StrCaseCmp (char *s1, char *s2)
17981 {
17982     char c1, c2;
17983
17984     for (;;) {
17985         c1 = ToLower(*s1++);
17986         c2 = ToLower(*s2++);
17987         if (c1 > c2) return 1;
17988         if (c1 < c2) return -1;
17989         if (c1 == NULLCHAR) return 0;
17990     }
17991 }
17992
17993
17994 int
17995 ToLower (int c)
17996 {
17997     return isupper(c) ? tolower(c) : c;
17998 }
17999
18000
18001 int
18002 ToUpper (int c)
18003 {
18004     return islower(c) ? toupper(c) : c;
18005 }
18006 #endif /* !_amigados    */
18007
18008 char *
18009 StrSave (char *s)
18010 {
18011   char *ret;
18012
18013   if ((ret = (char *) malloc(strlen(s) + 1)))
18014     {
18015       safeStrCpy(ret, s, strlen(s)+1);
18016     }
18017   return ret;
18018 }
18019
18020 char *
18021 StrSavePtr (char *s, char **savePtr)
18022 {
18023     if (*savePtr) {
18024         free(*savePtr);
18025     }
18026     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18027       safeStrCpy(*savePtr, s, strlen(s)+1);
18028     }
18029     return(*savePtr);
18030 }
18031
18032 char *
18033 PGNDate ()
18034 {
18035     time_t clock;
18036     struct tm *tm;
18037     char buf[MSG_SIZ];
18038
18039     clock = time((time_t *)NULL);
18040     tm = localtime(&clock);
18041     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18042             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18043     return StrSave(buf);
18044 }
18045
18046
18047 char *
18048 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18049 {
18050     int i, j, fromX, fromY, toX, toY;
18051     int whiteToPlay, haveRights = nrCastlingRights;
18052     char buf[MSG_SIZ];
18053     char *p, *q;
18054     int emptycount;
18055     ChessSquare piece;
18056
18057     whiteToPlay = (gameMode == EditPosition) ?
18058       !blackPlaysFirst : (move % 2 == 0);
18059     p = buf;
18060
18061     /* Piece placement data */
18062     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18063         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18064         emptycount = 0;
18065         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18066             if (boards[move][i][j] == EmptySquare) {
18067                 emptycount++;
18068             } else { ChessSquare piece = boards[move][i][j];
18069                 if (emptycount > 0) {
18070                     if(emptycount<10) /* [HGM] can be >= 10 */
18071                         *p++ = '0' + emptycount;
18072                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18073                     emptycount = 0;
18074                 }
18075                 if(PieceToChar(piece) == '+') {
18076                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18077                     *p++ = '+';
18078                     piece = (ChessSquare)(CHUDEMOTED(piece));
18079                 }
18080                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18081                 if(*p = PieceSuffix(piece)) p++;
18082                 if(p[-1] == '~') {
18083                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18084                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18085                     *p++ = '~';
18086                 }
18087             }
18088         }
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         *p++ = '/';
18096     }
18097     *(p - 1) = ' ';
18098
18099     /* [HGM] print Crazyhouse or Shogi holdings */
18100     if( gameInfo.holdingsWidth ) {
18101         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18102         q = p;
18103         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18104             piece = boards[move][i][BOARD_WIDTH-1];
18105             if( piece != EmptySquare )
18106               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18107                   *p++ = PieceToChar(piece);
18108         }
18109         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18110             piece = boards[move][BOARD_HEIGHT-i-1][0];
18111             if( piece != EmptySquare )
18112               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18113                   *p++ = PieceToChar(piece);
18114         }
18115
18116         if( q == p ) *p++ = '-';
18117         *p++ = ']';
18118         *p++ = ' ';
18119     }
18120
18121     /* Active color */
18122     *p++ = whiteToPlay ? 'w' : 'b';
18123     *p++ = ' ';
18124
18125   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18126     haveRights = 0; q = p;
18127     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18128       piece = boards[move][0][i];
18129       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18130         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18131       }
18132     }
18133     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18134       piece = boards[move][BOARD_HEIGHT-1][i];
18135       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18136         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18137       }
18138     }
18139     if(p == q) *p++ = '-';
18140     *p++ = ' ';
18141   }
18142
18143   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18144     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18145   } else {
18146   if(haveRights) {
18147      int handW=0, handB=0;
18148      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18149         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18150         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18151      }
18152      q = p;
18153      if(appData.fischerCastling) {
18154         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18155            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18156                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18157         } else {
18158        /* [HGM] write directly from rights */
18159            if(boards[move][CASTLING][2] != NoRights &&
18160               boards[move][CASTLING][0] != NoRights   )
18161                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18162            if(boards[move][CASTLING][2] != NoRights &&
18163               boards[move][CASTLING][1] != NoRights   )
18164                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18165         }
18166         if(handB) {
18167            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18168                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18169         } else {
18170            if(boards[move][CASTLING][5] != NoRights &&
18171               boards[move][CASTLING][3] != NoRights   )
18172                 *p++ = boards[move][CASTLING][3] + AAA;
18173            if(boards[move][CASTLING][5] != NoRights &&
18174               boards[move][CASTLING][4] != NoRights   )
18175                 *p++ = boards[move][CASTLING][4] + AAA;
18176         }
18177      } else {
18178
18179         /* [HGM] write true castling rights */
18180         if( nrCastlingRights == 6 ) {
18181             int q, k=0;
18182             if(boards[move][CASTLING][0] != NoRights &&
18183                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18184             q = (boards[move][CASTLING][1] != NoRights &&
18185                  boards[move][CASTLING][2] != NoRights  );
18186             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18187                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18188                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18189                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18190             }
18191             if(q) *p++ = 'Q';
18192             k = 0;
18193             if(boards[move][CASTLING][3] != NoRights &&
18194                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18195             q = (boards[move][CASTLING][4] != NoRights &&
18196                  boards[move][CASTLING][5] != NoRights  );
18197             if(handB) {
18198                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18199                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18200                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18201             }
18202             if(q) *p++ = 'q';
18203         }
18204      }
18205      if (q == p) *p++ = '-'; /* No castling rights */
18206      *p++ = ' ';
18207   }
18208
18209   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18210      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18211      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18212     /* En passant target square */
18213     if (move > backwardMostMove) {
18214         fromX = moveList[move - 1][0] - AAA;
18215         fromY = moveList[move - 1][1] - ONE;
18216         toX = moveList[move - 1][2] - AAA;
18217         toY = moveList[move - 1][3] - ONE;
18218         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18219             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18220             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18221             fromX == toX) {
18222             /* 2-square pawn move just happened */
18223             *p++ = toX + AAA;
18224             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18225         } else {
18226             *p++ = '-';
18227         }
18228     } else if(move == backwardMostMove) {
18229         // [HGM] perhaps we should always do it like this, and forget the above?
18230         if((signed char)boards[move][EP_STATUS] >= 0) {
18231             *p++ = boards[move][EP_STATUS] + AAA;
18232             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18233         } else {
18234             *p++ = '-';
18235         }
18236     } else {
18237         *p++ = '-';
18238     }
18239     *p++ = ' ';
18240   }
18241   }
18242
18243     if(moveCounts)
18244     {   int i = 0, j=move;
18245
18246         /* [HGM] find reversible plies */
18247         if (appData.debugMode) { int k;
18248             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18249             for(k=backwardMostMove; k<=forwardMostMove; k++)
18250                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18251
18252         }
18253
18254         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18255         if( j == backwardMostMove ) i += initialRulePlies;
18256         sprintf(p, "%d ", i);
18257         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18258
18259         /* Fullmove number */
18260         sprintf(p, "%d", (move / 2) + 1);
18261     } else *--p = NULLCHAR;
18262
18263     return StrSave(buf);
18264 }
18265
18266 Boolean
18267 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18268 {
18269     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18270     char *p, c;
18271     int emptycount, virgin[BOARD_FILES];
18272     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18273
18274     p = fen;
18275
18276     /* Piece placement data */
18277     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18278         j = 0;
18279         for (;;) {
18280             if (*p == '/' || *p == ' ' || *p == '[' ) {
18281                 if(j > w) w = j;
18282                 emptycount = gameInfo.boardWidth - j;
18283                 while (emptycount--)
18284                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18285                 if (*p == '/') p++;
18286                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18287                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18288                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18289                     }
18290                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18291                 }
18292                 break;
18293 #if(BOARD_FILES >= 10)*0
18294             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18295                 p++; emptycount=10;
18296                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18297                 while (emptycount--)
18298                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18299 #endif
18300             } else if (*p == '*') {
18301                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18302             } else if (isdigit(*p)) {
18303                 emptycount = *p++ - '0';
18304                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18305                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18306                 while (emptycount--)
18307                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18308             } else if (*p == '<') {
18309                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18310                 else if (i != 0 || !shuffle) return FALSE;
18311                 p++;
18312             } else if (shuffle && *p == '>') {
18313                 p++; // for now ignore closing shuffle range, and assume rank-end
18314             } else if (*p == '?') {
18315                 if (j >= gameInfo.boardWidth) return FALSE;
18316                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18317                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18318             } else if (*p == '+' || isalpha(*p)) {
18319                 char *q, *s = SUFFIXES;
18320                 if (j >= gameInfo.boardWidth) return FALSE;
18321                 if(*p=='+') {
18322                     char c = *++p;
18323                     if(q = strchr(s, p[1])) p++;
18324                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18325                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18326                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18327                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18328                 } else {
18329                     char c = *p++;
18330                     if(q = strchr(s, *p)) p++;
18331                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18332                 }
18333
18334                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18335                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18336                     piece = (ChessSquare) (PROMOTED(piece));
18337                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18338                     p++;
18339                 }
18340                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18341                 if(piece == king) wKingRank = i;
18342                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18343             } else {
18344                 return FALSE;
18345             }
18346         }
18347     }
18348     while (*p == '/' || *p == ' ') p++;
18349
18350     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18351
18352     /* [HGM] by default clear Crazyhouse holdings, if present */
18353     if(gameInfo.holdingsWidth) {
18354        for(i=0; i<BOARD_HEIGHT; i++) {
18355            board[i][0]             = EmptySquare; /* black holdings */
18356            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18357            board[i][1]             = (ChessSquare) 0; /* black counts */
18358            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18359        }
18360     }
18361
18362     /* [HGM] look for Crazyhouse holdings here */
18363     while(*p==' ') p++;
18364     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18365         int swap=0, wcnt=0, bcnt=0;
18366         if(*p == '[') p++;
18367         if(*p == '<') swap++, p++;
18368         if(*p == '-' ) p++; /* empty holdings */ else {
18369             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18370             /* if we would allow FEN reading to set board size, we would   */
18371             /* have to add holdings and shift the board read so far here   */
18372             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18373                 p++;
18374                 if((int) piece >= (int) BlackPawn ) {
18375                     i = (int)piece - (int)BlackPawn;
18376                     i = PieceToNumber((ChessSquare)i);
18377                     if( i >= gameInfo.holdingsSize ) return FALSE;
18378                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18379                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18380                     bcnt++;
18381                 } else {
18382                     i = (int)piece - (int)WhitePawn;
18383                     i = PieceToNumber((ChessSquare)i);
18384                     if( i >= gameInfo.holdingsSize ) return FALSE;
18385                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18386                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18387                     wcnt++;
18388                 }
18389             }
18390             if(subst) { // substitute back-rank question marks by holdings pieces
18391                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18392                     int k, m, n = bcnt + 1;
18393                     if(board[0][j] == ClearBoard) {
18394                         if(!wcnt) return FALSE;
18395                         n = rand() % wcnt;
18396                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18397                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18398                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18399                             break;
18400                         }
18401                     }
18402                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18403                         if(!bcnt) return FALSE;
18404                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18405                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18406                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18407                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18408                             break;
18409                         }
18410                     }
18411                 }
18412                 subst = 0;
18413             }
18414         }
18415         if(*p == ']') p++;
18416     }
18417
18418     if(subst) return FALSE; // substitution requested, but no holdings
18419
18420     while(*p == ' ') p++;
18421
18422     /* Active color */
18423     c = *p++;
18424     if(appData.colorNickNames) {
18425       if( c == appData.colorNickNames[0] ) c = 'w'; else
18426       if( c == appData.colorNickNames[1] ) c = 'b';
18427     }
18428     switch (c) {
18429       case 'w':
18430         *blackPlaysFirst = FALSE;
18431         break;
18432       case 'b':
18433         *blackPlaysFirst = TRUE;
18434         break;
18435       default:
18436         return FALSE;
18437     }
18438
18439     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18440     /* return the extra info in global variiables             */
18441
18442     while(*p==' ') p++;
18443
18444     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18445         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18446         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18447     }
18448
18449     /* set defaults in case FEN is incomplete */
18450     board[EP_STATUS] = EP_UNKNOWN;
18451     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18452     for(i=0; i<nrCastlingRights; i++ ) {
18453         board[CASTLING][i] =
18454             appData.fischerCastling ? NoRights : initialRights[i];
18455     }   /* assume possible unless obviously impossible */
18456     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18457     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18458     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18459                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18460     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18461     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18462     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18463                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18464     FENrulePlies = 0;
18465
18466     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18467       char *q = p;
18468       int w=0, b=0;
18469       while(isalpha(*p)) {
18470         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18471         if(islower(*p)) b |= 1 << (*p++ - 'a');
18472       }
18473       if(*p == '-') p++;
18474       if(p != q) {
18475         board[TOUCHED_W] = ~w;
18476         board[TOUCHED_B] = ~b;
18477         while(*p == ' ') p++;
18478       }
18479     } else
18480
18481     if(nrCastlingRights) {
18482       int fischer = 0;
18483       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18484       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18485           /* castling indicator present, so default becomes no castlings */
18486           for(i=0; i<nrCastlingRights; i++ ) {
18487                  board[CASTLING][i] = NoRights;
18488           }
18489       }
18490       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18491              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18492              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18493              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18494         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18495
18496         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18497             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18498             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18499         }
18500         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18501             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18502         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18503                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18504         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18505                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18506         switch(c) {
18507           case'K':
18508               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18509               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18510               board[CASTLING][2] = whiteKingFile;
18511               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18512               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18513               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18514               break;
18515           case'Q':
18516               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18517               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18518               board[CASTLING][2] = whiteKingFile;
18519               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18520               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18521               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18522               break;
18523           case'k':
18524               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18525               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18526               board[CASTLING][5] = blackKingFile;
18527               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18528               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18529               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18530               break;
18531           case'q':
18532               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18533               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18534               board[CASTLING][5] = blackKingFile;
18535               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18536               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18537               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18538           case '-':
18539               break;
18540           default: /* FRC castlings */
18541               if(c >= 'a') { /* black rights */
18542                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18543                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18544                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18545                   if(i == BOARD_RGHT) break;
18546                   board[CASTLING][5] = i;
18547                   c -= AAA;
18548                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18549                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18550                   if(c > i)
18551                       board[CASTLING][3] = c;
18552                   else
18553                       board[CASTLING][4] = c;
18554               } else { /* white rights */
18555                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18556                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18557                     if(board[0][i] == WhiteKing) break;
18558                   if(i == BOARD_RGHT) break;
18559                   board[CASTLING][2] = i;
18560                   c -= AAA - 'a' + 'A';
18561                   if(board[0][c] >= WhiteKing) break;
18562                   if(c > i)
18563                       board[CASTLING][0] = c;
18564                   else
18565                       board[CASTLING][1] = c;
18566               }
18567         }
18568       }
18569       for(i=0; i<nrCastlingRights; i++)
18570         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18571       if(gameInfo.variant == VariantSChess)
18572         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18573       if(fischer && shuffle) appData.fischerCastling = TRUE;
18574     if (appData.debugMode) {
18575         fprintf(debugFP, "FEN castling rights:");
18576         for(i=0; i<nrCastlingRights; i++)
18577         fprintf(debugFP, " %d", board[CASTLING][i]);
18578         fprintf(debugFP, "\n");
18579     }
18580
18581       while(*p==' ') p++;
18582     }
18583
18584     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18585
18586     /* read e.p. field in games that know e.p. capture */
18587     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18588        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18589        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18590       if(*p=='-') {
18591         p++; board[EP_STATUS] = EP_NONE;
18592       } else {
18593          char c = *p++ - AAA;
18594
18595          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18596          if(*p >= '0' && *p <='9') p++;
18597          board[EP_STATUS] = c;
18598       }
18599     }
18600
18601
18602     if(sscanf(p, "%d", &i) == 1) {
18603         FENrulePlies = i; /* 50-move ply counter */
18604         /* (The move number is still ignored)    */
18605     }
18606
18607     return TRUE;
18608 }
18609
18610 void
18611 EditPositionPasteFEN (char *fen)
18612 {
18613   if (fen != NULL) {
18614     Board initial_position;
18615
18616     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18617       DisplayError(_("Bad FEN position in clipboard"), 0);
18618       return ;
18619     } else {
18620       int savedBlackPlaysFirst = blackPlaysFirst;
18621       EditPositionEvent();
18622       blackPlaysFirst = savedBlackPlaysFirst;
18623       CopyBoard(boards[0], initial_position);
18624       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18625       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18626       DisplayBothClocks();
18627       DrawPosition(FALSE, boards[currentMove]);
18628     }
18629   }
18630 }
18631
18632 static char cseq[12] = "\\   ";
18633
18634 Boolean
18635 set_cont_sequence (char *new_seq)
18636 {
18637     int len;
18638     Boolean ret;
18639
18640     // handle bad attempts to set the sequence
18641         if (!new_seq)
18642                 return 0; // acceptable error - no debug
18643
18644     len = strlen(new_seq);
18645     ret = (len > 0) && (len < sizeof(cseq));
18646     if (ret)
18647       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18648     else if (appData.debugMode)
18649       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18650     return ret;
18651 }
18652
18653 /*
18654     reformat a source message so words don't cross the width boundary.  internal
18655     newlines are not removed.  returns the wrapped size (no null character unless
18656     included in source message).  If dest is NULL, only calculate the size required
18657     for the dest buffer.  lp argument indicats line position upon entry, and it's
18658     passed back upon exit.
18659 */
18660 int
18661 wrap (char *dest, char *src, int count, int width, int *lp)
18662 {
18663     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18664
18665     cseq_len = strlen(cseq);
18666     old_line = line = *lp;
18667     ansi = len = clen = 0;
18668
18669     for (i=0; i < count; i++)
18670     {
18671         if (src[i] == '\033')
18672             ansi = 1;
18673
18674         // if we hit the width, back up
18675         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18676         {
18677             // store i & len in case the word is too long
18678             old_i = i, old_len = len;
18679
18680             // find the end of the last word
18681             while (i && src[i] != ' ' && src[i] != '\n')
18682             {
18683                 i--;
18684                 len--;
18685             }
18686
18687             // word too long?  restore i & len before splitting it
18688             if ((old_i-i+clen) >= width)
18689             {
18690                 i = old_i;
18691                 len = old_len;
18692             }
18693
18694             // extra space?
18695             if (i && src[i-1] == ' ')
18696                 len--;
18697
18698             if (src[i] != ' ' && src[i] != '\n')
18699             {
18700                 i--;
18701                 if (len)
18702                     len--;
18703             }
18704
18705             // now append the newline and continuation sequence
18706             if (dest)
18707                 dest[len] = '\n';
18708             len++;
18709             if (dest)
18710                 strncpy(dest+len, cseq, cseq_len);
18711             len += cseq_len;
18712             line = cseq_len;
18713             clen = cseq_len;
18714             continue;
18715         }
18716
18717         if (dest)
18718             dest[len] = src[i];
18719         len++;
18720         if (!ansi)
18721             line++;
18722         if (src[i] == '\n')
18723             line = 0;
18724         if (src[i] == 'm')
18725             ansi = 0;
18726     }
18727     if (dest && appData.debugMode)
18728     {
18729         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18730             count, width, line, len, *lp);
18731         show_bytes(debugFP, src, count);
18732         fprintf(debugFP, "\ndest: ");
18733         show_bytes(debugFP, dest, len);
18734         fprintf(debugFP, "\n");
18735     }
18736     *lp = dest ? line : old_line;
18737
18738     return len;
18739 }
18740
18741 // [HGM] vari: routines for shelving variations
18742 Boolean modeRestore = FALSE;
18743
18744 void
18745 PushInner (int firstMove, int lastMove)
18746 {
18747         int i, j, nrMoves = lastMove - firstMove;
18748
18749         // push current tail of game on stack
18750         savedResult[storedGames] = gameInfo.result;
18751         savedDetails[storedGames] = gameInfo.resultDetails;
18752         gameInfo.resultDetails = NULL;
18753         savedFirst[storedGames] = firstMove;
18754         savedLast [storedGames] = lastMove;
18755         savedFramePtr[storedGames] = framePtr;
18756         framePtr -= nrMoves; // reserve space for the boards
18757         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18758             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18759             for(j=0; j<MOVE_LEN; j++)
18760                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18761             for(j=0; j<2*MOVE_LEN; j++)
18762                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18763             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18764             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18765             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18766             pvInfoList[firstMove+i-1].depth = 0;
18767             commentList[framePtr+i] = commentList[firstMove+i];
18768             commentList[firstMove+i] = NULL;
18769         }
18770
18771         storedGames++;
18772         forwardMostMove = firstMove; // truncate game so we can start variation
18773 }
18774
18775 void
18776 PushTail (int firstMove, int lastMove)
18777 {
18778         if(appData.icsActive) { // only in local mode
18779                 forwardMostMove = currentMove; // mimic old ICS behavior
18780                 return;
18781         }
18782         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18783
18784         PushInner(firstMove, lastMove);
18785         if(storedGames == 1) GreyRevert(FALSE);
18786         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18787 }
18788
18789 void
18790 PopInner (Boolean annotate)
18791 {
18792         int i, j, nrMoves;
18793         char buf[8000], moveBuf[20];
18794
18795         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18796         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18797         nrMoves = savedLast[storedGames] - currentMove;
18798         if(annotate) {
18799                 int cnt = 10;
18800                 if(!WhiteOnMove(currentMove))
18801                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18802                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18803                 for(i=currentMove; i<forwardMostMove; i++) {
18804                         if(WhiteOnMove(i))
18805                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18806                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18807                         strcat(buf, moveBuf);
18808                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18809                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18810                 }
18811                 strcat(buf, ")");
18812         }
18813         for(i=1; i<=nrMoves; i++) { // copy last variation back
18814             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18815             for(j=0; j<MOVE_LEN; j++)
18816                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18817             for(j=0; j<2*MOVE_LEN; j++)
18818                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18819             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18820             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18821             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18822             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18823             commentList[currentMove+i] = commentList[framePtr+i];
18824             commentList[framePtr+i] = NULL;
18825         }
18826         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18827         framePtr = savedFramePtr[storedGames];
18828         gameInfo.result = savedResult[storedGames];
18829         if(gameInfo.resultDetails != NULL) {
18830             free(gameInfo.resultDetails);
18831       }
18832         gameInfo.resultDetails = savedDetails[storedGames];
18833         forwardMostMove = currentMove + nrMoves;
18834 }
18835
18836 Boolean
18837 PopTail (Boolean annotate)
18838 {
18839         if(appData.icsActive) return FALSE; // only in local mode
18840         if(!storedGames) return FALSE; // sanity
18841         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18842
18843         PopInner(annotate);
18844         if(currentMove < forwardMostMove) ForwardEvent(); else
18845         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18846
18847         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18848         return TRUE;
18849 }
18850
18851 void
18852 CleanupTail ()
18853 {       // remove all shelved variations
18854         int i;
18855         for(i=0; i<storedGames; i++) {
18856             if(savedDetails[i])
18857                 free(savedDetails[i]);
18858             savedDetails[i] = NULL;
18859         }
18860         for(i=framePtr; i<MAX_MOVES; i++) {
18861                 if(commentList[i]) free(commentList[i]);
18862                 commentList[i] = NULL;
18863         }
18864         framePtr = MAX_MOVES-1;
18865         storedGames = 0;
18866 }
18867
18868 void
18869 LoadVariation (int index, char *text)
18870 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18871         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18872         int level = 0, move;
18873
18874         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18875         // first find outermost bracketing variation
18876         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18877             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18878                 if(*p == '{') wait = '}'; else
18879                 if(*p == '[') wait = ']'; else
18880                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18881                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18882             }
18883             if(*p == wait) wait = NULLCHAR; // closing ]} found
18884             p++;
18885         }
18886         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18887         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18888         end[1] = NULLCHAR; // clip off comment beyond variation
18889         ToNrEvent(currentMove-1);
18890         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18891         // kludge: use ParsePV() to append variation to game
18892         move = currentMove;
18893         ParsePV(start, TRUE, TRUE);
18894         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18895         ClearPremoveHighlights();
18896         CommentPopDown();
18897         ToNrEvent(currentMove+1);
18898 }
18899
18900 void
18901 LoadTheme ()
18902 {
18903     char *p, *q, buf[MSG_SIZ];
18904     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18905         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18906         ParseArgsFromString(buf);
18907         ActivateTheme(TRUE); // also redo colors
18908         return;
18909     }
18910     p = nickName;
18911     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18912     {
18913         int len;
18914         q = appData.themeNames;
18915         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18916       if(appData.useBitmaps) {
18917         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18918                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18919                 appData.liteBackTextureMode,
18920                 appData.darkBackTextureMode );
18921       } else {
18922         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18923                 Col2Text(2),   // lightSquareColor
18924                 Col2Text(3) ); // darkSquareColor
18925       }
18926       if(appData.useBorder) {
18927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18928                 appData.border);
18929       } else {
18930         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18931       }
18932       if(appData.useFont) {
18933         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18934                 appData.renderPiecesWithFont,
18935                 appData.fontToPieceTable,
18936                 Col2Text(9),    // appData.fontBackColorWhite
18937                 Col2Text(10) ); // appData.fontForeColorBlack
18938       } else {
18939         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18940                 appData.pieceDirectory);
18941         if(!appData.pieceDirectory[0])
18942           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18943                 Col2Text(0),   // whitePieceColor
18944                 Col2Text(1) ); // blackPieceColor
18945       }
18946       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18947                 Col2Text(4),   // highlightSquareColor
18948                 Col2Text(5) ); // premoveHighlightColor
18949         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18950         if(insert != q) insert[-1] = NULLCHAR;
18951         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18952         if(q)   free(q);
18953     }
18954     ActivateTheme(FALSE);
18955 }