Repair flashing of moved piece (XB)
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         int oldFMM = forwardMostMove;
4167                         gotPremove = 0;
4168                         ClearPremoveHighlights();
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                           UserMoveEvent(premoveFromX, premoveFromY,
4172                                         premoveToX, premoveToY,
4173                                         premovePromoChar);
4174                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4175                           if(moveList[oldFMM-1][1] != '@')
4176                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4177                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4178                           else // (drop)
4179                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4180                         }
4181                       }
4182                     }
4183
4184                     /* Usually suppress following prompt */
4185                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4186                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4187                         if (looking_at(buf, &i, "*% ")) {
4188                             savingComment = FALSE;
4189                             suppressKibitz = 0;
4190                         }
4191                     }
4192                     next_out = i;
4193                 } else if (started == STARTED_HOLDINGS) {
4194                     int gamenum;
4195                     char new_piece[MSG_SIZ];
4196                     started = STARTED_NONE;
4197                     parse[parse_pos] = NULLCHAR;
4198                     if (appData.debugMode)
4199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4200                                                         parse, currentMove);
4201                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4202                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4203                         if (gameInfo.variant == VariantNormal) {
4204                           /* [HGM] We seem to switch variant during a game!
4205                            * Presumably no holdings were displayed, so we have
4206                            * to move the position two files to the right to
4207                            * create room for them!
4208                            */
4209                           VariantClass newVariant;
4210                           switch(gameInfo.boardWidth) { // base guess on board width
4211                                 case 9:  newVariant = VariantShogi; break;
4212                                 case 10: newVariant = VariantGreat; break;
4213                                 default: newVariant = VariantCrazyhouse; break;
4214                           }
4215                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4216                           /* Get a move list just to see the header, which
4217                              will tell us whether this is really bug or zh */
4218                           if (ics_getting_history == H_FALSE) {
4219                             ics_getting_history = H_REQUESTED;
4220                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221                             SendToICS(str);
4222                           }
4223                         }
4224                         new_piece[0] = NULLCHAR;
4225                         sscanf(parse, "game %d white [%s black [%s <- %s",
4226                                &gamenum, white_holding, black_holding,
4227                                new_piece);
4228                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4229                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4230                         /* [HGM] copy holdings to board holdings area */
4231                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4232                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4233                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4234 #if ZIPPY
4235                         if (appData.zippyPlay && first.initDone) {
4236                             ZippyHoldings(white_holding, black_holding,
4237                                           new_piece);
4238                         }
4239 #endif /*ZIPPY*/
4240                         if (tinyLayout || smallLayout) {
4241                             char wh[16], bh[16];
4242                             PackHolding(wh, white_holding);
4243                             PackHolding(bh, black_holding);
4244                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4245                                     gameInfo.white, gameInfo.black);
4246                         } else {
4247                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4248                                     gameInfo.white, white_holding, _("vs."),
4249                                     gameInfo.black, black_holding);
4250                         }
4251                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4252                         DrawPosition(FALSE, boards[currentMove]);
4253                         DisplayTitle(str);
4254                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4255                         sscanf(parse, "game %d white [%s black [%s <- %s",
4256                                &gamenum, white_holding, black_holding,
4257                                new_piece);
4258                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4259                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4260                         /* [HGM] copy holdings to partner-board holdings area */
4261                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4262                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4263                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4264                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4265                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4266                       }
4267                     }
4268                     /* Suppress following prompt */
4269                     if (looking_at(buf, &i, "*% ")) {
4270                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4271                         savingComment = FALSE;
4272                         suppressKibitz = 0;
4273                     }
4274                     next_out = i;
4275                 }
4276                 continue;
4277             }
4278
4279             i++;                /* skip unparsed character and loop back */
4280         }
4281
4282         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4283 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4284 //          SendToPlayer(&buf[next_out], i - next_out);
4285             started != STARTED_HOLDINGS && leftover_start > next_out) {
4286             SendToPlayer(&buf[next_out], leftover_start - next_out);
4287             next_out = i;
4288         }
4289
4290         leftover_len = buf_len - leftover_start;
4291         /* if buffer ends with something we couldn't parse,
4292            reparse it after appending the next read */
4293
4294     } else if (count == 0) {
4295         RemoveInputSource(isr);
4296         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4297     } else {
4298         DisplayFatalError(_("Error reading from ICS"), error, 1);
4299     }
4300 }
4301
4302
4303 /* Board style 12 looks like this:
4304
4305    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4306
4307  * The "<12> " is stripped before it gets to this routine.  The two
4308  * trailing 0's (flip state and clock ticking) are later addition, and
4309  * some chess servers may not have them, or may have only the first.
4310  * Additional trailing fields may be added in the future.
4311  */
4312
4313 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4314
4315 #define RELATION_OBSERVING_PLAYED    0
4316 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4317 #define RELATION_PLAYING_MYMOVE      1
4318 #define RELATION_PLAYING_NOTMYMOVE  -1
4319 #define RELATION_EXAMINING           2
4320 #define RELATION_ISOLATED_BOARD     -3
4321 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4322
4323 void
4324 ParseBoard12 (char *string)
4325 {
4326 #if ZIPPY
4327     int i, takeback;
4328     char *bookHit = NULL; // [HGM] book
4329 #endif
4330     GameMode newGameMode;
4331     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4332     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4333     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4334     char to_play, board_chars[200];
4335     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4336     char black[32], white[32];
4337     Board board;
4338     int prevMove = currentMove;
4339     int ticking = 2;
4340     ChessMove moveType;
4341     int fromX, fromY, toX, toY;
4342     char promoChar;
4343     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4344     Boolean weird = FALSE, reqFlag = FALSE;
4345
4346     fromX = fromY = toX = toY = -1;
4347
4348     newGame = FALSE;
4349
4350     if (appData.debugMode)
4351       fprintf(debugFP, "Parsing board: %s\n", string);
4352
4353     move_str[0] = NULLCHAR;
4354     elapsed_time[0] = NULLCHAR;
4355     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4356         int  i = 0, j;
4357         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4358             if(string[i] == ' ') { ranks++; files = 0; }
4359             else files++;
4360             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4361             i++;
4362         }
4363         for(j = 0; j <i; j++) board_chars[j] = string[j];
4364         board_chars[i] = '\0';
4365         string += i + 1;
4366     }
4367     n = sscanf(string, PATTERN, &to_play, &double_push,
4368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4369                &gamenum, white, black, &relation, &basetime, &increment,
4370                &white_stren, &black_stren, &white_time, &black_time,
4371                &moveNum, str, elapsed_time, move_str, &ics_flip,
4372                &ticking);
4373
4374     if (n < 21) {
4375         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4376         DisplayError(str, 0);
4377         return;
4378     }
4379
4380     /* Convert the move number to internal form */
4381     moveNum = (moveNum - 1) * 2;
4382     if (to_play == 'B') moveNum++;
4383     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4385                         0, 1);
4386       return;
4387     }
4388
4389     switch (relation) {
4390       case RELATION_OBSERVING_PLAYED:
4391       case RELATION_OBSERVING_STATIC:
4392         if (gamenum == -1) {
4393             /* Old ICC buglet */
4394             relation = RELATION_OBSERVING_STATIC;
4395         }
4396         newGameMode = IcsObserving;
4397         break;
4398       case RELATION_PLAYING_MYMOVE:
4399       case RELATION_PLAYING_NOTMYMOVE:
4400         newGameMode =
4401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4402             IcsPlayingWhite : IcsPlayingBlack;
4403         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4404         break;
4405       case RELATION_EXAMINING:
4406         newGameMode = IcsExamining;
4407         break;
4408       case RELATION_ISOLATED_BOARD:
4409       default:
4410         /* Just display this board.  If user was doing something else,
4411            we will forget about it until the next board comes. */
4412         newGameMode = IcsIdle;
4413         break;
4414       case RELATION_STARTING_POSITION:
4415         newGameMode = gameMode;
4416         break;
4417     }
4418
4419     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4420         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4421          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4422       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4423       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4424       static int lastBgGame = -1;
4425       char *toSqr;
4426       for (k = 0; k < ranks; k++) {
4427         for (j = 0; j < files; j++)
4428           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429         if(gameInfo.holdingsWidth > 1) {
4430              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432         }
4433       }
4434       CopyBoard(partnerBoard, board);
4435       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4436         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4437         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4438       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4439       if(toSqr = strchr(str, '-')) {
4440         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4441         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4442       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4443       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4444       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4445       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4446       if(twoBoards) {
4447           DisplayWhiteClock(white_time*fac, to_play == 'W');
4448           DisplayBlackClock(black_time*fac, to_play != 'W');
4449           activePartner = to_play;
4450           if(gamenum != lastBgGame) {
4451               char buf[MSG_SIZ];
4452               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4453               DisplayTitle(buf);
4454           }
4455           lastBgGame = gamenum;
4456           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4457                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4458       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4459                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4460       if(!twoBoards) DisplayMessage(partnerStatus, "");
4461         partnerBoardValid = TRUE;
4462       return;
4463     }
4464
4465     if(appData.dualBoard && appData.bgObserve) {
4466         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4467             SendToICS(ics_prefix), SendToICS("pobserve\n");
4468         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4469             char buf[MSG_SIZ];
4470             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4471             SendToICS(buf);
4472         }
4473     }
4474
4475     /* Modify behavior for initial board display on move listing
4476        of wild games.
4477        */
4478     switch (ics_getting_history) {
4479       case H_FALSE:
4480       case H_REQUESTED:
4481         break;
4482       case H_GOT_REQ_HEADER:
4483       case H_GOT_UNREQ_HEADER:
4484         /* This is the initial position of the current game */
4485         gamenum = ics_gamenum;
4486         moveNum = 0;            /* old ICS bug workaround */
4487         if (to_play == 'B') {
4488           startedFromSetupPosition = TRUE;
4489           blackPlaysFirst = TRUE;
4490           moveNum = 1;
4491           if (forwardMostMove == 0) forwardMostMove = 1;
4492           if (backwardMostMove == 0) backwardMostMove = 1;
4493           if (currentMove == 0) currentMove = 1;
4494         }
4495         newGameMode = gameMode;
4496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4497         break;
4498       case H_GOT_UNWANTED_HEADER:
4499         /* This is an initial board that we don't want */
4500         return;
4501       case H_GETTING_MOVES:
4502         /* Should not happen */
4503         DisplayError(_("Error gathering move list: extra board"), 0);
4504         ics_getting_history = H_FALSE;
4505         return;
4506     }
4507
4508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4509                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4510                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4511      /* [HGM] We seem to have switched variant unexpectedly
4512       * Try to guess new variant from board size
4513       */
4514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4519           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4520           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4521           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4522           /* Get a move list just to see the header, which
4523              will tell us whether this is really bug or zh */
4524           if (ics_getting_history == H_FALSE) {
4525             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528           }
4529     }
4530
4531     /* Take action if this is the first board of a new game, or of a
4532        different game than is currently being displayed.  */
4533     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4534         relation == RELATION_ISOLATED_BOARD) {
4535
4536         /* Forget the old game and get the history (if any) of the new one */
4537         if (gameMode != BeginningOfGame) {
4538           Reset(TRUE, TRUE);
4539         }
4540         newGame = TRUE;
4541         if (appData.autoRaiseBoard) BoardToTop();
4542         prevMove = -3;
4543         if (gamenum == -1) {
4544             newGameMode = IcsIdle;
4545         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4546                    appData.getMoveList && !reqFlag) {
4547             /* Need to get game history */
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552
4553         /* Initially flip the board to have black on the bottom if playing
4554            black or if the ICS flip flag is set, but let the user change
4555            it with the Flip View button. */
4556         flipView = appData.autoFlipView ?
4557           (newGameMode == IcsPlayingBlack) || ics_flip :
4558           appData.flipView;
4559
4560         /* Done with values from previous mode; copy in new ones */
4561         gameMode = newGameMode;
4562         ModeHighlight();
4563         ics_gamenum = gamenum;
4564         if (gamenum == gs_gamenum) {
4565             int klen = strlen(gs_kind);
4566             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4567             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4568             gameInfo.event = StrSave(str);
4569         } else {
4570             gameInfo.event = StrSave("ICS game");
4571         }
4572         gameInfo.site = StrSave(appData.icsHost);
4573         gameInfo.date = PGNDate();
4574         gameInfo.round = StrSave("-");
4575         gameInfo.white = StrSave(white);
4576         gameInfo.black = StrSave(black);
4577         timeControl = basetime * 60 * 1000;
4578         timeControl_2 = 0;
4579         timeIncrement = increment * 1000;
4580         movesPerSession = 0;
4581         gameInfo.timeControl = TimeControlTagValue();
4582         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4583   if (appData.debugMode) {
4584     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4585     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4586     setbuf(debugFP, NULL);
4587   }
4588
4589         gameInfo.outOfBook = NULL;
4590
4591         /* Do we have the ratings? */
4592         if (strcmp(player1Name, white) == 0 &&
4593             strcmp(player2Name, black) == 0) {
4594             if (appData.debugMode)
4595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4596                       player1Rating, player2Rating);
4597             gameInfo.whiteRating = player1Rating;
4598             gameInfo.blackRating = player2Rating;
4599         } else if (strcmp(player2Name, white) == 0 &&
4600                    strcmp(player1Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player2Rating, player1Rating);
4604             gameInfo.whiteRating = player2Rating;
4605             gameInfo.blackRating = player1Rating;
4606         }
4607         player1Name[0] = player2Name[0] = NULLCHAR;
4608
4609         /* Silence shouts if requested */
4610         if (appData.quietPlay &&
4611             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4612             SendToICS(ics_prefix);
4613             SendToICS("set shout 0\n");
4614         }
4615     }
4616
4617     /* Deal with midgame name changes */
4618     if (!newGame) {
4619         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4620             if (gameInfo.white) free(gameInfo.white);
4621             gameInfo.white = StrSave(white);
4622         }
4623         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4624             if (gameInfo.black) free(gameInfo.black);
4625             gameInfo.black = StrSave(black);
4626         }
4627     }
4628
4629     /* Throw away game result if anything actually changes in examine mode */
4630     if (gameMode == IcsExamining && !newGame) {
4631         gameInfo.result = GameUnfinished;
4632         if (gameInfo.resultDetails != NULL) {
4633             free(gameInfo.resultDetails);
4634             gameInfo.resultDetails = NULL;
4635         }
4636     }
4637
4638     /* In pausing && IcsExamining mode, we ignore boards coming
4639        in if they are in a different variation than we are. */
4640     if (pauseExamInvalid) return;
4641     if (pausing && gameMode == IcsExamining) {
4642         if (moveNum <= pauseExamForwardMostMove) {
4643             pauseExamInvalid = TRUE;
4644             forwardMostMove = pauseExamForwardMostMove;
4645             return;
4646         }
4647     }
4648
4649   if (appData.debugMode) {
4650     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4651   }
4652     /* Parse the board */
4653     for (k = 0; k < ranks; k++) {
4654       for (j = 0; j < files; j++)
4655         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4656       if(gameInfo.holdingsWidth > 1) {
4657            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4658            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4659       }
4660     }
4661     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4662       board[5][BOARD_RGHT+1] = WhiteAngel;
4663       board[6][BOARD_RGHT+1] = WhiteMarshall;
4664       board[1][0] = BlackMarshall;
4665       board[2][0] = BlackAngel;
4666       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4667     }
4668     CopyBoard(boards[moveNum], board);
4669     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4670     if (moveNum == 0) {
4671         startedFromSetupPosition =
4672           !CompareBoards(board, initialPosition);
4673         if(startedFromSetupPosition)
4674             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4675     }
4676
4677     /* [HGM] Set castling rights. Take the outermost Rooks,
4678        to make it also work for FRC opening positions. Note that board12
4679        is really defective for later FRC positions, as it has no way to
4680        indicate which Rook can castle if they are on the same side of King.
4681        For the initial position we grant rights to the outermost Rooks,
4682        and remember thos rights, and we then copy them on positions
4683        later in an FRC game. This means WB might not recognize castlings with
4684        Rooks that have moved back to their original position as illegal,
4685        but in ICS mode that is not its job anyway.
4686     */
4687     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4688     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4689
4690         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4691             if(board[0][i] == WhiteRook) j = i;
4692         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4694             if(board[0][i] == WhiteRook) j = i;
4695         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4698         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4701         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702
4703         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4704         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4705         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4706             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4707         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4708             if(board[BOARD_HEIGHT-1][k] == bKing)
4709                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4710         if(gameInfo.variant == VariantTwoKings) {
4711             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4712             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4713             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4714         }
4715     } else { int r;
4716         r = boards[moveNum][CASTLING][0] = initialRights[0];
4717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4718         r = boards[moveNum][CASTLING][1] = initialRights[1];
4719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4720         r = boards[moveNum][CASTLING][3] = initialRights[3];
4721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4722         r = boards[moveNum][CASTLING][4] = initialRights[4];
4723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4724         /* wildcastle kludge: always assume King has rights */
4725         r = boards[moveNum][CASTLING][2] = initialRights[2];
4726         r = boards[moveNum][CASTLING][5] = initialRights[5];
4727     }
4728     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4729     boards[moveNum][EP_STATUS] = EP_NONE;
4730     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4731     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4732     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4733
4734
4735     if (ics_getting_history == H_GOT_REQ_HEADER ||
4736         ics_getting_history == H_GOT_UNREQ_HEADER) {
4737         /* This was an initial position from a move list, not
4738            the current position */
4739         return;
4740     }
4741
4742     /* Update currentMove and known move number limits */
4743     newMove = newGame || moveNum > forwardMostMove;
4744
4745     if (newGame) {
4746         forwardMostMove = backwardMostMove = currentMove = moveNum;
4747         if (gameMode == IcsExamining && moveNum == 0) {
4748           /* Workaround for ICS limitation: we are not told the wild
4749              type when starting to examine a game.  But if we ask for
4750              the move list, the move list header will tell us */
4751             ics_getting_history = H_REQUESTED;
4752             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4753             SendToICS(str);
4754         }
4755     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4756                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4757 #if ZIPPY
4758         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4759         /* [HGM] applied this also to an engine that is silently watching        */
4760         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4761             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4762             gameInfo.variant == currentlyInitializedVariant) {
4763           takeback = forwardMostMove - moveNum;
4764           for (i = 0; i < takeback; i++) {
4765             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4766             SendToProgram("undo\n", &first);
4767           }
4768         }
4769 #endif
4770
4771         forwardMostMove = moveNum;
4772         if (!pausing || currentMove > forwardMostMove)
4773           currentMove = forwardMostMove;
4774     } else {
4775         /* New part of history that is not contiguous with old part */
4776         if (pausing && gameMode == IcsExamining) {
4777             pauseExamInvalid = TRUE;
4778             forwardMostMove = pauseExamForwardMostMove;
4779             return;
4780         }
4781         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4782 #if ZIPPY
4783             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4784                 // [HGM] when we will receive the move list we now request, it will be
4785                 // fed to the engine from the first move on. So if the engine is not
4786                 // in the initial position now, bring it there.
4787                 InitChessProgram(&first, 0);
4788             }
4789 #endif
4790             ics_getting_history = H_REQUESTED;
4791             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792             SendToICS(str);
4793         }
4794         forwardMostMove = backwardMostMove = currentMove = moveNum;
4795     }
4796
4797     /* Update the clocks */
4798     if (strchr(elapsed_time, '.')) {
4799       /* Time is in ms */
4800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4802     } else {
4803       /* Time is in seconds */
4804       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4805       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4806     }
4807
4808
4809 #if ZIPPY
4810     if (appData.zippyPlay && newGame &&
4811         gameMode != IcsObserving && gameMode != IcsIdle &&
4812         gameMode != IcsExamining)
4813       ZippyFirstBoard(moveNum, basetime, increment);
4814 #endif
4815
4816     /* Put the move on the move list, first converting
4817        to canonical algebraic form. */
4818     if (moveNum > 0) {
4819   if (appData.debugMode) {
4820     int f = forwardMostMove;
4821     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4822             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4823             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4825     fprintf(debugFP, "moveNum = %d\n", moveNum);
4826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4827     setbuf(debugFP, NULL);
4828   }
4829         if (moveNum <= backwardMostMove) {
4830             /* We don't know what the board looked like before
4831                this move.  Punt. */
4832           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4833             strcat(parseList[moveNum - 1], " ");
4834             strcat(parseList[moveNum - 1], elapsed_time);
4835             moveList[moveNum - 1][0] = NULLCHAR;
4836         } else if (strcmp(move_str, "none") == 0) {
4837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4838             /* Again, we don't know what the board looked like;
4839                this is really the start of the game. */
4840             parseList[moveNum - 1][0] = NULLCHAR;
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842             backwardMostMove = moveNum;
4843             startedFromSetupPosition = TRUE;
4844             fromX = fromY = toX = toY = -1;
4845         } else {
4846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4847           //                 So we parse the long-algebraic move string in stead of the SAN move
4848           int valid; char buf[MSG_SIZ], *prom;
4849
4850           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4851                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4852           // str looks something like "Q/a1-a2"; kill the slash
4853           if(str[1] == '/')
4854             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4855           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4856           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4857                 strcat(buf, prom); // long move lacks promo specification!
4858           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4859                 if(appData.debugMode)
4860                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4861                 safeStrCpy(move_str, buf, MSG_SIZ);
4862           }
4863           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4864                                 &fromX, &fromY, &toX, &toY, &promoChar)
4865                || ParseOneMove(buf, moveNum - 1, &moveType,
4866                                 &fromX, &fromY, &toX, &toY, &promoChar);
4867           // end of long SAN patch
4868           if (valid) {
4869             (void) CoordsToAlgebraic(boards[moveNum - 1],
4870                                      PosFlags(moveNum - 1),
4871                                      fromY, fromX, toY, toX, promoChar,
4872                                      parseList[moveNum-1]);
4873             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4874               case MT_NONE:
4875               case MT_STALEMATE:
4876               default:
4877                 break;
4878               case MT_CHECK:
4879                 if(!IS_SHOGI(gameInfo.variant))
4880                     strcat(parseList[moveNum - 1], "+");
4881                 break;
4882               case MT_CHECKMATE:
4883               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4884                 strcat(parseList[moveNum - 1], "#");
4885                 break;
4886             }
4887             strcat(parseList[moveNum - 1], " ");
4888             strcat(parseList[moveNum - 1], elapsed_time);
4889             /* currentMoveString is set as a side-effect of ParseOneMove */
4890             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4891             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4892             strcat(moveList[moveNum - 1], "\n");
4893
4894             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4895                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4896               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4897                 ChessSquare old, new = boards[moveNum][k][j];
4898                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4899                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4900                   if(old == new) continue;
4901                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4902                   else if(new == WhiteWazir || new == BlackWazir) {
4903                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4904                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4905                       else boards[moveNum][k][j] = old; // preserve type of Gold
4906                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4907                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4908               }
4909           } else {
4910             /* Move from ICS was illegal!?  Punt. */
4911             if (appData.debugMode) {
4912               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4913               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4914             }
4915             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4916             strcat(parseList[moveNum - 1], " ");
4917             strcat(parseList[moveNum - 1], elapsed_time);
4918             moveList[moveNum - 1][0] = NULLCHAR;
4919             fromX = fromY = toX = toY = -1;
4920           }
4921         }
4922   if (appData.debugMode) {
4923     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4924     setbuf(debugFP, NULL);
4925   }
4926
4927 #if ZIPPY
4928         /* Send move to chess program (BEFORE animating it). */
4929         if (appData.zippyPlay && !newGame && newMove &&
4930            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4931
4932             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4933                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4934                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4936                             move_str);
4937                     DisplayError(str, 0);
4938                 } else {
4939                     if (first.sendTime) {
4940                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4941                     }
4942                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4943                     if (firstMove && !bookHit) {
4944                         firstMove = FALSE;
4945                         if (first.useColors) {
4946                           SendToProgram(gameMode == IcsPlayingWhite ?
4947                                         "white\ngo\n" :
4948                                         "black\ngo\n", &first);
4949                         } else {
4950                           SendToProgram("go\n", &first);
4951                         }
4952                         first.maybeThinking = TRUE;
4953                     }
4954                 }
4955             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4956               if (moveList[moveNum - 1][0] == NULLCHAR) {
4957                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4958                 DisplayError(str, 0);
4959               } else {
4960                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4961                 SendMoveToProgram(moveNum - 1, &first);
4962               }
4963             }
4964         }
4965 #endif
4966     }
4967
4968     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4969         /* If move comes from a remote source, animate it.  If it
4970            isn't remote, it will have already been animated. */
4971         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4972             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4973         }
4974         if (!pausing && appData.highlightLastMove) {
4975             SetHighlights(fromX, fromY, toX, toY);
4976         }
4977     }
4978
4979     /* Start the clocks */
4980     whiteFlag = blackFlag = FALSE;
4981     appData.clockMode = !(basetime == 0 && increment == 0);
4982     if (ticking == 0) {
4983       ics_clock_paused = TRUE;
4984       StopClocks();
4985     } else if (ticking == 1) {
4986       ics_clock_paused = FALSE;
4987     }
4988     if (gameMode == IcsIdle ||
4989         relation == RELATION_OBSERVING_STATIC ||
4990         relation == RELATION_EXAMINING ||
4991         ics_clock_paused)
4992       DisplayBothClocks();
4993     else
4994       StartClocks();
4995
4996     /* Display opponents and material strengths */
4997     if (gameInfo.variant != VariantBughouse &&
4998         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4999         if (tinyLayout || smallLayout) {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5007                     basetime, increment, (int) gameInfo.variant);
5008         } else {
5009             if(gameInfo.variant == VariantNormal)
5010               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5011                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5012                     basetime, increment);
5013             else
5014               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5015                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5016                     basetime, increment, VariantName(gameInfo.variant));
5017         }
5018         DisplayTitle(str);
5019   if (appData.debugMode) {
5020     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5021   }
5022     }
5023
5024
5025     /* Display the board */
5026     if (!pausing && !appData.noGUI) {
5027
5028       if (appData.premove)
5029           if (!gotPremove ||
5030              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5031              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5032               ClearPremoveHighlights();
5033
5034       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5035         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5036       DrawPosition(j, boards[currentMove]);
5037
5038       DisplayMove(moveNum - 1);
5039       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5040             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5041               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5042         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5043       }
5044     }
5045
5046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5047 #if ZIPPY
5048     if(bookHit) { // [HGM] book: simulate book reply
5049         static char bookMove[MSG_SIZ]; // a bit generous?
5050
5051         programStats.nodes = programStats.depth = programStats.time =
5052         programStats.score = programStats.got_only_move = 0;
5053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5054
5055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5056         strcat(bookMove, bookHit);
5057         HandleMachineMove(bookMove, &first);
5058     }
5059 #endif
5060 }
5061
5062 void
5063 GetMoveListEvent ()
5064 {
5065     char buf[MSG_SIZ];
5066     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5067         ics_getting_history = H_REQUESTED;
5068         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5069         SendToICS(buf);
5070     }
5071 }
5072
5073 void
5074 SendToBoth (char *msg)
5075 {   // to make it easy to keep two engines in step in dual analysis
5076     SendToProgram(msg, &first);
5077     if(second.analyzing) SendToProgram(msg, &second);
5078 }
5079
5080 void
5081 AnalysisPeriodicEvent (int force)
5082 {
5083     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5084          && !force) || !appData.periodicUpdates)
5085       return;
5086
5087     /* Send . command to Crafty to collect stats */
5088     SendToBoth(".\n");
5089
5090     /* Don't send another until we get a response (this makes
5091        us stop sending to old Crafty's which don't understand
5092        the "." command (sending illegal cmds resets node count & time,
5093        which looks bad)) */
5094     programStats.ok_to_send = 0;
5095 }
5096
5097 void
5098 ics_update_width (int new_width)
5099 {
5100         ics_printf("set width %d\n", new_width);
5101 }
5102
5103 void
5104 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5105 {
5106     char buf[MSG_SIZ];
5107
5108     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5109         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5110             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5111             SendToProgram(buf, cps);
5112             return;
5113         }
5114         // null move in variant where engine does not understand it (for analysis purposes)
5115         SendBoard(cps, moveNum + 1); // send position after move in stead.
5116         return;
5117     }
5118     if (cps->useUsermove) {
5119       SendToProgram("usermove ", cps);
5120     }
5121     if (cps->useSAN) {
5122       char *space;
5123       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5124         int len = space - parseList[moveNum];
5125         memcpy(buf, parseList[moveNum], len);
5126         buf[len++] = '\n';
5127         buf[len] = NULLCHAR;
5128       } else {
5129         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5130       }
5131       SendToProgram(buf, cps);
5132     } else {
5133       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5134         AlphaRank(moveList[moveNum], 4);
5135         SendToProgram(moveList[moveNum], cps);
5136         AlphaRank(moveList[moveNum], 4); // and back
5137       } else
5138       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5139        * the engine. It would be nice to have a better way to identify castle
5140        * moves here. */
5141       if(appData.fischerCastling && cps->useOOCastle) {
5142         int fromX = moveList[moveNum][0] - AAA;
5143         int fromY = moveList[moveNum][1] - ONE;
5144         int toX = moveList[moveNum][2] - AAA;
5145         int toY = moveList[moveNum][3] - ONE;
5146         if((boards[moveNum][fromY][fromX] == WhiteKing
5147             && boards[moveNum][toY][toX] == WhiteRook)
5148            || (boards[moveNum][fromY][fromX] == BlackKing
5149                && boards[moveNum][toY][toX] == BlackRook)) {
5150           if(toX > fromX) SendToProgram("O-O\n", cps);
5151           else SendToProgram("O-O-O\n", cps);
5152         }
5153         else SendToProgram(moveList[moveNum], cps);
5154       } else
5155       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5156         char *m = moveList[moveNum];
5157         static char c[2];
5158         *c = m[7]; // promoChar
5159         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5160           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5161                                                m[2], m[3] - '0',
5162                                                m[5], m[6] - '0',
5163                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5164         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5165           *c = m[9];
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5167                                                m[7], m[8] - '0',
5168                                                m[7], m[8] - '0',
5169                                                m[5], m[6] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2], m[3] - '0', c);
5172         } else
5173           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5174                                                m[5], m[6] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[2], m[3] - '0', c);
5177           SendToProgram(buf, cps);
5178       } else
5179       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5180         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5181           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5182           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5183                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5186                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5187         SendToProgram(buf, cps);
5188       }
5189       else SendToProgram(moveList[moveNum], cps);
5190       /* End of additions by Tord */
5191     }
5192
5193     /* [HGM] setting up the opening has brought engine in force mode! */
5194     /*       Send 'go' if we are in a mode where machine should play. */
5195     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5196         (gameMode == TwoMachinesPlay   ||
5197 #if ZIPPY
5198          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5199 #endif
5200          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5201         SendToProgram("go\n", cps);
5202   if (appData.debugMode) {
5203     fprintf(debugFP, "(extra)\n");
5204   }
5205     }
5206     setboardSpoiledMachineBlack = 0;
5207 }
5208
5209 void
5210 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5211 {
5212     char user_move[MSG_SIZ];
5213     char suffix[4];
5214
5215     if(gameInfo.variant == VariantSChess && promoChar) {
5216         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5217         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5218     } else suffix[0] = NULLCHAR;
5219
5220     switch (moveType) {
5221       default:
5222         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5223                 (int)moveType, fromX, fromY, toX, toY);
5224         DisplayError(user_move + strlen("say "), 0);
5225         break;
5226       case WhiteKingSideCastle:
5227       case BlackKingSideCastle:
5228       case WhiteQueenSideCastleWild:
5229       case BlackQueenSideCastleWild:
5230       /* PUSH Fabien */
5231       case WhiteHSideCastleFR:
5232       case BlackHSideCastleFR:
5233       /* POP Fabien */
5234         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5235         break;
5236       case WhiteQueenSideCastle:
5237       case BlackQueenSideCastle:
5238       case WhiteKingSideCastleWild:
5239       case BlackKingSideCastleWild:
5240       /* PUSH Fabien */
5241       case WhiteASideCastleFR:
5242       case BlackASideCastleFR:
5243       /* POP Fabien */
5244         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5245         break;
5246       case WhiteNonPromotion:
5247       case BlackNonPromotion:
5248         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5249         break;
5250       case WhitePromotion:
5251       case BlackPromotion:
5252         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5253            gameInfo.variant == VariantMakruk)
5254           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5255                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5256                 PieceToChar(WhiteFerz));
5257         else if(gameInfo.variant == VariantGreat)
5258           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5259                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5260                 PieceToChar(WhiteMan));
5261         else
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 promoChar);
5265         break;
5266       case WhiteDrop:
5267       case BlackDrop:
5268       drop:
5269         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5270                  ToUpper(PieceToChar((ChessSquare) fromX)),
5271                  AAA + toX, ONE + toY);
5272         break;
5273       case IllegalMove:  /* could be a variant we don't quite understand */
5274         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5275       case NormalMove:
5276       case WhiteCapturesEnPassant:
5277       case BlackCapturesEnPassant:
5278         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5279                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5280         break;
5281     }
5282     SendToICS(user_move);
5283     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5284         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5285 }
5286
5287 void
5288 UploadGameEvent ()
5289 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5290     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5291     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5292     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5293       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5294       return;
5295     }
5296     if(gameMode != IcsExamining) { // is this ever not the case?
5297         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5298
5299         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5300           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5301         } else { // on FICS we must first go to general examine mode
5302           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5303         }
5304         if(gameInfo.variant != VariantNormal) {
5305             // try figure out wild number, as xboard names are not always valid on ICS
5306             for(i=1; i<=36; i++) {
5307               snprintf(buf, MSG_SIZ, "wild/%d", i);
5308                 if(StringToVariant(buf) == gameInfo.variant) break;
5309             }
5310             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5311             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5312             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5313         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5314         SendToICS(ics_prefix);
5315         SendToICS(buf);
5316         if(startedFromSetupPosition || backwardMostMove != 0) {
5317           fen = PositionToFEN(backwardMostMove, NULL, 1);
5318           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5319             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5320             SendToICS(buf);
5321           } else { // FICS: everything has to set by separate bsetup commands
5322             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5323             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5324             SendToICS(buf);
5325             if(!WhiteOnMove(backwardMostMove)) {
5326                 SendToICS("bsetup tomove black\n");
5327             }
5328             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5329             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5330             SendToICS(buf);
5331             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5332             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5333             SendToICS(buf);
5334             i = boards[backwardMostMove][EP_STATUS];
5335             if(i >= 0) { // set e.p.
5336               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5337                 SendToICS(buf);
5338             }
5339             bsetup++;
5340           }
5341         }
5342       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5343             SendToICS("bsetup done\n"); // switch to normal examining.
5344     }
5345     for(i = backwardMostMove; i<last; i++) {
5346         char buf[20];
5347         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5348         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5349             int len = strlen(moveList[i]);
5350             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5351             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5352         }
5353         SendToICS(buf);
5354     }
5355     SendToICS(ics_prefix);
5356     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5357 }
5358
5359 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5360 int legNr = 1;
5361
5362 void
5363 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5364 {
5365     if (rf == DROP_RANK) {
5366       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5367       sprintf(move, "%c@%c%c\n",
5368                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5369     } else {
5370         if (promoChar == 'x' || promoChar == NULLCHAR) {
5371           sprintf(move, "%c%c%c%c\n",
5372                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5373           if(killX >= 0 && killY >= 0) {
5374             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5375             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5376           }
5377         } else {
5378             sprintf(move, "%c%c%c%c%c\n",
5379                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5380           if(killX >= 0 && killY >= 0) {
5381             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5382             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5383           }
5384         }
5385     }
5386 }
5387
5388 void
5389 ProcessICSInitScript (FILE *f)
5390 {
5391     char buf[MSG_SIZ];
5392
5393     while (fgets(buf, MSG_SIZ, f)) {
5394         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5395     }
5396
5397     fclose(f);
5398 }
5399
5400
5401 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5402 int dragging;
5403 static ClickType lastClickType;
5404
5405 int
5406 PieceInString (char *s, ChessSquare piece)
5407 {
5408   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5409   while((p = strchr(s, ID))) {
5410     if(!suffix || p[1] == suffix) return TRUE;
5411     s = p;
5412   }
5413   return FALSE;
5414 }
5415
5416 int
5417 Partner (ChessSquare *p)
5418 { // change piece into promotion partner if one shogi-promotes to the other
5419   ChessSquare partner = promoPartner[*p];
5420   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5421   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5422   *p = partner;
5423   return 1;
5424 }
5425
5426 void
5427 Sweep (int step)
5428 {
5429     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5430     static int toggleFlag;
5431     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5432     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5433     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5434     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5435     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5436     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5437     do {
5438         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5439         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5440         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5441         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5442         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5443         if(!step) step = -1;
5444     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5445             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5446             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5447             promoSweep == pawn ||
5448             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5449             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5450     if(toX >= 0) {
5451         int victim = boards[currentMove][toY][toX];
5452         boards[currentMove][toY][toX] = promoSweep;
5453         DrawPosition(FALSE, boards[currentMove]);
5454         boards[currentMove][toY][toX] = victim;
5455     } else
5456     ChangeDragPiece(promoSweep);
5457 }
5458
5459 int
5460 PromoScroll (int x, int y)
5461 {
5462   int step = 0;
5463
5464   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5465   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5466   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5467   if(!step) return FALSE;
5468   lastX = x; lastY = y;
5469   if((promoSweep < BlackPawn) == flipView) step = -step;
5470   if(step > 0) selectFlag = 1;
5471   if(!selectFlag) Sweep(step);
5472   return FALSE;
5473 }
5474
5475 void
5476 NextPiece (int step)
5477 {
5478     ChessSquare piece = boards[currentMove][toY][toX];
5479     do {
5480         pieceSweep -= step;
5481         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5482         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5483         if(!step) step = -1;
5484     } while(PieceToChar(pieceSweep) == '.');
5485     boards[currentMove][toY][toX] = pieceSweep;
5486     DrawPosition(FALSE, boards[currentMove]);
5487     boards[currentMove][toY][toX] = piece;
5488 }
5489 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5490 void
5491 AlphaRank (char *move, int n)
5492 {
5493 //    char *p = move, c; int x, y;
5494
5495     if (appData.debugMode) {
5496         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5497     }
5498
5499     if(move[1]=='*' &&
5500        move[2]>='0' && move[2]<='9' &&
5501        move[3]>='a' && move[3]<='x'    ) {
5502         move[1] = '@';
5503         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5504         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5505     } else
5506     if(move[0]>='0' && move[0]<='9' &&
5507        move[1]>='a' && move[1]<='x' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         /* input move, Shogi -> normal */
5511         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5512         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5513         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5514         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5515     } else
5516     if(move[1]=='@' &&
5517        move[3]>='0' && move[3]<='9' &&
5518        move[2]>='a' && move[2]<='x'    ) {
5519         move[1] = '*';
5520         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5521         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5522     } else
5523     if(
5524        move[0]>='a' && move[0]<='x' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527          /* output move, normal -> Shogi */
5528         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5529         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5530         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5531         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5532         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5533     }
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "   out = '%s'\n", move);
5536     }
5537 }
5538
5539 char yy_textstr[8000];
5540
5541 /* Parser for moves from gnuchess, ICS, or user typein box */
5542 Boolean
5543 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5544 {
5545     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5546
5547     switch (*moveType) {
5548       case WhitePromotion:
5549       case BlackPromotion:
5550       case WhiteNonPromotion:
5551       case BlackNonPromotion:
5552       case NormalMove:
5553       case FirstLeg:
5554       case WhiteCapturesEnPassant:
5555       case BlackCapturesEnPassant:
5556       case WhiteKingSideCastle:
5557       case WhiteQueenSideCastle:
5558       case BlackKingSideCastle:
5559       case BlackQueenSideCastle:
5560       case WhiteKingSideCastleWild:
5561       case WhiteQueenSideCastleWild:
5562       case BlackKingSideCastleWild:
5563       case BlackQueenSideCastleWild:
5564       /* Code added by Tord: */
5565       case WhiteHSideCastleFR:
5566       case WhiteASideCastleFR:
5567       case BlackHSideCastleFR:
5568       case BlackASideCastleFR:
5569       /* End of code added by Tord */
5570       case IllegalMove:         /* bug or odd chess variant */
5571         if(currentMoveString[1] == '@') { // illegal drop
5572           *fromX = WhiteOnMove(moveNum) ?
5573             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5574             (int) CharToPiece(ToLower(currentMoveString[0]));
5575           goto drop;
5576         }
5577         *fromX = currentMoveString[0] - AAA;
5578         *fromY = currentMoveString[1] - ONE;
5579         *toX = currentMoveString[2] - AAA;
5580         *toY = currentMoveString[3] - ONE;
5581         *promoChar = currentMoveString[4];
5582         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5583         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5584             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5585     if (appData.debugMode) {
5586         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5587     }
5588             *fromX = *fromY = *toX = *toY = 0;
5589             return FALSE;
5590         }
5591         if (appData.testLegality) {
5592           return (*moveType != IllegalMove);
5593         } else {
5594           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5595                          // [HGM] lion: if this is a double move we are less critical
5596                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5597         }
5598
5599       case WhiteDrop:
5600       case BlackDrop:
5601         *fromX = *moveType == WhiteDrop ?
5602           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5603           (int) CharToPiece(ToLower(currentMoveString[0]));
5604       drop:
5605         *fromY = DROP_RANK;
5606         *toX = currentMoveString[2] - AAA;
5607         *toY = currentMoveString[3] - ONE;
5608         *promoChar = NULLCHAR;
5609         return TRUE;
5610
5611       case AmbiguousMove:
5612       case ImpossibleMove:
5613       case EndOfFile:
5614       case ElapsedTime:
5615       case Comment:
5616       case PGNTag:
5617       case NAG:
5618       case WhiteWins:
5619       case BlackWins:
5620       case GameIsDrawn:
5621       default:
5622     if (appData.debugMode) {
5623         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5624     }
5625         /* bug? */
5626         *fromX = *fromY = *toX = *toY = 0;
5627         *promoChar = NULLCHAR;
5628         return FALSE;
5629     }
5630 }
5631
5632 Boolean pushed = FALSE;
5633 char *lastParseAttempt;
5634
5635 void
5636 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5637 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5638   int fromX, fromY, toX, toY; char promoChar;
5639   ChessMove moveType;
5640   Boolean valid;
5641   int nr = 0;
5642
5643   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5644   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5645     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5646     pushed = TRUE;
5647   }
5648   endPV = forwardMostMove;
5649   do {
5650     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5651     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5652     lastParseAttempt = pv;
5653     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5654     if(!valid && nr == 0 &&
5655        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5656         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5657         // Hande case where played move is different from leading PV move
5658         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5659         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5660         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5661         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5662           endPV += 2; // if position different, keep this
5663           moveList[endPV-1][0] = fromX + AAA;
5664           moveList[endPV-1][1] = fromY + ONE;
5665           moveList[endPV-1][2] = toX + AAA;
5666           moveList[endPV-1][3] = toY + ONE;
5667           parseList[endPV-1][0] = NULLCHAR;
5668           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5669         }
5670       }
5671     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5672     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5673     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5674     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5675         valid++; // allow comments in PV
5676         continue;
5677     }
5678     nr++;
5679     if(endPV+1 > framePtr) break; // no space, truncate
5680     if(!valid) break;
5681     endPV++;
5682     CopyBoard(boards[endPV], boards[endPV-1]);
5683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5684     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5685     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5686     CoordsToAlgebraic(boards[endPV - 1],
5687                              PosFlags(endPV - 1),
5688                              fromY, fromX, toY, toX, promoChar,
5689                              parseList[endPV - 1]);
5690   } while(valid);
5691   if(atEnd == 2) return; // used hidden, for PV conversion
5692   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5693   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5694   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5695                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5696   DrawPosition(TRUE, boards[currentMove]);
5697 }
5698
5699 int
5700 MultiPV (ChessProgramState *cps, int kind)
5701 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5702         int i;
5703         for(i=0; i<cps->nrOptions; i++) {
5704             char *s = cps->option[i].name;
5705             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5706             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5707                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5708         }
5709         return -1;
5710 }
5711
5712 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5713 static int multi, pv_margin;
5714 static ChessProgramState *activeCps;
5715
5716 Boolean
5717 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5718 {
5719         int startPV, lineStart, origIndex = index;
5720         char *p, buf2[MSG_SIZ];
5721         ChessProgramState *cps = (pane ? &second : &first);
5722
5723         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5724         lastX = x; lastY = y;
5725         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5726         lineStart = startPV = index;
5727         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5728         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5729         index = startPV;
5730         do{ while(buf[index] && buf[index] != '\n') index++;
5731         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5732         buf[index] = 0;
5733         if(lineStart == 0 && gameMode == AnalyzeMode) {
5734             int n = 0;
5735             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5736             if(n == 0) { // click not on "fewer" or "more"
5737                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5738                     pv_margin = cps->option[multi].value;
5739                     activeCps = cps; // non-null signals margin adjustment
5740                 }
5741             } else if((multi = MultiPV(cps, 1)) >= 0) {
5742                 n += cps->option[multi].value; if(n < 1) n = 1;
5743                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5744                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5745                 cps->option[multi].value = n;
5746                 *start = *end = 0;
5747                 return FALSE;
5748             }
5749         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5750                 ExcludeClick(origIndex - lineStart);
5751                 return FALSE;
5752         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5753                 Collapse(origIndex - lineStart);
5754                 return FALSE;
5755         }
5756         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5757         *start = startPV; *end = index-1;
5758         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5759         return TRUE;
5760 }
5761
5762 char *
5763 PvToSAN (char *pv)
5764 {
5765         static char buf[10*MSG_SIZ];
5766         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5767         *buf = NULLCHAR;
5768         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5769         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5770         for(i = forwardMostMove; i<endPV; i++){
5771             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5772             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5773             k += strlen(buf+k);
5774         }
5775         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5776         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5777         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5778         endPV = savedEnd;
5779         return buf;
5780 }
5781
5782 Boolean
5783 LoadPV (int x, int y)
5784 { // called on right mouse click to load PV
5785   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5786   lastX = x; lastY = y;
5787   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5788   extendGame = FALSE;
5789   return TRUE;
5790 }
5791
5792 void
5793 UnLoadPV ()
5794 {
5795   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5796   if(activeCps) {
5797     if(pv_margin != activeCps->option[multi].value) {
5798       char buf[MSG_SIZ];
5799       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5800       SendToProgram(buf, activeCps);
5801       activeCps->option[multi].value = pv_margin;
5802     }
5803     activeCps = NULL;
5804     return;
5805   }
5806   if(endPV < 0) return;
5807   if(appData.autoCopyPV) CopyFENToClipboard();
5808   endPV = -1;
5809   if(extendGame && currentMove > forwardMostMove) {
5810         Boolean saveAnimate = appData.animate;
5811         if(pushed) {
5812             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5813                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5814             } else storedGames--; // abandon shelved tail of original game
5815         }
5816         pushed = FALSE;
5817         forwardMostMove = currentMove;
5818         currentMove = oldFMM;
5819         appData.animate = FALSE;
5820         ToNrEvent(forwardMostMove);
5821         appData.animate = saveAnimate;
5822   }
5823   currentMove = forwardMostMove;
5824   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5825   ClearPremoveHighlights();
5826   DrawPosition(TRUE, boards[currentMove]);
5827 }
5828
5829 void
5830 MovePV (int x, int y, int h)
5831 { // step through PV based on mouse coordinates (called on mouse move)
5832   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5833
5834   if(activeCps) { // adjusting engine's multi-pv margin
5835     if(x > lastX) pv_margin++; else
5836     if(x < lastX) pv_margin -= (pv_margin > 0);
5837     if(x != lastX) {
5838       char buf[MSG_SIZ];
5839       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5840       DisplayMessage(buf, "");
5841     }
5842     lastX = x;
5843     return;
5844   }
5845   // we must somehow check if right button is still down (might be released off board!)
5846   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5847   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5848   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5849   if(!step) return;
5850   lastX = x; lastY = y;
5851
5852   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5853   if(endPV < 0) return;
5854   if(y < margin) step = 1; else
5855   if(y > h - margin) step = -1;
5856   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5857   currentMove += step;
5858   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5859   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5860                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5861   DrawPosition(FALSE, boards[currentMove]);
5862 }
5863
5864
5865 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5866 // All positions will have equal probability, but the current method will not provide a unique
5867 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5868 #define DARK 1
5869 #define LITE 2
5870 #define ANY 3
5871
5872 int squaresLeft[4];
5873 int piecesLeft[(int)BlackPawn];
5874 int seed, nrOfShuffles;
5875
5876 void
5877 GetPositionNumber ()
5878 {       // sets global variable seed
5879         int i;
5880
5881         seed = appData.defaultFrcPosition;
5882         if(seed < 0) { // randomize based on time for negative FRC position numbers
5883                 for(i=0; i<50; i++) seed += random();
5884                 seed = random() ^ random() >> 8 ^ random() << 8;
5885                 if(seed<0) seed = -seed;
5886         }
5887 }
5888
5889 int
5890 put (Board board, int pieceType, int rank, int n, int shade)
5891 // put the piece on the (n-1)-th empty squares of the given shade
5892 {
5893         int i;
5894
5895         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5896                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5897                         board[rank][i] = (ChessSquare) pieceType;
5898                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5899                         squaresLeft[ANY]--;
5900                         piecesLeft[pieceType]--;
5901                         return i;
5902                 }
5903         }
5904         return -1;
5905 }
5906
5907
5908 void
5909 AddOnePiece (Board board, int pieceType, int rank, int shade)
5910 // calculate where the next piece goes, (any empty square), and put it there
5911 {
5912         int i;
5913
5914         i = seed % squaresLeft[shade];
5915         nrOfShuffles *= squaresLeft[shade];
5916         seed /= squaresLeft[shade];
5917         put(board, pieceType, rank, i, shade);
5918 }
5919
5920 void
5921 AddTwoPieces (Board board, int pieceType, int rank)
5922 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5923 {
5924         int i, n=squaresLeft[ANY], j=n-1, k;
5925
5926         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5927         i = seed % k;  // pick one
5928         nrOfShuffles *= k;
5929         seed /= k;
5930         while(i >= j) i -= j--;
5931         j = n - 1 - j; i += j;
5932         put(board, pieceType, rank, j, ANY);
5933         put(board, pieceType, rank, i, ANY);
5934 }
5935
5936 void
5937 SetUpShuffle (Board board, int number)
5938 {
5939         int i, p, first=1;
5940
5941         GetPositionNumber(); nrOfShuffles = 1;
5942
5943         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5944         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5945         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5946
5947         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5948
5949         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5950             p = (int) board[0][i];
5951             if(p < (int) BlackPawn) piecesLeft[p] ++;
5952             board[0][i] = EmptySquare;
5953         }
5954
5955         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5956             // shuffles restricted to allow normal castling put KRR first
5957             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5958                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5959             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5960                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5961             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5962                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5963             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5964                 put(board, WhiteRook, 0, 0, ANY);
5965             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5966         }
5967
5968         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5969             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5970             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5971                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5972                 while(piecesLeft[p] >= 2) {
5973                     AddOnePiece(board, p, 0, LITE);
5974                     AddOnePiece(board, p, 0, DARK);
5975                 }
5976                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5977             }
5978
5979         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5980             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5981             // but we leave King and Rooks for last, to possibly obey FRC restriction
5982             if(p == (int)WhiteRook) continue;
5983             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5984             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5985         }
5986
5987         // now everything is placed, except perhaps King (Unicorn) and Rooks
5988
5989         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5990             // Last King gets castling rights
5991             while(piecesLeft[(int)WhiteUnicorn]) {
5992                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5993                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5994             }
5995
5996             while(piecesLeft[(int)WhiteKing]) {
5997                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5998                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5999             }
6000
6001
6002         } else {
6003             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6004             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6005         }
6006
6007         // Only Rooks can be left; simply place them all
6008         while(piecesLeft[(int)WhiteRook]) {
6009                 i = put(board, WhiteRook, 0, 0, ANY);
6010                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6011                         if(first) {
6012                                 first=0;
6013                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6014                         }
6015                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6016                 }
6017         }
6018         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6019             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6020         }
6021
6022         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6023 }
6024
6025 int
6026 ptclen (const char *s, char *escapes)
6027 {
6028     int n = 0;
6029     if(!*escapes) return strlen(s);
6030     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6031     return n;
6032 }
6033
6034 int
6035 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6036 /* [HGM] moved here from winboard.c because of its general usefulness */
6037 /*       Basically a safe strcpy that uses the last character as King */
6038 {
6039     int result = FALSE; int NrPieces;
6040     unsigned char partner[EmptySquare];
6041
6042     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6043                     && NrPieces >= 12 && !(NrPieces&1)) {
6044         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6045
6046         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6047         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6048             char *p, c=0;
6049             if(map[j] == '/') offs = WhitePBishop - i, j++;
6050             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6051             table[i+offs] = map[j++];
6052             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6053             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6054             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6055         }
6056         table[(int) WhiteKing]  = map[j++];
6057         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6058             char *p, c=0;
6059             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6060             i = WHITE_TO_BLACK ii;
6061             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6062             table[i+offs] = map[j++];
6063             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6064             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6065             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6066         }
6067         table[(int) BlackKing]  = map[j++];
6068
6069
6070         if(*escapes) { // set up promotion pairing
6071             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6072             // pieceToChar entirely filled, so we can look up specified partners
6073             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6074                 int c = table[i];
6075                 if(c == '^' || c == '-') { // has specified partner
6076                     int p;
6077                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6078                     if(c == '^') table[i] = '+';
6079                     if(p < EmptySquare) {
6080                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6081                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6082                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6083                     }
6084                 } else if(c == '*') {
6085                     table[i] = partner[i];
6086                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6087                 }
6088             }
6089         }
6090
6091         result = TRUE;
6092     }
6093
6094     return result;
6095 }
6096
6097 int
6098 SetCharTable (unsigned char *table, const char * map)
6099 {
6100     return SetCharTableEsc(table, map, "");
6101 }
6102
6103 void
6104 Prelude (Board board)
6105 {       // [HGM] superchess: random selection of exo-pieces
6106         int i, j, k; ChessSquare p;
6107         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6108
6109         GetPositionNumber(); // use FRC position number
6110
6111         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6112             SetCharTable(pieceToChar, appData.pieceToCharTable);
6113             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6114                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6115         }
6116
6117         j = seed%4;                 seed /= 4;
6118         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6119         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6120         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6121         j = seed%3 + (seed%3 >= j); seed /= 3;
6122         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6123         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6124         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6125         j = seed%3;                 seed /= 3;
6126         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%2 + (seed%2 >= j); seed /= 2;
6130         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6134         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6135         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6136         put(board, exoPieces[0],    0, 0, ANY);
6137         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6138 }
6139
6140 void
6141 InitPosition (int redraw)
6142 {
6143     ChessSquare (* pieces)[BOARD_FILES];
6144     int i, j, pawnRow=1, pieceRows=1, overrule,
6145     oldx = gameInfo.boardWidth,
6146     oldy = gameInfo.boardHeight,
6147     oldh = gameInfo.holdingsWidth;
6148     static int oldv;
6149
6150     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6151
6152     /* [AS] Initialize pv info list [HGM] and game status */
6153     {
6154         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6155             pvInfoList[i].depth = 0;
6156             boards[i][EP_STATUS] = EP_NONE;
6157             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6158         }
6159
6160         initialRulePlies = 0; /* 50-move counter start */
6161
6162         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6163         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6164     }
6165
6166
6167     /* [HGM] logic here is completely changed. In stead of full positions */
6168     /* the initialized data only consist of the two backranks. The switch */
6169     /* selects which one we will use, which is than copied to the Board   */
6170     /* initialPosition, which for the rest is initialized by Pawns and    */
6171     /* empty squares. This initial position is then copied to boards[0],  */
6172     /* possibly after shuffling, so that it remains available.            */
6173
6174     gameInfo.holdingsWidth = 0; /* default board sizes */
6175     gameInfo.boardWidth    = 8;
6176     gameInfo.boardHeight   = 8;
6177     gameInfo.holdingsSize  = 0;
6178     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6179     for(i=0; i<BOARD_FILES-6; i++)
6180       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6181     initialPosition[EP_STATUS] = EP_NONE;
6182     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6183     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6184     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6185          SetCharTable(pieceNickName, appData.pieceNickNames);
6186     else SetCharTable(pieceNickName, "............");
6187     pieces = FIDEArray;
6188
6189     switch (gameInfo.variant) {
6190     case VariantFischeRandom:
6191       shuffleOpenings = TRUE;
6192       appData.fischerCastling = TRUE;
6193     default:
6194       break;
6195     case VariantShatranj:
6196       pieces = ShatranjArray;
6197       nrCastlingRights = 0;
6198       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6199       break;
6200     case VariantMakruk:
6201       pieces = makrukArray;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6204       break;
6205     case VariantASEAN:
6206       pieces = aseanArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6209       break;
6210     case VariantTwoKings:
6211       pieces = twoKingsArray;
6212       break;
6213     case VariantGrand:
6214       pieces = GrandArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6217       gameInfo.boardWidth = 10;
6218       gameInfo.boardHeight = 10;
6219       gameInfo.holdingsSize = 7;
6220       break;
6221     case VariantCapaRandom:
6222       shuffleOpenings = TRUE;
6223       appData.fischerCastling = TRUE;
6224     case VariantCapablanca:
6225       pieces = CapablancaArray;
6226       gameInfo.boardWidth = 10;
6227       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6228       break;
6229     case VariantGothic:
6230       pieces = GothicArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6233       break;
6234     case VariantSChess:
6235       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6236       gameInfo.holdingsSize = 7;
6237       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6238       break;
6239     case VariantJanus:
6240       pieces = JanusArray;
6241       gameInfo.boardWidth = 10;
6242       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6243       nrCastlingRights = 6;
6244         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6245         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6246         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6247         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6248         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6249         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6250       break;
6251     case VariantFalcon:
6252       pieces = FalconArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6255       break;
6256     case VariantXiangqi:
6257       pieces = XiangqiArray;
6258       gameInfo.boardWidth  = 9;
6259       gameInfo.boardHeight = 10;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6262       break;
6263     case VariantShogi:
6264       pieces = ShogiArray;
6265       gameInfo.boardWidth  = 9;
6266       gameInfo.boardHeight = 9;
6267       gameInfo.holdingsSize = 7;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6270       break;
6271     case VariantChu:
6272       pieces = ChuArray; pieceRows = 3;
6273       gameInfo.boardWidth  = 12;
6274       gameInfo.boardHeight = 12;
6275       nrCastlingRights = 0;
6276       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6277                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6278       break;
6279     case VariantCourier:
6280       pieces = CourierArray;
6281       gameInfo.boardWidth  = 12;
6282       nrCastlingRights = 0;
6283       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6284       break;
6285     case VariantKnightmate:
6286       pieces = KnightmateArray;
6287       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6288       break;
6289     case VariantSpartan:
6290       pieces = SpartanArray;
6291       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6292       break;
6293     case VariantLion:
6294       pieces = lionArray;
6295       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6296       break;
6297     case VariantChuChess:
6298       pieces = ChuChessArray;
6299       gameInfo.boardWidth = 10;
6300       gameInfo.boardHeight = 10;
6301       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6302       break;
6303     case VariantFairy:
6304       pieces = fairyArray;
6305       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6306       break;
6307     case VariantGreat:
6308       pieces = GreatArray;
6309       gameInfo.boardWidth = 10;
6310       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6311       gameInfo.holdingsSize = 8;
6312       break;
6313     case VariantSuper:
6314       pieces = FIDEArray;
6315       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6316       gameInfo.holdingsSize = 8;
6317       startedFromSetupPosition = TRUE;
6318       break;
6319     case VariantCrazyhouse:
6320     case VariantBughouse:
6321       pieces = FIDEArray;
6322       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6323       gameInfo.holdingsSize = 5;
6324       break;
6325     case VariantWildCastle:
6326       pieces = FIDEArray;
6327       /* !!?shuffle with kings guaranteed to be on d or e file */
6328       shuffleOpenings = 1;
6329       break;
6330     case VariantNoCastle:
6331       pieces = FIDEArray;
6332       nrCastlingRights = 0;
6333       /* !!?unconstrained back-rank shuffle */
6334       shuffleOpenings = 1;
6335       break;
6336     }
6337
6338     overrule = 0;
6339     if(appData.NrFiles >= 0) {
6340         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6341         gameInfo.boardWidth = appData.NrFiles;
6342     }
6343     if(appData.NrRanks >= 0) {
6344         gameInfo.boardHeight = appData.NrRanks;
6345     }
6346     if(appData.holdingsSize >= 0) {
6347         i = appData.holdingsSize;
6348         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6349         gameInfo.holdingsSize = i;
6350     }
6351     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6352     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6353         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6354
6355     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6356     if(pawnRow < 1) pawnRow = 1;
6357     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6358        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6359     if(gameInfo.variant == VariantChu) pawnRow = 3;
6360
6361     /* User pieceToChar list overrules defaults */
6362     if(appData.pieceToCharTable != NULL)
6363         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6364
6365     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6366
6367         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6368             s = (ChessSquare) 0; /* account holding counts in guard band */
6369         for( i=0; i<BOARD_HEIGHT; i++ )
6370             initialPosition[i][j] = s;
6371
6372         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6373         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6374         initialPosition[pawnRow][j] = WhitePawn;
6375         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6376         if(gameInfo.variant == VariantXiangqi) {
6377             if(j&1) {
6378                 initialPosition[pawnRow][j] =
6379                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6380                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6381                    initialPosition[2][j] = WhiteCannon;
6382                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6383                 }
6384             }
6385         }
6386         if(gameInfo.variant == VariantChu) {
6387              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6388                initialPosition[pawnRow+1][j] = WhiteCobra,
6389                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6390              for(i=1; i<pieceRows; i++) {
6391                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6392                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6393              }
6394         }
6395         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6396             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6397                initialPosition[0][j] = WhiteRook;
6398                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6399             }
6400         }
6401         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6402     }
6403     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6404     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6405
6406             j=BOARD_LEFT+1;
6407             initialPosition[1][j] = WhiteBishop;
6408             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6409             j=BOARD_RGHT-2;
6410             initialPosition[1][j] = WhiteRook;
6411             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6412     }
6413
6414     if( nrCastlingRights == -1) {
6415         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6416         /*       This sets default castling rights from none to normal corners   */
6417         /* Variants with other castling rights must set them themselves above    */
6418         nrCastlingRights = 6;
6419
6420         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6421         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6422         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6423         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6424         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6425         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6426      }
6427
6428      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6429      if(gameInfo.variant == VariantGreat) { // promotion commoners
6430         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6431         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6432         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6433         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6434      }
6435      if( gameInfo.variant == VariantSChess ) {
6436       initialPosition[1][0] = BlackMarshall;
6437       initialPosition[2][0] = BlackAngel;
6438       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6439       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6440       initialPosition[1][1] = initialPosition[2][1] =
6441       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6442      }
6443   if (appData.debugMode) {
6444     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6445   }
6446     if(shuffleOpenings) {
6447         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6448         startedFromSetupPosition = TRUE;
6449     }
6450     if(startedFromPositionFile) {
6451       /* [HGM] loadPos: use PositionFile for every new game */
6452       CopyBoard(initialPosition, filePosition);
6453       for(i=0; i<nrCastlingRights; i++)
6454           initialRights[i] = filePosition[CASTLING][i];
6455       startedFromSetupPosition = TRUE;
6456     }
6457
6458     CopyBoard(boards[0], initialPosition);
6459
6460     if(oldx != gameInfo.boardWidth ||
6461        oldy != gameInfo.boardHeight ||
6462        oldv != gameInfo.variant ||
6463        oldh != gameInfo.holdingsWidth
6464                                          )
6465             InitDrawingSizes(-2 ,0);
6466
6467     oldv = gameInfo.variant;
6468     if (redraw)
6469       DrawPosition(TRUE, boards[currentMove]);
6470 }
6471
6472 void
6473 SendBoard (ChessProgramState *cps, int moveNum)
6474 {
6475     char message[MSG_SIZ];
6476
6477     if (cps->useSetboard) {
6478       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6479       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6480       SendToProgram(message, cps);
6481       free(fen);
6482
6483     } else {
6484       ChessSquare *bp;
6485       int i, j, left=0, right=BOARD_WIDTH;
6486       /* Kludge to set black to move, avoiding the troublesome and now
6487        * deprecated "black" command.
6488        */
6489       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6490         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6491
6492       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6493
6494       SendToProgram("edit\n", cps);
6495       SendToProgram("#\n", cps);
6496       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6497         bp = &boards[moveNum][i][left];
6498         for (j = left; j < right; j++, bp++) {
6499           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6500           if ((int) *bp < (int) BlackPawn) {
6501             if(j == BOARD_RGHT+1)
6502                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6503             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6504             if(message[0] == '+' || message[0] == '~') {
6505               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6506                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6507                         AAA + j, ONE + i - '0');
6508             }
6509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6510                 message[1] = BOARD_RGHT   - 1 - j + '1';
6511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6512             }
6513             SendToProgram(message, cps);
6514           }
6515         }
6516       }
6517
6518       SendToProgram("c\n", cps);
6519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6520         bp = &boards[moveNum][i][left];
6521         for (j = left; j < right; j++, bp++) {
6522           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6523           if (((int) *bp != (int) EmptySquare)
6524               && ((int) *bp >= (int) BlackPawn)) {
6525             if(j == BOARD_LEFT-2)
6526                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6527             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6528                     AAA + j, ONE + i - '0');
6529             if(message[0] == '+' || message[0] == '~') {
6530               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6531                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6532                         AAA + j, ONE + i - '0');
6533             }
6534             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6535                 message[1] = BOARD_RGHT   - 1 - j + '1';
6536                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6537             }
6538             SendToProgram(message, cps);
6539           }
6540         }
6541       }
6542
6543       SendToProgram(".\n", cps);
6544     }
6545     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6546 }
6547
6548 char exclusionHeader[MSG_SIZ];
6549 int exCnt, excludePtr;
6550 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6551 static Exclusion excluTab[200];
6552 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6553
6554 static void
6555 WriteMap (int s)
6556 {
6557     int j;
6558     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6559     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6560 }
6561
6562 static void
6563 ClearMap ()
6564 {
6565     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6566     excludePtr = 24; exCnt = 0;
6567     WriteMap(0);
6568 }
6569
6570 static void
6571 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6572 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6573     char buf[2*MOVE_LEN], *p;
6574     Exclusion *e = excluTab;
6575     int i;
6576     for(i=0; i<exCnt; i++)
6577         if(e[i].ff == fromX && e[i].fr == fromY &&
6578            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6579     if(i == exCnt) { // was not in exclude list; add it
6580         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6581         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6582             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6583             return; // abort
6584         }
6585         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6586         excludePtr++; e[i].mark = excludePtr++;
6587         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6588         exCnt++;
6589     }
6590     exclusionHeader[e[i].mark] = state;
6591 }
6592
6593 static int
6594 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6595 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6596     char buf[MSG_SIZ];
6597     int j, k;
6598     ChessMove moveType;
6599     if((signed char)promoChar == -1) { // kludge to indicate best move
6600         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6601             return 1; // if unparsable, abort
6602     }
6603     // update exclusion map (resolving toggle by consulting existing state)
6604     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6605     j = k%8; k >>= 3;
6606     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6607     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6608          excludeMap[k] |=   1<<j;
6609     else excludeMap[k] &= ~(1<<j);
6610     // update header
6611     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6612     // inform engine
6613     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6614     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6615     SendToBoth(buf);
6616     return (state == '+');
6617 }
6618
6619 static void
6620 ExcludeClick (int index)
6621 {
6622     int i, j;
6623     Exclusion *e = excluTab;
6624     if(index < 25) { // none, best or tail clicked
6625         if(index < 13) { // none: include all
6626             WriteMap(0); // clear map
6627             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6628             SendToBoth("include all\n"); // and inform engine
6629         } else if(index > 18) { // tail
6630             if(exclusionHeader[19] == '-') { // tail was excluded
6631                 SendToBoth("include all\n");
6632                 WriteMap(0); // clear map completely
6633                 // now re-exclude selected moves
6634                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6635                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6636             } else { // tail was included or in mixed state
6637                 SendToBoth("exclude all\n");
6638                 WriteMap(0xFF); // fill map completely
6639                 // now re-include selected moves
6640                 j = 0; // count them
6641                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6642                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6643                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6644             }
6645         } else { // best
6646             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6647         }
6648     } else {
6649         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6650             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6651             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6652             break;
6653         }
6654     }
6655 }
6656
6657 ChessSquare
6658 DefaultPromoChoice (int white)
6659 {
6660     ChessSquare result;
6661     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6662        gameInfo.variant == VariantMakruk)
6663         result = WhiteFerz; // no choice
6664     else if(gameInfo.variant == VariantASEAN)
6665         result = WhiteRook; // no choice
6666     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6667         result= WhiteKing; // in Suicide Q is the last thing we want
6668     else if(gameInfo.variant == VariantSpartan)
6669         result = white ? WhiteQueen : WhiteAngel;
6670     else result = WhiteQueen;
6671     if(!white) result = WHITE_TO_BLACK result;
6672     return result;
6673 }
6674
6675 static int autoQueen; // [HGM] oneclick
6676
6677 int
6678 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6679 {
6680     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6681     /* [HGM] add Shogi promotions */
6682     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6683     ChessSquare piece, partner;
6684     ChessMove moveType;
6685     Boolean premove;
6686
6687     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6688     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6689
6690     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6691       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6692         return FALSE;
6693
6694     piece = boards[currentMove][fromY][fromX];
6695     if(gameInfo.variant == VariantChu) {
6696         promotionZoneSize = BOARD_HEIGHT/3;
6697         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6698     } else if(gameInfo.variant == VariantShogi) {
6699         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6700         highestPromotingPiece = (int)WhiteAlfil;
6701     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6702         promotionZoneSize = 3;
6703     }
6704
6705     // Treat Lance as Pawn when it is not representing Amazon or Lance
6706     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6707         if(piece == WhiteLance) piece = WhitePawn; else
6708         if(piece == BlackLance) piece = BlackPawn;
6709     }
6710
6711     // next weed out all moves that do not touch the promotion zone at all
6712     if((int)piece >= BlackPawn) {
6713         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6714              return FALSE;
6715         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6716         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6717     } else {
6718         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6719            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6720         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6721              return FALSE;
6722     }
6723
6724     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6725
6726     // weed out mandatory Shogi promotions
6727     if(gameInfo.variant == VariantShogi) {
6728         if(piece >= BlackPawn) {
6729             if(toY == 0 && piece == BlackPawn ||
6730                toY == 0 && piece == BlackQueen ||
6731                toY <= 1 && piece == BlackKnight) {
6732                 *promoChoice = '+';
6733                 return FALSE;
6734             }
6735         } else {
6736             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6737                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6738                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         }
6743     }
6744
6745     // weed out obviously illegal Pawn moves
6746     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6747         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6748         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6749         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6750         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6751         // note we are not allowed to test for valid (non-)capture, due to premove
6752     }
6753
6754     // we either have a choice what to promote to, or (in Shogi) whether to promote
6755     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6756        gameInfo.variant == VariantMakruk) {
6757         ChessSquare p=BlackFerz;  // no choice
6758         while(p < EmptySquare) {  //but make sure we use piece that exists
6759             *promoChoice = PieceToChar(p++);
6760             if(*promoChoice != '.') break;
6761         }
6762         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6763     }
6764     // no sense asking what we must promote to if it is going to explode...
6765     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6766         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6767         return FALSE;
6768     }
6769     // give caller the default choice even if we will not make it
6770     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6771     partner = piece; // pieces can promote if the pieceToCharTable says so
6772     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6773     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6774     if(        sweepSelect && gameInfo.variant != VariantGreat
6775                            && gameInfo.variant != VariantGrand
6776                            && gameInfo.variant != VariantSuper) return FALSE;
6777     if(autoQueen) return FALSE; // predetermined
6778
6779     // suppress promotion popup on illegal moves that are not premoves
6780     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6781               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6782     if(appData.testLegality && !premove) {
6783         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6784                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6785         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6786         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6787             return FALSE;
6788     }
6789
6790     return TRUE;
6791 }
6792
6793 int
6794 InPalace (int row, int column)
6795 {   /* [HGM] for Xiangqi */
6796     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6797          column < (BOARD_WIDTH + 4)/2 &&
6798          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6799     return FALSE;
6800 }
6801
6802 int
6803 PieceForSquare (int x, int y)
6804 {
6805   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6806      return -1;
6807   else
6808      return boards[currentMove][y][x];
6809 }
6810
6811 int
6812 OKToStartUserMove (int x, int y)
6813 {
6814     ChessSquare from_piece;
6815     int white_piece;
6816
6817     if (matchMode) return FALSE;
6818     if (gameMode == EditPosition) return TRUE;
6819
6820     if (x >= 0 && y >= 0)
6821       from_piece = boards[currentMove][y][x];
6822     else
6823       from_piece = EmptySquare;
6824
6825     if (from_piece == EmptySquare) return FALSE;
6826
6827     white_piece = (int)from_piece >= (int)WhitePawn &&
6828       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6829
6830     switch (gameMode) {
6831       case AnalyzeFile:
6832       case TwoMachinesPlay:
6833       case EndOfGame:
6834         return FALSE;
6835
6836       case IcsObserving:
6837       case IcsIdle:
6838         return FALSE;
6839
6840       case MachinePlaysWhite:
6841       case IcsPlayingBlack:
6842         if (appData.zippyPlay) return FALSE;
6843         if (white_piece) {
6844             DisplayMoveError(_("You are playing Black"));
6845             return FALSE;
6846         }
6847         break;
6848
6849       case MachinePlaysBlack:
6850       case IcsPlayingWhite:
6851         if (appData.zippyPlay) return FALSE;
6852         if (!white_piece) {
6853             DisplayMoveError(_("You are playing White"));
6854             return FALSE;
6855         }
6856         break;
6857
6858       case PlayFromGameFile:
6859             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6860       case EditGame:
6861       case AnalyzeMode:
6862         if (!white_piece && WhiteOnMove(currentMove)) {
6863             DisplayMoveError(_("It is White's turn"));
6864             return FALSE;
6865         }
6866         if (white_piece && !WhiteOnMove(currentMove)) {
6867             DisplayMoveError(_("It is Black's turn"));
6868             return FALSE;
6869         }
6870         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6871             /* Editing correspondence game history */
6872             /* Could disallow this or prompt for confirmation */
6873             cmailOldMove = -1;
6874         }
6875         break;
6876
6877       case BeginningOfGame:
6878         if (appData.icsActive) return FALSE;
6879         if (!appData.noChessProgram) {
6880             if (!white_piece) {
6881                 DisplayMoveError(_("You are playing White"));
6882                 return FALSE;
6883             }
6884         }
6885         break;
6886
6887       case Training:
6888         if (!white_piece && WhiteOnMove(currentMove)) {
6889             DisplayMoveError(_("It is White's turn"));
6890             return FALSE;
6891         }
6892         if (white_piece && !WhiteOnMove(currentMove)) {
6893             DisplayMoveError(_("It is Black's turn"));
6894             return FALSE;
6895         }
6896         break;
6897
6898       default:
6899       case IcsExamining:
6900         break;
6901     }
6902     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6903         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6904         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6905         && gameMode != AnalyzeFile && gameMode != Training) {
6906         DisplayMoveError(_("Displayed position is not current"));
6907         return FALSE;
6908     }
6909     return TRUE;
6910 }
6911
6912 Boolean
6913 OnlyMove (int *x, int *y, Boolean captures)
6914 {
6915     DisambiguateClosure cl;
6916     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6917     switch(gameMode) {
6918       case MachinePlaysBlack:
6919       case IcsPlayingWhite:
6920       case BeginningOfGame:
6921         if(!WhiteOnMove(currentMove)) return FALSE;
6922         break;
6923       case MachinePlaysWhite:
6924       case IcsPlayingBlack:
6925         if(WhiteOnMove(currentMove)) return FALSE;
6926         break;
6927       case EditGame:
6928         break;
6929       default:
6930         return FALSE;
6931     }
6932     cl.pieceIn = EmptySquare;
6933     cl.rfIn = *y;
6934     cl.ffIn = *x;
6935     cl.rtIn = -1;
6936     cl.ftIn = -1;
6937     cl.promoCharIn = NULLCHAR;
6938     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6939     if( cl.kind == NormalMove ||
6940         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6941         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6942         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6943       fromX = cl.ff;
6944       fromY = cl.rf;
6945       *x = cl.ft;
6946       *y = cl.rt;
6947       return TRUE;
6948     }
6949     if(cl.kind != ImpossibleMove) return FALSE;
6950     cl.pieceIn = EmptySquare;
6951     cl.rfIn = -1;
6952     cl.ffIn = -1;
6953     cl.rtIn = *y;
6954     cl.ftIn = *x;
6955     cl.promoCharIn = NULLCHAR;
6956     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6957     if( cl.kind == NormalMove ||
6958         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6959         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6960         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6961       fromX = cl.ff;
6962       fromY = cl.rf;
6963       *x = cl.ft;
6964       *y = cl.rt;
6965       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6966       return TRUE;
6967     }
6968     return FALSE;
6969 }
6970
6971 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6972 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6973 int lastLoadGameUseList = FALSE;
6974 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6975 ChessMove lastLoadGameStart = EndOfFile;
6976 int doubleClick;
6977 Boolean addToBookFlag;
6978
6979 void
6980 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6981 {
6982     ChessMove moveType;
6983     ChessSquare pup;
6984     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6985
6986     /* Check if the user is playing in turn.  This is complicated because we
6987        let the user "pick up" a piece before it is his turn.  So the piece he
6988        tried to pick up may have been captured by the time he puts it down!
6989        Therefore we use the color the user is supposed to be playing in this
6990        test, not the color of the piece that is currently on the starting
6991        square---except in EditGame mode, where the user is playing both
6992        sides; fortunately there the capture race can't happen.  (It can
6993        now happen in IcsExamining mode, but that's just too bad.  The user
6994        will get a somewhat confusing message in that case.)
6995        */
6996
6997     switch (gameMode) {
6998       case AnalyzeFile:
6999       case TwoMachinesPlay:
7000       case EndOfGame:
7001       case IcsObserving:
7002       case IcsIdle:
7003         /* We switched into a game mode where moves are not accepted,
7004            perhaps while the mouse button was down. */
7005         return;
7006
7007       case MachinePlaysWhite:
7008         /* User is moving for Black */
7009         if (WhiteOnMove(currentMove)) {
7010             DisplayMoveError(_("It is White's turn"));
7011             return;
7012         }
7013         break;
7014
7015       case MachinePlaysBlack:
7016         /* User is moving for White */
7017         if (!WhiteOnMove(currentMove)) {
7018             DisplayMoveError(_("It is Black's turn"));
7019             return;
7020         }
7021         break;
7022
7023       case PlayFromGameFile:
7024             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7025       case EditGame:
7026       case IcsExamining:
7027       case BeginningOfGame:
7028       case AnalyzeMode:
7029       case Training:
7030         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7031         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7032             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7033             /* User is moving for Black */
7034             if (WhiteOnMove(currentMove)) {
7035                 DisplayMoveError(_("It is White's turn"));
7036                 return;
7037             }
7038         } else {
7039             /* User is moving for White */
7040             if (!WhiteOnMove(currentMove)) {
7041                 DisplayMoveError(_("It is Black's turn"));
7042                 return;
7043             }
7044         }
7045         break;
7046
7047       case IcsPlayingBlack:
7048         /* User is moving for Black */
7049         if (WhiteOnMove(currentMove)) {
7050             if (!appData.premove) {
7051                 DisplayMoveError(_("It is White's turn"));
7052             } else if (toX >= 0 && toY >= 0) {
7053                 premoveToX = toX;
7054                 premoveToY = toY;
7055                 premoveFromX = fromX;
7056                 premoveFromY = fromY;
7057                 premovePromoChar = promoChar;
7058                 gotPremove = 1;
7059                 if (appData.debugMode)
7060                     fprintf(debugFP, "Got premove: fromX %d,"
7061                             "fromY %d, toX %d, toY %d\n",
7062                             fromX, fromY, toX, toY);
7063             }
7064             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7065             return;
7066         }
7067         break;
7068
7069       case IcsPlayingWhite:
7070         /* User is moving for White */
7071         if (!WhiteOnMove(currentMove)) {
7072             if (!appData.premove) {
7073                 DisplayMoveError(_("It is Black's turn"));
7074             } else if (toX >= 0 && toY >= 0) {
7075                 premoveToX = toX;
7076                 premoveToY = toY;
7077                 premoveFromX = fromX;
7078                 premoveFromY = fromY;
7079                 premovePromoChar = promoChar;
7080                 gotPremove = 1;
7081                 if (appData.debugMode)
7082                     fprintf(debugFP, "Got premove: fromX %d,"
7083                             "fromY %d, toX %d, toY %d\n",
7084                             fromX, fromY, toX, toY);
7085             }
7086             DrawPosition(TRUE, boards[currentMove]);
7087             return;
7088         }
7089         break;
7090
7091       default:
7092         break;
7093
7094       case EditPosition:
7095         /* EditPosition, empty square, or different color piece;
7096            click-click move is possible */
7097         if (toX == -2 || toY == -2) {
7098             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7099             DrawPosition(FALSE, boards[currentMove]);
7100             return;
7101         } else if (toX >= 0 && toY >= 0) {
7102             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7103                 ChessSquare p = boards[0][rf][ff];
7104                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7105                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7106             }
7107             boards[0][toY][toX] = boards[0][fromY][fromX];
7108             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7109                 if(boards[0][fromY][0] != EmptySquare) {
7110                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7111                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7112                 }
7113             } else
7114             if(fromX == BOARD_RGHT+1) {
7115                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7116                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7117                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7118                 }
7119             } else
7120             boards[0][fromY][fromX] = gatingPiece;
7121             ClearHighlights();
7122             DrawPosition(FALSE, boards[currentMove]);
7123             return;
7124         }
7125         return;
7126     }
7127
7128     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7129     pup = boards[currentMove][toY][toX];
7130
7131     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7132     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7133          if( pup != EmptySquare ) return;
7134          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7135            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7136                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7137            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7138            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7139            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7140            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7141          fromY = DROP_RANK;
7142     }
7143
7144     /* [HGM] always test for legality, to get promotion info */
7145     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7146                                          fromY, fromX, toY, toX, promoChar);
7147
7148     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7149
7150     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7151
7152     /* [HGM] but possibly ignore an IllegalMove result */
7153     if (appData.testLegality) {
7154         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7155             DisplayMoveError(_("Illegal move"));
7156             return;
7157         }
7158     }
7159
7160     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7161         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7162              ClearPremoveHighlights(); // was included
7163         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7164         return;
7165     }
7166
7167     if(addToBookFlag) { // adding moves to book
7168         char buf[MSG_SIZ], move[MSG_SIZ];
7169         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7170         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7171                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7172         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7173         AddBookMove(buf);
7174         addToBookFlag = FALSE;
7175         ClearHighlights();
7176         return;
7177     }
7178
7179     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7180 }
7181
7182 /* Common tail of UserMoveEvent and DropMenuEvent */
7183 int
7184 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7185 {
7186     char *bookHit = 0;
7187
7188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7189         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7191         if(WhiteOnMove(currentMove)) {
7192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7193         } else {
7194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7195         }
7196     }
7197
7198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7199        move type in caller when we know the move is a legal promotion */
7200     if(moveType == NormalMove && promoChar)
7201         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7202
7203     /* [HGM] <popupFix> The following if has been moved here from
7204        UserMoveEvent(). Because it seemed to belong here (why not allow
7205        piece drops in training games?), and because it can only be
7206        performed after it is known to what we promote. */
7207     if (gameMode == Training) {
7208       /* compare the move played on the board to the next move in the
7209        * game. If they match, display the move and the opponent's response.
7210        * If they don't match, display an error message.
7211        */
7212       int saveAnimate;
7213       Board testBoard;
7214       CopyBoard(testBoard, boards[currentMove]);
7215       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7216
7217       if (CompareBoards(testBoard, boards[currentMove+1])) {
7218         ForwardInner(currentMove+1);
7219
7220         /* Autoplay the opponent's response.
7221          * if appData.animate was TRUE when Training mode was entered,
7222          * the response will be animated.
7223          */
7224         saveAnimate = appData.animate;
7225         appData.animate = animateTraining;
7226         ForwardInner(currentMove+1);
7227         appData.animate = saveAnimate;
7228
7229         /* check for the end of the game */
7230         if (currentMove >= forwardMostMove) {
7231           gameMode = PlayFromGameFile;
7232           ModeHighlight();
7233           SetTrainingModeOff();
7234           DisplayInformation(_("End of game"));
7235         }
7236       } else {
7237         DisplayError(_("Incorrect move"), 0);
7238       }
7239       return 1;
7240     }
7241
7242   /* Ok, now we know that the move is good, so we can kill
7243      the previous line in Analysis Mode */
7244   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7245                                 && currentMove < forwardMostMove) {
7246     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7247     else forwardMostMove = currentMove;
7248   }
7249
7250   ClearMap();
7251
7252   /* If we need the chess program but it's dead, restart it */
7253   ResurrectChessProgram();
7254
7255   /* A user move restarts a paused game*/
7256   if (pausing)
7257     PauseEvent();
7258
7259   thinkOutput[0] = NULLCHAR;
7260
7261   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7262
7263   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7264     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7265     return 1;
7266   }
7267
7268   if (gameMode == BeginningOfGame) {
7269     if (appData.noChessProgram) {
7270       gameMode = EditGame;
7271       SetGameInfo();
7272     } else {
7273       char buf[MSG_SIZ];
7274       gameMode = MachinePlaysBlack;
7275       StartClocks();
7276       SetGameInfo();
7277       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7278       DisplayTitle(buf);
7279       if (first.sendName) {
7280         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7281         SendToProgram(buf, &first);
7282       }
7283       StartClocks();
7284     }
7285     ModeHighlight();
7286   }
7287
7288   /* Relay move to ICS or chess engine */
7289   if (appData.icsActive) {
7290     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7291         gameMode == IcsExamining) {
7292       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7293         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7294         SendToICS("draw ");
7295         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7296       }
7297       // also send plain move, in case ICS does not understand atomic claims
7298       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7299       ics_user_moved = 1;
7300     }
7301   } else {
7302     if (first.sendTime && (gameMode == BeginningOfGame ||
7303                            gameMode == MachinePlaysWhite ||
7304                            gameMode == MachinePlaysBlack)) {
7305       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7306     }
7307     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7308          // [HGM] book: if program might be playing, let it use book
7309         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7310         first.maybeThinking = TRUE;
7311     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7312         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7313         SendBoard(&first, currentMove+1);
7314         if(second.analyzing) {
7315             if(!second.useSetboard) SendToProgram("undo\n", &second);
7316             SendBoard(&second, currentMove+1);
7317         }
7318     } else {
7319         SendMoveToProgram(forwardMostMove-1, &first);
7320         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7321     }
7322     if (currentMove == cmailOldMove + 1) {
7323       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7324     }
7325   }
7326
7327   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7328
7329   switch (gameMode) {
7330   case EditGame:
7331     if(appData.testLegality)
7332     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7333     case MT_NONE:
7334     case MT_CHECK:
7335       break;
7336     case MT_CHECKMATE:
7337     case MT_STAINMATE:
7338       if (WhiteOnMove(currentMove)) {
7339         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7340       } else {
7341         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7342       }
7343       break;
7344     case MT_STALEMATE:
7345       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7346       break;
7347     }
7348     break;
7349
7350   case MachinePlaysBlack:
7351   case MachinePlaysWhite:
7352     /* disable certain menu options while machine is thinking */
7353     SetMachineThinkingEnables();
7354     break;
7355
7356   default:
7357     break;
7358   }
7359
7360   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7361   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7362
7363   if(bookHit) { // [HGM] book: simulate book reply
7364         static char bookMove[MSG_SIZ]; // a bit generous?
7365
7366         programStats.nodes = programStats.depth = programStats.time =
7367         programStats.score = programStats.got_only_move = 0;
7368         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7369
7370         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7371         strcat(bookMove, bookHit);
7372         HandleMachineMove(bookMove, &first);
7373   }
7374   return 1;
7375 }
7376
7377 void
7378 MarkByFEN(char *fen)
7379 {
7380         int r, f;
7381         if(!appData.markers || !appData.highlightDragging) return;
7382         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7383         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7384         while(*fen) {
7385             int s = 0;
7386             marker[r][f] = 0;
7387             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7388             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7389             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7390             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7391             if(*fen == 'T') marker[r][f++] = 0; else
7392             if(*fen == 'Y') marker[r][f++] = 1; else
7393             if(*fen == 'G') marker[r][f++] = 3; else
7394             if(*fen == 'B') marker[r][f++] = 4; else
7395             if(*fen == 'C') marker[r][f++] = 5; else
7396             if(*fen == 'M') marker[r][f++] = 6; else
7397             if(*fen == 'W') marker[r][f++] = 7; else
7398             if(*fen == 'D') marker[r][f++] = 8; else
7399             if(*fen == 'R') marker[r][f++] = 2; else {
7400                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7401               f += s; fen -= s>0;
7402             }
7403             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7404             if(r < 0) break;
7405             fen++;
7406         }
7407         DrawPosition(TRUE, NULL);
7408 }
7409
7410 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7411
7412 void
7413 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7414 {
7415     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7416     Markers *m = (Markers *) closure;
7417     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7418                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7419         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7420                          || kind == WhiteCapturesEnPassant
7421                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7422     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7423 }
7424
7425 static int hoverSavedValid;
7426
7427 void
7428 MarkTargetSquares (int clear)
7429 {
7430   int x, y, sum=0;
7431   if(clear) { // no reason to ever suppress clearing
7432     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7433     hoverSavedValid = 0;
7434     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7435   } else {
7436     int capt = 0;
7437     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7438        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7439     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7440     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7441       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7442       if(capt)
7443       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7444     }
7445   }
7446   DrawPosition(FALSE, NULL);
7447 }
7448
7449 int
7450 Explode (Board board, int fromX, int fromY, int toX, int toY)
7451 {
7452     if(gameInfo.variant == VariantAtomic &&
7453        (board[toY][toX] != EmptySquare ||                     // capture?
7454         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7455                          board[fromY][fromX] == BlackPawn   )
7456       )) {
7457         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7458         return TRUE;
7459     }
7460     return FALSE;
7461 }
7462
7463 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7464
7465 int
7466 CanPromote (ChessSquare piece, int y)
7467 {
7468         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7469         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7470         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7471         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7472            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7473           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7474            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7475         return (piece == BlackPawn && y <= zone ||
7476                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7477                 piece == BlackLance && y <= zone ||
7478                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7479 }
7480
7481 void
7482 HoverEvent (int xPix, int yPix, int x, int y)
7483 {
7484         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7485         int r, f;
7486         if(!first.highlight) return;
7487         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7488         if(x == oldX && y == oldY) return; // only do something if we enter new square
7489         oldFromX = fromX; oldFromY = fromY;
7490         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7491           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7492             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7493           hoverSavedValid = 1;
7494         } else if(oldX != x || oldY != y) {
7495           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7496           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7497           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7498             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7499           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7500             char buf[MSG_SIZ];
7501             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7502             SendToProgram(buf, &first);
7503           }
7504           oldX = x; oldY = y;
7505 //        SetHighlights(fromX, fromY, x, y);
7506         }
7507 }
7508
7509 void ReportClick(char *action, int x, int y)
7510 {
7511         char buf[MSG_SIZ]; // Inform engine of what user does
7512         int r, f;
7513         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7516         if(!first.highlight || gameMode == EditPosition) return;
7517         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7518         SendToProgram(buf, &first);
7519 }
7520
7521 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7522
7523 void
7524 LeftClick (ClickType clickType, int xPix, int yPix)
7525 {
7526     int x, y;
7527     Boolean saveAnimate;
7528     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7529     char promoChoice = NULLCHAR;
7530     ChessSquare piece;
7531     static TimeMark lastClickTime, prevClickTime;
7532
7533     if(flashing) return;
7534
7535     x = EventToSquare(xPix, BOARD_WIDTH);
7536     y = EventToSquare(yPix, BOARD_HEIGHT);
7537     if (!flipView && y >= 0) {
7538         y = BOARD_HEIGHT - 1 - y;
7539     }
7540     if (flipView && x >= 0) {
7541         x = BOARD_WIDTH - 1 - x;
7542     }
7543
7544     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7545         static int dummy;
7546         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7547         right = TRUE;
7548         return;
7549     }
7550
7551     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7552
7553     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7554
7555     if (clickType == Press) ErrorPopDown();
7556     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7557
7558     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7559         defaultPromoChoice = promoSweep;
7560         promoSweep = EmptySquare;   // terminate sweep
7561         promoDefaultAltered = TRUE;
7562         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7563     }
7564
7565     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7566         if(clickType == Release) return; // ignore upclick of click-click destination
7567         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7568         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7569         if(gameInfo.holdingsWidth &&
7570                 (WhiteOnMove(currentMove)
7571                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7572                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7573             // click in right holdings, for determining promotion piece
7574             ChessSquare p = boards[currentMove][y][x];
7575             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7576             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7577             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7578                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7579                 fromX = fromY = -1;
7580                 return;
7581             }
7582         }
7583         DrawPosition(FALSE, boards[currentMove]);
7584         return;
7585     }
7586
7587     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7588     if(clickType == Press
7589             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7590               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7591               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7592         return;
7593
7594     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7595         // could be static click on premove from-square: abort premove
7596         gotPremove = 0;
7597         ClearPremoveHighlights();
7598     }
7599
7600     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7601         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7602
7603     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7604         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7605                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7606         defaultPromoChoice = DefaultPromoChoice(side);
7607     }
7608
7609     autoQueen = appData.alwaysPromoteToQueen;
7610
7611     if (fromX == -1) {
7612       int originalY = y;
7613       gatingPiece = EmptySquare;
7614       if (clickType != Press) {
7615         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7616             DragPieceEnd(xPix, yPix); dragging = 0;
7617             DrawPosition(FALSE, NULL);
7618         }
7619         return;
7620       }
7621       doubleClick = FALSE;
7622       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7623         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7624       }
7625       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7626       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7627          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7628          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7629             /* First square */
7630             if (OKToStartUserMove(fromX, fromY)) {
7631                 second = 0;
7632                 ReportClick("lift", x, y);
7633                 MarkTargetSquares(0);
7634                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7635                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7636                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7637                     promoSweep = defaultPromoChoice;
7638                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7639                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7640                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7641                 }
7642                 if (appData.highlightDragging) {
7643                     SetHighlights(fromX, fromY, -1, -1);
7644                 } else {
7645                     ClearHighlights();
7646                 }
7647             } else fromX = fromY = -1;
7648             return;
7649         }
7650     }
7651
7652     /* fromX != -1 */
7653     if (clickType == Press && gameMode != EditPosition) {
7654         ChessSquare fromP;
7655         ChessSquare toP;
7656         int frc;
7657
7658         // ignore off-board to clicks
7659         if(y < 0 || x < 0) return;
7660
7661         /* Check if clicking again on the same color piece */
7662         fromP = boards[currentMove][fromY][fromX];
7663         toP = boards[currentMove][y][x];
7664         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7665         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7666             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7667            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7668              WhitePawn <= toP && toP <= WhiteKing &&
7669              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7670              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7671             (BlackPawn <= fromP && fromP <= BlackKing &&
7672              BlackPawn <= toP && toP <= BlackKing &&
7673              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7674              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7675             /* Clicked again on same color piece -- changed his mind */
7676             second = (x == fromX && y == fromY);
7677             killX = killY = kill2X = kill2Y = -1;
7678             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7679                 second = FALSE; // first double-click rather than scond click
7680                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7681             }
7682             promoDefaultAltered = FALSE;
7683            if(!second) MarkTargetSquares(1);
7684            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7685             if (appData.highlightDragging) {
7686                 SetHighlights(x, y, -1, -1);
7687             } else {
7688                 ClearHighlights();
7689             }
7690             if (OKToStartUserMove(x, y)) {
7691                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7692                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7693                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7694                  gatingPiece = boards[currentMove][fromY][fromX];
7695                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7696                 fromX = x;
7697                 fromY = y; dragging = 1;
7698                 if(!second) ReportClick("lift", x, y);
7699                 MarkTargetSquares(0);
7700                 DragPieceBegin(xPix, yPix, FALSE);
7701                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7702                     promoSweep = defaultPromoChoice;
7703                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7704                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7705                 }
7706             }
7707            }
7708            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7709            second = FALSE;
7710         }
7711         // ignore clicks on holdings
7712         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7713     }
7714
7715     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7716         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7717         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7718         return;
7719     }
7720
7721     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7722         DragPieceEnd(xPix, yPix); dragging = 0;
7723         if(clearFlag) {
7724             // a deferred attempt to click-click move an empty square on top of a piece
7725             boards[currentMove][y][x] = EmptySquare;
7726             ClearHighlights();
7727             DrawPosition(FALSE, boards[currentMove]);
7728             fromX = fromY = -1; clearFlag = 0;
7729             return;
7730         }
7731         if (appData.animateDragging) {
7732             /* Undo animation damage if any */
7733             DrawPosition(FALSE, NULL);
7734         }
7735         if (second) {
7736             /* Second up/down in same square; just abort move */
7737             second = 0;
7738             fromX = fromY = -1;
7739             gatingPiece = EmptySquare;
7740             ClearHighlights();
7741             gotPremove = 0;
7742             ClearPremoveHighlights();
7743             MarkTargetSquares(-1);
7744             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7745         } else {
7746             /* First upclick in same square; start click-click mode */
7747             SetHighlights(x, y, -1, -1);
7748         }
7749         return;
7750     }
7751
7752     clearFlag = 0;
7753
7754     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7755        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7756         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7757         DisplayMessage(_("only marked squares are legal"),"");
7758         DrawPosition(TRUE, NULL);
7759         return; // ignore to-click
7760     }
7761
7762     /* we now have a different from- and (possibly off-board) to-square */
7763     /* Completed move */
7764     if(!sweepSelecting) {
7765         toX = x;
7766         toY = y;
7767     }
7768
7769     piece = boards[currentMove][fromY][fromX];
7770
7771     saveAnimate = appData.animate;
7772     if (clickType == Press) {
7773         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7774         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7775             // must be Edit Position mode with empty-square selected
7776             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7777             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7778             return;
7779         }
7780         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7781             return;
7782         }
7783         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7784             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7785         } else
7786         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7787         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7788           if(appData.sweepSelect) {
7789             promoSweep = defaultPromoChoice;
7790             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7791             selectFlag = 0; lastX = xPix; lastY = yPix;
7792             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7793             saveFlash = appData.flashCount; appData.flashCount = 0;
7794             Sweep(0); // Pawn that is going to promote: preview promotion piece
7795             sweepSelecting = 1;
7796             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7797             MarkTargetSquares(1);
7798           }
7799           return; // promo popup appears on up-click
7800         }
7801         /* Finish clickclick move */
7802         if (appData.animate || appData.highlightLastMove) {
7803             SetHighlights(fromX, fromY, toX, toY);
7804         } else {
7805             ClearHighlights();
7806         }
7807         MarkTargetSquares(1);
7808     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7809         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7810         *promoRestrict = 0; appData.flashCount = saveFlash;
7811         if (appData.animate || appData.highlightLastMove) {
7812             SetHighlights(fromX, fromY, toX, toY);
7813         } else {
7814             ClearHighlights();
7815         }
7816         MarkTargetSquares(1);
7817     } else {
7818 #if 0
7819 // [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
7820         /* Finish drag move */
7821         if (appData.highlightLastMove) {
7822             SetHighlights(fromX, fromY, toX, toY);
7823         } else {
7824             ClearHighlights();
7825         }
7826 #endif
7827         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7828           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7829         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7830         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7831           dragging *= 2;            // flag button-less dragging if we are dragging
7832           MarkTargetSquares(1);
7833           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7834           else {
7835             kill2X = killX; kill2Y = killY;
7836             killX = x; killY = y;     // remember this square as intermediate
7837             ReportClick("put", x, y); // and inform engine
7838             ReportClick("lift", x, y);
7839             MarkTargetSquares(0);
7840             return;
7841           }
7842         }
7843         DragPieceEnd(xPix, yPix); dragging = 0;
7844         /* Don't animate move and drag both */
7845         appData.animate = FALSE;
7846         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7847     }
7848
7849     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7850     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7851         ChessSquare piece = boards[currentMove][fromY][fromX];
7852         if(gameMode == EditPosition && piece != EmptySquare &&
7853            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7854             int n;
7855
7856             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7857                 n = PieceToNumber(piece - (int)BlackPawn);
7858                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7859                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7860                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7861             } else
7862             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7863                 n = PieceToNumber(piece);
7864                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7865                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7866                 boards[currentMove][n][BOARD_WIDTH-2]++;
7867             }
7868             boards[currentMove][fromY][fromX] = EmptySquare;
7869         }
7870         ClearHighlights();
7871         fromX = fromY = -1;
7872         MarkTargetSquares(1);
7873         DrawPosition(TRUE, boards[currentMove]);
7874         return;
7875     }
7876
7877     // off-board moves should not be highlighted
7878     if(x < 0 || y < 0) ClearHighlights();
7879     else ReportClick("put", x, y);
7880
7881     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7882
7883     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7884
7885     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7886         SetHighlights(fromX, fromY, toX, toY);
7887         MarkTargetSquares(1);
7888         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7889             // [HGM] super: promotion to captured piece selected from holdings
7890             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7891             promotionChoice = TRUE;
7892             // kludge follows to temporarily execute move on display, without promoting yet
7893             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7894             boards[currentMove][toY][toX] = p;
7895             DrawPosition(FALSE, boards[currentMove]);
7896             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7897             boards[currentMove][toY][toX] = q;
7898             DisplayMessage("Click in holdings to choose piece", "");
7899             return;
7900         }
7901         PromotionPopUp(promoChoice);
7902     } else {
7903         int oldMove = currentMove;
7904         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7905         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7906         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7907         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7908         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7909            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7910             DrawPosition(TRUE, boards[currentMove]);
7911         fromX = fromY = -1;
7912         flashing = 0;
7913     }
7914     appData.animate = saveAnimate;
7915     if (appData.animate || appData.animateDragging) {
7916         /* Undo animation damage if needed */
7917 //      DrawPosition(FALSE, NULL);
7918     }
7919 }
7920
7921 int
7922 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7923 {   // front-end-free part taken out of PieceMenuPopup
7924     int whichMenu; int xSqr, ySqr;
7925
7926     if(seekGraphUp) { // [HGM] seekgraph
7927         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7928         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7929         return -2;
7930     }
7931
7932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7933          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7934         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7935         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7936         if(action == Press)   {
7937             originalFlip = flipView;
7938             flipView = !flipView; // temporarily flip board to see game from partners perspective
7939             DrawPosition(TRUE, partnerBoard);
7940             DisplayMessage(partnerStatus, "");
7941             partnerUp = TRUE;
7942         } else if(action == Release) {
7943             flipView = originalFlip;
7944             DrawPosition(TRUE, boards[currentMove]);
7945             partnerUp = FALSE;
7946         }
7947         return -2;
7948     }
7949
7950     xSqr = EventToSquare(x, BOARD_WIDTH);
7951     ySqr = EventToSquare(y, BOARD_HEIGHT);
7952     if (action == Release) {
7953         if(pieceSweep != EmptySquare) {
7954             EditPositionMenuEvent(pieceSweep, toX, toY);
7955             pieceSweep = EmptySquare;
7956         } else UnLoadPV(); // [HGM] pv
7957     }
7958     if (action != Press) return -2; // return code to be ignored
7959     switch (gameMode) {
7960       case IcsExamining:
7961         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7962       case EditPosition:
7963         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7964         if (xSqr < 0 || ySqr < 0) return -1;
7965         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7966         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7967         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7968         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7969         NextPiece(0);
7970         return 2; // grab
7971       case IcsObserving:
7972         if(!appData.icsEngineAnalyze) return -1;
7973       case IcsPlayingWhite:
7974       case IcsPlayingBlack:
7975         if(!appData.zippyPlay) goto noZip;
7976       case AnalyzeMode:
7977       case AnalyzeFile:
7978       case MachinePlaysWhite:
7979       case MachinePlaysBlack:
7980       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7981         if (!appData.dropMenu) {
7982           LoadPV(x, y);
7983           return 2; // flag front-end to grab mouse events
7984         }
7985         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7986            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7987       case EditGame:
7988       noZip:
7989         if (xSqr < 0 || ySqr < 0) return -1;
7990         if (!appData.dropMenu || appData.testLegality &&
7991             gameInfo.variant != VariantBughouse &&
7992             gameInfo.variant != VariantCrazyhouse) return -1;
7993         whichMenu = 1; // drop menu
7994         break;
7995       default:
7996         return -1;
7997     }
7998
7999     if (((*fromX = xSqr) < 0) ||
8000         ((*fromY = ySqr) < 0)) {
8001         *fromX = *fromY = -1;
8002         return -1;
8003     }
8004     if (flipView)
8005       *fromX = BOARD_WIDTH - 1 - *fromX;
8006     else
8007       *fromY = BOARD_HEIGHT - 1 - *fromY;
8008
8009     return whichMenu;
8010 }
8011
8012 void
8013 Wheel (int dir, int x, int y)
8014 {
8015     if(gameMode == EditPosition) {
8016         int xSqr = EventToSquare(x, BOARD_WIDTH);
8017         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8018         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8019         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8020         do {
8021             boards[currentMove][ySqr][xSqr] += dir;
8022             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8023             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8024         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8025         DrawPosition(FALSE, boards[currentMove]);
8026     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8027 }
8028
8029 void
8030 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8031 {
8032 //    char * hint = lastHint;
8033     FrontEndProgramStats stats;
8034
8035     stats.which = cps == &first ? 0 : 1;
8036     stats.depth = cpstats->depth;
8037     stats.nodes = cpstats->nodes;
8038     stats.score = cpstats->score;
8039     stats.time = cpstats->time;
8040     stats.pv = cpstats->movelist;
8041     stats.hint = lastHint;
8042     stats.an_move_index = 0;
8043     stats.an_move_count = 0;
8044
8045     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8046         stats.hint = cpstats->move_name;
8047         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8048         stats.an_move_count = cpstats->nr_moves;
8049     }
8050
8051     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
8052
8053     SetProgramStats( &stats );
8054 }
8055
8056 void
8057 ClearEngineOutputPane (int which)
8058 {
8059     static FrontEndProgramStats dummyStats;
8060     dummyStats.which = which;
8061     dummyStats.pv = "#";
8062     SetProgramStats( &dummyStats );
8063 }
8064
8065 #define MAXPLAYERS 500
8066
8067 char *
8068 TourneyStandings (int display)
8069 {
8070     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8071     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8072     char result, *p, *names[MAXPLAYERS];
8073
8074     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8075         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8076     names[0] = p = strdup(appData.participants);
8077     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8078
8079     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8080
8081     while(result = appData.results[nr]) {
8082         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8083         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8084         wScore = bScore = 0;
8085         switch(result) {
8086           case '+': wScore = 2; break;
8087           case '-': bScore = 2; break;
8088           case '=': wScore = bScore = 1; break;
8089           case ' ':
8090           case '*': return strdup("busy"); // tourney not finished
8091         }
8092         score[w] += wScore;
8093         score[b] += bScore;
8094         games[w]++;
8095         games[b]++;
8096         nr++;
8097     }
8098     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8099     for(w=0; w<nPlayers; w++) {
8100         bScore = -1;
8101         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8102         ranking[w] = b; points[w] = bScore; score[b] = -2;
8103     }
8104     p = malloc(nPlayers*34+1);
8105     for(w=0; w<nPlayers && w<display; w++)
8106         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8107     free(names[0]);
8108     return p;
8109 }
8110
8111 void
8112 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8113 {       // count all piece types
8114         int p, f, r;
8115         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8116         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8117         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8118                 p = board[r][f];
8119                 pCnt[p]++;
8120                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8121                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8122                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8123                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8124                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8125                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8126         }
8127 }
8128
8129 int
8130 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8131 {
8132         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8133         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8134
8135         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8136         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8137         if(myPawns == 2 && nMine == 3) // KPP
8138             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8139         if(myPawns == 1 && nMine == 2) // KP
8140             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8141         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8142             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8143         if(myPawns) return FALSE;
8144         if(pCnt[WhiteRook+side])
8145             return pCnt[BlackRook-side] ||
8146                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8147                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8148                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8149         if(pCnt[WhiteCannon+side]) {
8150             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8151             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8152         }
8153         if(pCnt[WhiteKnight+side])
8154             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8155         return FALSE;
8156 }
8157
8158 int
8159 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8160 {
8161         VariantClass v = gameInfo.variant;
8162
8163         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8164         if(v == VariantShatranj) return TRUE; // always winnable through baring
8165         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8166         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8167
8168         if(v == VariantXiangqi) {
8169                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8170
8171                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8172                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8173                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8174                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8175                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8176                 if(stale) // we have at least one last-rank P plus perhaps C
8177                     return majors // KPKX
8178                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8179                 else // KCA*E*
8180                     return pCnt[WhiteFerz+side] // KCAK
8181                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8182                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8183                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8184
8185         } else if(v == VariantKnightmate) {
8186                 if(nMine == 1) return FALSE;
8187                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8188         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8189                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8190
8191                 if(nMine == 1) return FALSE; // bare King
8192                 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
8193                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8194                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8195                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8196                 if(pCnt[WhiteKnight+side])
8197                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8198                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8199                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8200                 if(nBishops)
8201                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8202                 if(pCnt[WhiteAlfil+side])
8203                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8204                 if(pCnt[WhiteWazir+side])
8205                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8206         }
8207
8208         return TRUE;
8209 }
8210
8211 int
8212 CompareWithRights (Board b1, Board b2)
8213 {
8214     int rights = 0;
8215     if(!CompareBoards(b1, b2)) return FALSE;
8216     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8217     /* compare castling rights */
8218     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8219            rights++; /* King lost rights, while rook still had them */
8220     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8221         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8222            rights++; /* but at least one rook lost them */
8223     }
8224     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8225            rights++;
8226     if( b1[CASTLING][5] != NoRights ) {
8227         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8228            rights++;
8229     }
8230     return rights == 0;
8231 }
8232
8233 int
8234 Adjudicate (ChessProgramState *cps)
8235 {       // [HGM] some adjudications useful with buggy engines
8236         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8237         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8238         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8239         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8240         int k, drop, count = 0; static int bare = 1;
8241         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8242         Boolean canAdjudicate = !appData.icsActive;
8243
8244         // most tests only when we understand the game, i.e. legality-checking on
8245             if( appData.testLegality )
8246             {   /* [HGM] Some more adjudications for obstinate engines */
8247                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8248                 static int moveCount = 6;
8249                 ChessMove result;
8250                 char *reason = NULL;
8251
8252                 /* Count what is on board. */
8253                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8254
8255                 /* Some material-based adjudications that have to be made before stalemate test */
8256                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8257                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8258                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8259                      if(canAdjudicate && appData.checkMates) {
8260                          if(engineOpponent)
8261                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8262                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8263                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8264                          return 1;
8265                      }
8266                 }
8267
8268                 /* Bare King in Shatranj (loses) or Losers (wins) */
8269                 if( nrW == 1 || nrB == 1) {
8270                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8271                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8272                      if(canAdjudicate && appData.checkMates) {
8273                          if(engineOpponent)
8274                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8275                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8276                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8277                          return 1;
8278                      }
8279                   } else
8280                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8281                   {    /* bare King */
8282                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8283                         if(canAdjudicate && appData.checkMates) {
8284                             /* but only adjudicate if adjudication enabled */
8285                             if(engineOpponent)
8286                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8287                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8288                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8289                             return 1;
8290                         }
8291                   }
8292                 } else bare = 1;
8293
8294
8295             // don't wait for engine to announce game end if we can judge ourselves
8296             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8297               case MT_CHECK:
8298                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8299                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8300                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8301                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8302                             checkCnt++;
8303                         if(checkCnt >= 2) {
8304                             reason = "Xboard adjudication: 3rd check";
8305                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8306                             break;
8307                         }
8308                     }
8309                 }
8310               case MT_NONE:
8311               default:
8312                 break;
8313               case MT_STEALMATE:
8314               case MT_STALEMATE:
8315               case MT_STAINMATE:
8316                 reason = "Xboard adjudication: Stalemate";
8317                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8318                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8319                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8320                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8321                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8322                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8323                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8324                                                                         EP_CHECKMATE : EP_WINS);
8325                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8326                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8327                 }
8328                 break;
8329               case MT_CHECKMATE:
8330                 reason = "Xboard adjudication: Checkmate";
8331                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8332                 if(gameInfo.variant == VariantShogi) {
8333                     if(forwardMostMove > backwardMostMove
8334                        && moveList[forwardMostMove-1][1] == '@'
8335                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8336                         reason = "XBoard adjudication: pawn-drop mate";
8337                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8338                     }
8339                 }
8340                 break;
8341             }
8342
8343                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8344                     case EP_STALEMATE:
8345                         result = GameIsDrawn; break;
8346                     case EP_CHECKMATE:
8347                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8348                     case EP_WINS:
8349                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8350                     default:
8351                         result = EndOfFile;
8352                 }
8353                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8354                     if(engineOpponent)
8355                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8356                     GameEnds( result, reason, GE_XBOARD );
8357                     return 1;
8358                 }
8359
8360                 /* Next absolutely insufficient mating material. */
8361                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8362                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8363                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8364
8365                      /* always flag draws, for judging claims */
8366                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8367
8368                      if(canAdjudicate && appData.materialDraws) {
8369                          /* but only adjudicate them if adjudication enabled */
8370                          if(engineOpponent) {
8371                            SendToProgram("force\n", engineOpponent); // suppress reply
8372                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8373                          }
8374                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8375                          return 1;
8376                      }
8377                 }
8378
8379                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8380                 if(gameInfo.variant == VariantXiangqi ?
8381                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8382                  : nrW + nrB == 4 &&
8383                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8384                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8385                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8386                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8387                    ) ) {
8388                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8389                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8390                           if(engineOpponent) {
8391                             SendToProgram("force\n", engineOpponent); // suppress reply
8392                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8393                           }
8394                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8395                           return 1;
8396                      }
8397                 } else moveCount = 6;
8398             }
8399
8400         // Repetition draws and 50-move rule can be applied independently of legality testing
8401
8402                 /* Check for rep-draws */
8403                 count = 0;
8404                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8405                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8406                 for(k = forwardMostMove-2;
8407                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8408                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8409                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8410                     k-=2)
8411                 {   int rights=0;
8412                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8413                         /* compare castling rights */
8414                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8415                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8416                                 rights++; /* King lost rights, while rook still had them */
8417                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8418                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8419                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8420                                    rights++; /* but at least one rook lost them */
8421                         }
8422                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8423                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8424                                 rights++;
8425                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8426                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8427                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8428                                    rights++;
8429                         }
8430                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8431                             && appData.drawRepeats > 1) {
8432                              /* adjudicate after user-specified nr of repeats */
8433                              int result = GameIsDrawn;
8434                              char *details = "XBoard adjudication: repetition draw";
8435                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8436                                 // [HGM] xiangqi: check for forbidden perpetuals
8437                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8438                                 for(m=forwardMostMove; m>k; m-=2) {
8439                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8440                                         ourPerpetual = 0; // the current mover did not always check
8441                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8442                                         hisPerpetual = 0; // the opponent did not always check
8443                                 }
8444                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8445                                                                         ourPerpetual, hisPerpetual);
8446                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8447                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8448                                     details = "Xboard adjudication: perpetual checking";
8449                                 } else
8450                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8451                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8452                                 } else
8453                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8454                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8455                                         result = BlackWins;
8456                                         details = "Xboard adjudication: repetition";
8457                                     }
8458                                 } else // it must be XQ
8459                                 // Now check for perpetual chases
8460                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8461                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8462                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8463                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8464                                         static char resdet[MSG_SIZ];
8465                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8466                                         details = resdet;
8467                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8468                                     } else
8469                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8470                                         break; // Abort repetition-checking loop.
8471                                 }
8472                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8473                              }
8474                              if(engineOpponent) {
8475                                SendToProgram("force\n", engineOpponent); // suppress reply
8476                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8477                              }
8478                              GameEnds( result, details, GE_XBOARD );
8479                              return 1;
8480                         }
8481                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8482                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8483                     }
8484                 }
8485
8486                 /* Now we test for 50-move draws. Determine ply count */
8487                 count = forwardMostMove;
8488                 /* look for last irreversble move */
8489                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8490                     count--;
8491                 /* if we hit starting position, add initial plies */
8492                 if( count == backwardMostMove )
8493                     count -= initialRulePlies;
8494                 count = forwardMostMove - count;
8495                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8496                         // adjust reversible move counter for checks in Xiangqi
8497                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8498                         if(i < backwardMostMove) i = backwardMostMove;
8499                         while(i <= forwardMostMove) {
8500                                 lastCheck = inCheck; // check evasion does not count
8501                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8502                                 if(inCheck || lastCheck) count--; // check does not count
8503                                 i++;
8504                         }
8505                 }
8506                 if( count >= 100)
8507                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8508                          /* this is used to judge if draw claims are legal */
8509                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8510                          if(engineOpponent) {
8511                            SendToProgram("force\n", engineOpponent); // suppress reply
8512                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8513                          }
8514                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8515                          return 1;
8516                 }
8517
8518                 /* if draw offer is pending, treat it as a draw claim
8519                  * when draw condition present, to allow engines a way to
8520                  * claim draws before making their move to avoid a race
8521                  * condition occurring after their move
8522                  */
8523                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8524                          char *p = NULL;
8525                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8526                              p = "Draw claim: 50-move rule";
8527                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8528                              p = "Draw claim: 3-fold repetition";
8529                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8530                              p = "Draw claim: insufficient mating material";
8531                          if( p != NULL && canAdjudicate) {
8532                              if(engineOpponent) {
8533                                SendToProgram("force\n", engineOpponent); // suppress reply
8534                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8535                              }
8536                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8537                              return 1;
8538                          }
8539                 }
8540
8541                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8542                     if(engineOpponent) {
8543                       SendToProgram("force\n", engineOpponent); // suppress reply
8544                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8545                     }
8546                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8547                     return 1;
8548                 }
8549         return 0;
8550 }
8551
8552 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8553 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8554 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8555
8556 static int
8557 BitbaseProbe ()
8558 {
8559     int pieces[10], squares[10], cnt=0, r, f, res;
8560     static int loaded;
8561     static PPROBE_EGBB probeBB;
8562     if(!appData.testLegality) return 10;
8563     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8564     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8565     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8566     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8567         ChessSquare piece = boards[forwardMostMove][r][f];
8568         int black = (piece >= BlackPawn);
8569         int type = piece - black*BlackPawn;
8570         if(piece == EmptySquare) continue;
8571         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8572         if(type == WhiteKing) type = WhiteQueen + 1;
8573         type = egbbCode[type];
8574         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8575         pieces[cnt] = type + black*6;
8576         if(++cnt > 5) return 11;
8577     }
8578     pieces[cnt] = squares[cnt] = 0;
8579     // probe EGBB
8580     if(loaded == 2) return 13; // loading failed before
8581     if(loaded == 0) {
8582         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8583         HMODULE lib;
8584         PLOAD_EGBB loadBB;
8585         loaded = 2; // prepare for failure
8586         if(!path) return 13; // no egbb installed
8587         strncpy(buf, path + 8, MSG_SIZ);
8588         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8589         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8590         lib = LoadLibrary(buf);
8591         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8592         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8593         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8594         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8595         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8596         loaded = 1; // success!
8597     }
8598     res = probeBB(forwardMostMove & 1, pieces, squares);
8599     return res > 0 ? 1 : res < 0 ? -1 : 0;
8600 }
8601
8602 char *
8603 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8604 {   // [HGM] book: this routine intercepts moves to simulate book replies
8605     char *bookHit = NULL;
8606
8607     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8608         char buf[MSG_SIZ];
8609         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8610         SendToProgram(buf, cps);
8611     }
8612     //first determine if the incoming move brings opponent into his book
8613     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8614         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8615     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8616     if(bookHit != NULL && !cps->bookSuspend) {
8617         // make sure opponent is not going to reply after receiving move to book position
8618         SendToProgram("force\n", cps);
8619         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8620     }
8621     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8622     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8623     // now arrange restart after book miss
8624     if(bookHit) {
8625         // after a book hit we never send 'go', and the code after the call to this routine
8626         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8627         char buf[MSG_SIZ], *move = bookHit;
8628         if(cps->useSAN) {
8629             int fromX, fromY, toX, toY;
8630             char promoChar;
8631             ChessMove moveType;
8632             move = buf + 30;
8633             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8634                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8635                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8636                                     PosFlags(forwardMostMove),
8637                                     fromY, fromX, toY, toX, promoChar, move);
8638             } else {
8639                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8640                 bookHit = NULL;
8641             }
8642         }
8643         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8644         SendToProgram(buf, cps);
8645         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8646     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8647         SendToProgram("go\n", cps);
8648         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8649     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8650         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8651             SendToProgram("go\n", cps);
8652         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8653     }
8654     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8655 }
8656
8657 int
8658 LoadError (char *errmess, ChessProgramState *cps)
8659 {   // unloads engine and switches back to -ncp mode if it was first
8660     if(cps->initDone) return FALSE;
8661     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8662     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8663     cps->pr = NoProc;
8664     if(cps == &first) {
8665         appData.noChessProgram = TRUE;
8666         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8667         gameMode = BeginningOfGame; ModeHighlight();
8668         SetNCPMode();
8669     }
8670     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8671     DisplayMessage("", ""); // erase waiting message
8672     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8673     return TRUE;
8674 }
8675
8676 char *savedMessage;
8677 ChessProgramState *savedState;
8678 void
8679 DeferredBookMove (void)
8680 {
8681         if(savedState->lastPing != savedState->lastPong)
8682                     ScheduleDelayedEvent(DeferredBookMove, 10);
8683         else
8684         HandleMachineMove(savedMessage, savedState);
8685 }
8686
8687 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8688 static ChessProgramState *stalledEngine;
8689 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8690
8691 void
8692 HandleMachineMove (char *message, ChessProgramState *cps)
8693 {
8694     static char firstLeg[20], legs;
8695     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8696     char realname[MSG_SIZ];
8697     int fromX, fromY, toX, toY;
8698     ChessMove moveType;
8699     char promoChar, roar;
8700     char *p, *pv=buf1;
8701     int oldError;
8702     char *bookHit;
8703
8704     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8705         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8706         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8707             DisplayError(_("Invalid pairing from pairing engine"), 0);
8708             return;
8709         }
8710         pairingReceived = 1;
8711         NextMatchGame();
8712         return; // Skim the pairing messages here.
8713     }
8714
8715     oldError = cps->userError; cps->userError = 0;
8716
8717 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8718     /*
8719      * Kludge to ignore BEL characters
8720      */
8721     while (*message == '\007') message++;
8722
8723     /*
8724      * [HGM] engine debug message: ignore lines starting with '#' character
8725      */
8726     if(cps->debug && *message == '#') return;
8727
8728     /*
8729      * Look for book output
8730      */
8731     if (cps == &first && bookRequested) {
8732         if (message[0] == '\t' || message[0] == ' ') {
8733             /* Part of the book output is here; append it */
8734             strcat(bookOutput, message);
8735             strcat(bookOutput, "  \n");
8736             return;
8737         } else if (bookOutput[0] != NULLCHAR) {
8738             /* All of book output has arrived; display it */
8739             char *p = bookOutput;
8740             while (*p != NULLCHAR) {
8741                 if (*p == '\t') *p = ' ';
8742                 p++;
8743             }
8744             DisplayInformation(bookOutput);
8745             bookRequested = FALSE;
8746             /* Fall through to parse the current output */
8747         }
8748     }
8749
8750     /*
8751      * Look for machine move.
8752      */
8753     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8754         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8755     {
8756         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8757             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8758             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8759             stalledEngine = cps;
8760             if(appData.ponderNextMove) { // bring opponent out of ponder
8761                 if(gameMode == TwoMachinesPlay) {
8762                     if(cps->other->pause)
8763                         PauseEngine(cps->other);
8764                     else
8765                         SendToProgram("easy\n", cps->other);
8766                 }
8767             }
8768             StopClocks();
8769             return;
8770         }
8771
8772       if(cps->usePing) {
8773
8774         /* This method is only useful on engines that support ping */
8775         if(abortEngineThink) {
8776             if (appData.debugMode) {
8777                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8778             }
8779             SendToProgram("undo\n", cps);
8780             return;
8781         }
8782
8783         if (cps->lastPing != cps->lastPong) {
8784             /* Extra move from before last new; ignore */
8785             if (appData.debugMode) {
8786                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8787             }
8788           return;
8789         }
8790
8791       } else {
8792
8793         int machineWhite = FALSE;
8794
8795         switch (gameMode) {
8796           case BeginningOfGame:
8797             /* Extra move from before last reset; ignore */
8798             if (appData.debugMode) {
8799                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8800             }
8801             return;
8802
8803           case EndOfGame:
8804           case IcsIdle:
8805           default:
8806             /* Extra move after we tried to stop.  The mode test is
8807                not a reliable way of detecting this problem, but it's
8808                the best we can do on engines that don't support ping.
8809             */
8810             if (appData.debugMode) {
8811                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8812                         cps->which, gameMode);
8813             }
8814             SendToProgram("undo\n", cps);
8815             return;
8816
8817           case MachinePlaysWhite:
8818           case IcsPlayingWhite:
8819             machineWhite = TRUE;
8820             break;
8821
8822           case MachinePlaysBlack:
8823           case IcsPlayingBlack:
8824             machineWhite = FALSE;
8825             break;
8826
8827           case TwoMachinesPlay:
8828             machineWhite = (cps->twoMachinesColor[0] == 'w');
8829             break;
8830         }
8831         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8832             if (appData.debugMode) {
8833                 fprintf(debugFP,
8834                         "Ignoring move out of turn by %s, gameMode %d"
8835                         ", forwardMost %d\n",
8836                         cps->which, gameMode, forwardMostMove);
8837             }
8838             return;
8839         }
8840       }
8841
8842         if(cps->alphaRank) AlphaRank(machineMove, 4);
8843
8844         // [HGM] lion: (some very limited) support for Alien protocol
8845         killX = killY = kill2X = kill2Y = -1;
8846         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8847             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8848             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8849             return;
8850         }
8851         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8852             char *q = strchr(p+1, ',');            // second comma?
8853             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8854             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8855             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8856         }
8857         if(firstLeg[0]) { // there was a previous leg;
8858             // only support case where same piece makes two step
8859             char buf[20], *p = machineMove+1, *q = buf+1, f;
8860             safeStrCpy(buf, machineMove, 20);
8861             while(isdigit(*q)) q++; // find start of to-square
8862             safeStrCpy(machineMove, firstLeg, 20);
8863             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8864             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8865             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8866             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8867             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8868             firstLeg[0] = NULLCHAR; legs = 0;
8869         }
8870
8871         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8872                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8873             /* Machine move could not be parsed; ignore it. */
8874           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8875                     machineMove, _(cps->which));
8876             DisplayMoveError(buf1);
8877             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8878                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8879             if (gameMode == TwoMachinesPlay) {
8880               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8881                        buf1, GE_XBOARD);
8882             }
8883             return;
8884         }
8885
8886         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8887         /* So we have to redo legality test with true e.p. status here,  */
8888         /* to make sure an illegal e.p. capture does not slip through,   */
8889         /* to cause a forfeit on a justified illegal-move complaint      */
8890         /* of the opponent.                                              */
8891         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8892            ChessMove moveType;
8893            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8894                              fromY, fromX, toY, toX, promoChar);
8895             if(moveType == IllegalMove) {
8896               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8897                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8898                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8899                            buf1, GE_XBOARD);
8900                 return;
8901            } else if(!appData.fischerCastling)
8902            /* [HGM] Kludge to handle engines that send FRC-style castling
8903               when they shouldn't (like TSCP-Gothic) */
8904            switch(moveType) {
8905              case WhiteASideCastleFR:
8906              case BlackASideCastleFR:
8907                toX+=2;
8908                currentMoveString[2]++;
8909                break;
8910              case WhiteHSideCastleFR:
8911              case BlackHSideCastleFR:
8912                toX--;
8913                currentMoveString[2]--;
8914                break;
8915              default: ; // nothing to do, but suppresses warning of pedantic compilers
8916            }
8917         }
8918         hintRequested = FALSE;
8919         lastHint[0] = NULLCHAR;
8920         bookRequested = FALSE;
8921         /* Program may be pondering now */
8922         cps->maybeThinking = TRUE;
8923         if (cps->sendTime == 2) cps->sendTime = 1;
8924         if (cps->offeredDraw) cps->offeredDraw--;
8925
8926         /* [AS] Save move info*/
8927         pvInfoList[ forwardMostMove ].score = programStats.score;
8928         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8929         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8930
8931         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8932
8933         /* Test suites abort the 'game' after one move */
8934         if(*appData.finger) {
8935            static FILE *f;
8936            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8937            if(!f) f = fopen(appData.finger, "w");
8938            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8939            else { DisplayFatalError("Bad output file", errno, 0); return; }
8940            free(fen);
8941            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8942         }
8943         if(appData.epd) {
8944            if(solvingTime >= 0) {
8945               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8946               totalTime += solvingTime; first.matchWins++;
8947            } else {
8948               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8949               second.matchWins++;
8950            }
8951            OutputKibitz(2, buf1);
8952            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8953         }
8954
8955         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8956         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8957             int count = 0;
8958
8959             while( count < adjudicateLossPlies ) {
8960                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8961
8962                 if( count & 1 ) {
8963                     score = -score; /* Flip score for winning side */
8964                 }
8965
8966                 if( score > appData.adjudicateLossThreshold ) {
8967                     break;
8968                 }
8969
8970                 count++;
8971             }
8972
8973             if( count >= adjudicateLossPlies ) {
8974                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8975
8976                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8977                     "Xboard adjudication",
8978                     GE_XBOARD );
8979
8980                 return;
8981             }
8982         }
8983
8984         if(Adjudicate(cps)) {
8985             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8986             return; // [HGM] adjudicate: for all automatic game ends
8987         }
8988
8989 #if ZIPPY
8990         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8991             first.initDone) {
8992           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8993                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8994                 SendToICS("draw ");
8995                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8996           }
8997           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8998           ics_user_moved = 1;
8999           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9000                 char buf[3*MSG_SIZ];
9001
9002                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9003                         programStats.score / 100.,
9004                         programStats.depth,
9005                         programStats.time / 100.,
9006                         (unsigned int)programStats.nodes,
9007                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9008                         programStats.movelist);
9009                 SendToICS(buf);
9010           }
9011         }
9012 #endif
9013
9014         /* [AS] Clear stats for next move */
9015         ClearProgramStats();
9016         thinkOutput[0] = NULLCHAR;
9017         hiddenThinkOutputState = 0;
9018
9019         bookHit = NULL;
9020         if (gameMode == TwoMachinesPlay) {
9021             /* [HGM] relaying draw offers moved to after reception of move */
9022             /* and interpreting offer as claim if it brings draw condition */
9023             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9024                 SendToProgram("draw\n", cps->other);
9025             }
9026             if (cps->other->sendTime) {
9027                 SendTimeRemaining(cps->other,
9028                                   cps->other->twoMachinesColor[0] == 'w');
9029             }
9030             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9031             if (firstMove && !bookHit) {
9032                 firstMove = FALSE;
9033                 if (cps->other->useColors) {
9034                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9035                 }
9036                 SendToProgram("go\n", cps->other);
9037             }
9038             cps->other->maybeThinking = TRUE;
9039         }
9040
9041         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9042
9043         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9044
9045         if (!pausing && appData.ringBellAfterMoves) {
9046             if(!roar) RingBell();
9047         }
9048
9049         /*
9050          * Reenable menu items that were disabled while
9051          * machine was thinking
9052          */
9053         if (gameMode != TwoMachinesPlay)
9054             SetUserThinkingEnables();
9055
9056         // [HGM] book: after book hit opponent has received move and is now in force mode
9057         // force the book reply into it, and then fake that it outputted this move by jumping
9058         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9059         if(bookHit) {
9060                 static char bookMove[MSG_SIZ]; // a bit generous?
9061
9062                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9063                 strcat(bookMove, bookHit);
9064                 message = bookMove;
9065                 cps = cps->other;
9066                 programStats.nodes = programStats.depth = programStats.time =
9067                 programStats.score = programStats.got_only_move = 0;
9068                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9069
9070                 if(cps->lastPing != cps->lastPong) {
9071                     savedMessage = message; // args for deferred call
9072                     savedState = cps;
9073                     ScheduleDelayedEvent(DeferredBookMove, 10);
9074                     return;
9075                 }
9076                 goto FakeBookMove;
9077         }
9078
9079         return;
9080     }
9081
9082     /* Set special modes for chess engines.  Later something general
9083      *  could be added here; for now there is just one kludge feature,
9084      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9085      *  when "xboard" is given as an interactive command.
9086      */
9087     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9088         cps->useSigint = FALSE;
9089         cps->useSigterm = FALSE;
9090     }
9091     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9092       ParseFeatures(message+8, cps);
9093       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9094     }
9095
9096     if (!strncmp(message, "setup ", 6) && 
9097         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9098           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9099                                         ) { // [HGM] allow first engine to define opening position
9100       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9101       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9102       *buf = NULLCHAR;
9103       if(sscanf(message, "setup (%s", buf) == 1) {
9104         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9105         ASSIGN(appData.pieceToCharTable, buf);
9106       }
9107       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9108       if(dummy >= 3) {
9109         while(message[s] && message[s++] != ' ');
9110         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9111            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9112             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9113             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9114           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9115           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9116           startedFromSetupPosition = FALSE;
9117         }
9118       }
9119       if(startedFromSetupPosition) return;
9120       ParseFEN(boards[0], &dummy, message+s, FALSE);
9121       DrawPosition(TRUE, boards[0]);
9122       CopyBoard(initialPosition, boards[0]);
9123       startedFromSetupPosition = TRUE;
9124       return;
9125     }
9126     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9127       ChessSquare piece = WhitePawn;
9128       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9129       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9130       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9131       piece += CharToPiece(ID & 255) - WhitePawn;
9132       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9133       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9134       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9135       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9136       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9137       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9138                                                && gameInfo.variant != VariantGreat
9139                                                && gameInfo.variant != VariantFairy    ) return;
9140       if(piece < EmptySquare) {
9141         pieceDefs = TRUE;
9142         ASSIGN(pieceDesc[piece], buf1);
9143         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9144       }
9145       return;
9146     }
9147     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9148       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9149       Sweep(0);
9150       return;
9151     }
9152     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9153      * want this, I was asked to put it in, and obliged.
9154      */
9155     if (!strncmp(message, "setboard ", 9)) {
9156         Board initial_position;
9157
9158         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9159
9160         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9161             DisplayError(_("Bad FEN received from engine"), 0);
9162             return ;
9163         } else {
9164            Reset(TRUE, FALSE);
9165            CopyBoard(boards[0], initial_position);
9166            initialRulePlies = FENrulePlies;
9167            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9168            else gameMode = MachinePlaysBlack;
9169            DrawPosition(FALSE, boards[currentMove]);
9170         }
9171         return;
9172     }
9173
9174     /*
9175      * Look for communication commands
9176      */
9177     if (!strncmp(message, "telluser ", 9)) {
9178         if(message[9] == '\\' && message[10] == '\\')
9179             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9180         PlayTellSound();
9181         DisplayNote(message + 9);
9182         return;
9183     }
9184     if (!strncmp(message, "tellusererror ", 14)) {
9185         cps->userError = 1;
9186         if(message[14] == '\\' && message[15] == '\\')
9187             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9188         PlayTellSound();
9189         DisplayError(message + 14, 0);
9190         return;
9191     }
9192     if (!strncmp(message, "tellopponent ", 13)) {
9193       if (appData.icsActive) {
9194         if (loggedOn) {
9195           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9196           SendToICS(buf1);
9197         }
9198       } else {
9199         DisplayNote(message + 13);
9200       }
9201       return;
9202     }
9203     if (!strncmp(message, "tellothers ", 11)) {
9204       if (appData.icsActive) {
9205         if (loggedOn) {
9206           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9207           SendToICS(buf1);
9208         }
9209       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9210       return;
9211     }
9212     if (!strncmp(message, "tellall ", 8)) {
9213       if (appData.icsActive) {
9214         if (loggedOn) {
9215           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9216           SendToICS(buf1);
9217         }
9218       } else {
9219         DisplayNote(message + 8);
9220       }
9221       return;
9222     }
9223     if (strncmp(message, "warning", 7) == 0) {
9224         /* Undocumented feature, use tellusererror in new code */
9225         DisplayError(message, 0);
9226         return;
9227     }
9228     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9229         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9230         strcat(realname, " query");
9231         AskQuestion(realname, buf2, buf1, cps->pr);
9232         return;
9233     }
9234     /* Commands from the engine directly to ICS.  We don't allow these to be
9235      *  sent until we are logged on. Crafty kibitzes have been known to
9236      *  interfere with the login process.
9237      */
9238     if (loggedOn) {
9239         if (!strncmp(message, "tellics ", 8)) {
9240             SendToICS(message + 8);
9241             SendToICS("\n");
9242             return;
9243         }
9244         if (!strncmp(message, "tellicsnoalias ", 15)) {
9245             SendToICS(ics_prefix);
9246             SendToICS(message + 15);
9247             SendToICS("\n");
9248             return;
9249         }
9250         /* The following are for backward compatibility only */
9251         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9252             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9253             SendToICS(ics_prefix);
9254             SendToICS(message);
9255             SendToICS("\n");
9256             return;
9257         }
9258     }
9259     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9260         if(initPing == cps->lastPong) {
9261             if(gameInfo.variant == VariantUnknown) {
9262                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9263                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9264                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9265             }
9266             initPing = -1;
9267         }
9268         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9269             abortEngineThink = FALSE;
9270             DisplayMessage("", "");
9271             ThawUI();
9272         }
9273         return;
9274     }
9275     if(!strncmp(message, "highlight ", 10)) {
9276         if(appData.testLegality && !*engineVariant && appData.markers) return;
9277         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9278         return;
9279     }
9280     if(!strncmp(message, "click ", 6)) {
9281         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9282         if(appData.testLegality || !appData.oneClick) return;
9283         sscanf(message+6, "%c%d%c", &f, &y, &c);
9284         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9285         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9286         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9287         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9288         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9289         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9290             LeftClick(Release, lastLeftX, lastLeftY);
9291         controlKey  = (c == ',');
9292         LeftClick(Press, x, y);
9293         LeftClick(Release, x, y);
9294         first.highlight = f;
9295         return;
9296     }
9297     /*
9298      * If the move is illegal, cancel it and redraw the board.
9299      * Also deal with other error cases.  Matching is rather loose
9300      * here to accommodate engines written before the spec.
9301      */
9302     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9303         strncmp(message, "Error", 5) == 0) {
9304         if (StrStr(message, "name") ||
9305             StrStr(message, "rating") || StrStr(message, "?") ||
9306             StrStr(message, "result") || StrStr(message, "board") ||
9307             StrStr(message, "bk") || StrStr(message, "computer") ||
9308             StrStr(message, "variant") || StrStr(message, "hint") ||
9309             StrStr(message, "random") || StrStr(message, "depth") ||
9310             StrStr(message, "accepted")) {
9311             return;
9312         }
9313         if (StrStr(message, "protover")) {
9314           /* Program is responding to input, so it's apparently done
9315              initializing, and this error message indicates it is
9316              protocol version 1.  So we don't need to wait any longer
9317              for it to initialize and send feature commands. */
9318           FeatureDone(cps, 1);
9319           cps->protocolVersion = 1;
9320           return;
9321         }
9322         cps->maybeThinking = FALSE;
9323
9324         if (StrStr(message, "draw")) {
9325             /* Program doesn't have "draw" command */
9326             cps->sendDrawOffers = 0;
9327             return;
9328         }
9329         if (cps->sendTime != 1 &&
9330             (StrStr(message, "time") || StrStr(message, "otim"))) {
9331           /* Program apparently doesn't have "time" or "otim" command */
9332           cps->sendTime = 0;
9333           return;
9334         }
9335         if (StrStr(message, "analyze")) {
9336             cps->analysisSupport = FALSE;
9337             cps->analyzing = FALSE;
9338 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9339             EditGameEvent(); // [HGM] try to preserve loaded game
9340             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9341             DisplayError(buf2, 0);
9342             return;
9343         }
9344         if (StrStr(message, "(no matching move)st")) {
9345           /* Special kludge for GNU Chess 4 only */
9346           cps->stKludge = TRUE;
9347           SendTimeControl(cps, movesPerSession, timeControl,
9348                           timeIncrement, appData.searchDepth,
9349                           searchTime);
9350           return;
9351         }
9352         if (StrStr(message, "(no matching move)sd")) {
9353           /* Special kludge for GNU Chess 4 only */
9354           cps->sdKludge = TRUE;
9355           SendTimeControl(cps, movesPerSession, timeControl,
9356                           timeIncrement, appData.searchDepth,
9357                           searchTime);
9358           return;
9359         }
9360         if (!StrStr(message, "llegal")) {
9361             return;
9362         }
9363         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9364             gameMode == IcsIdle) return;
9365         if (forwardMostMove <= backwardMostMove) return;
9366         if (pausing) PauseEvent();
9367       if(appData.forceIllegal) {
9368             // [HGM] illegal: machine refused move; force position after move into it
9369           SendToProgram("force\n", cps);
9370           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9371                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9372                 // when black is to move, while there might be nothing on a2 or black
9373                 // might already have the move. So send the board as if white has the move.
9374                 // But first we must change the stm of the engine, as it refused the last move
9375                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9376                 if(WhiteOnMove(forwardMostMove)) {
9377                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9378                     SendBoard(cps, forwardMostMove); // kludgeless board
9379                 } else {
9380                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9381                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9382                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9383                 }
9384           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9385             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9386                  gameMode == TwoMachinesPlay)
9387               SendToProgram("go\n", cps);
9388             return;
9389       } else
9390         if (gameMode == PlayFromGameFile) {
9391             /* Stop reading this game file */
9392             gameMode = EditGame;
9393             ModeHighlight();
9394         }
9395         /* [HGM] illegal-move claim should forfeit game when Xboard */
9396         /* only passes fully legal moves                            */
9397         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9398             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9399                                 "False illegal-move claim", GE_XBOARD );
9400             return; // do not take back move we tested as valid
9401         }
9402         currentMove = forwardMostMove-1;
9403         DisplayMove(currentMove-1); /* before DisplayMoveError */
9404         SwitchClocks(forwardMostMove-1); // [HGM] race
9405         DisplayBothClocks();
9406         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9407                 parseList[currentMove], _(cps->which));
9408         DisplayMoveError(buf1);
9409         DrawPosition(FALSE, boards[currentMove]);
9410
9411         SetUserThinkingEnables();
9412         return;
9413     }
9414     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9415         /* Program has a broken "time" command that
9416            outputs a string not ending in newline.
9417            Don't use it. */
9418         cps->sendTime = 0;
9419     }
9420     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9421         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9422             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9423     }
9424
9425     /*
9426      * If chess program startup fails, exit with an error message.
9427      * Attempts to recover here are futile. [HGM] Well, we try anyway
9428      */
9429     if ((StrStr(message, "unknown host") != NULL)
9430         || (StrStr(message, "No remote directory") != NULL)
9431         || (StrStr(message, "not found") != NULL)
9432         || (StrStr(message, "No such file") != NULL)
9433         || (StrStr(message, "can't alloc") != NULL)
9434         || (StrStr(message, "Permission denied") != NULL)) {
9435
9436         cps->maybeThinking = FALSE;
9437         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9438                 _(cps->which), cps->program, cps->host, message);
9439         RemoveInputSource(cps->isr);
9440         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9441             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9442             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9443         }
9444         return;
9445     }
9446
9447     /*
9448      * Look for hint output
9449      */
9450     if (sscanf(message, "Hint: %s", buf1) == 1) {
9451         if (cps == &first && hintRequested) {
9452             hintRequested = FALSE;
9453             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9454                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9455                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9456                                     PosFlags(forwardMostMove),
9457                                     fromY, fromX, toY, toX, promoChar, buf1);
9458                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9459                 DisplayInformation(buf2);
9460             } else {
9461                 /* Hint move could not be parsed!? */
9462               snprintf(buf2, sizeof(buf2),
9463                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9464                         buf1, _(cps->which));
9465                 DisplayError(buf2, 0);
9466             }
9467         } else {
9468           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9469         }
9470         return;
9471     }
9472
9473     /*
9474      * Ignore other messages if game is not in progress
9475      */
9476     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9477         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9478
9479     /*
9480      * look for win, lose, draw, or draw offer
9481      */
9482     if (strncmp(message, "1-0", 3) == 0) {
9483         char *p, *q, *r = "";
9484         p = strchr(message, '{');
9485         if (p) {
9486             q = strchr(p, '}');
9487             if (q) {
9488                 *q = NULLCHAR;
9489                 r = p + 1;
9490             }
9491         }
9492         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9493         return;
9494     } else if (strncmp(message, "0-1", 3) == 0) {
9495         char *p, *q, *r = "";
9496         p = strchr(message, '{');
9497         if (p) {
9498             q = strchr(p, '}');
9499             if (q) {
9500                 *q = NULLCHAR;
9501                 r = p + 1;
9502             }
9503         }
9504         /* Kludge for Arasan 4.1 bug */
9505         if (strcmp(r, "Black resigns") == 0) {
9506             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9507             return;
9508         }
9509         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9510         return;
9511     } else if (strncmp(message, "1/2", 3) == 0) {
9512         char *p, *q, *r = "";
9513         p = strchr(message, '{');
9514         if (p) {
9515             q = strchr(p, '}');
9516             if (q) {
9517                 *q = NULLCHAR;
9518                 r = p + 1;
9519             }
9520         }
9521
9522         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9523         return;
9524
9525     } else if (strncmp(message, "White resign", 12) == 0) {
9526         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9527         return;
9528     } else if (strncmp(message, "Black resign", 12) == 0) {
9529         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9530         return;
9531     } else if (strncmp(message, "White matches", 13) == 0 ||
9532                strncmp(message, "Black matches", 13) == 0   ) {
9533         /* [HGM] ignore GNUShogi noises */
9534         return;
9535     } else if (strncmp(message, "White", 5) == 0 &&
9536                message[5] != '(' &&
9537                StrStr(message, "Black") == NULL) {
9538         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9539         return;
9540     } else if (strncmp(message, "Black", 5) == 0 &&
9541                message[5] != '(') {
9542         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9543         return;
9544     } else if (strcmp(message, "resign") == 0 ||
9545                strcmp(message, "computer resigns") == 0) {
9546         switch (gameMode) {
9547           case MachinePlaysBlack:
9548           case IcsPlayingBlack:
9549             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9550             break;
9551           case MachinePlaysWhite:
9552           case IcsPlayingWhite:
9553             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9554             break;
9555           case TwoMachinesPlay:
9556             if (cps->twoMachinesColor[0] == 'w')
9557               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9558             else
9559               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9560             break;
9561           default:
9562             /* can't happen */
9563             break;
9564         }
9565         return;
9566     } else if (strncmp(message, "opponent mates", 14) == 0) {
9567         switch (gameMode) {
9568           case MachinePlaysBlack:
9569           case IcsPlayingBlack:
9570             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9571             break;
9572           case MachinePlaysWhite:
9573           case IcsPlayingWhite:
9574             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9575             break;
9576           case TwoMachinesPlay:
9577             if (cps->twoMachinesColor[0] == 'w')
9578               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9579             else
9580               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9581             break;
9582           default:
9583             /* can't happen */
9584             break;
9585         }
9586         return;
9587     } else if (strncmp(message, "computer mates", 14) == 0) {
9588         switch (gameMode) {
9589           case MachinePlaysBlack:
9590           case IcsPlayingBlack:
9591             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9592             break;
9593           case MachinePlaysWhite:
9594           case IcsPlayingWhite:
9595             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9596             break;
9597           case TwoMachinesPlay:
9598             if (cps->twoMachinesColor[0] == 'w')
9599               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9600             else
9601               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9602             break;
9603           default:
9604             /* can't happen */
9605             break;
9606         }
9607         return;
9608     } else if (strncmp(message, "checkmate", 9) == 0) {
9609         if (WhiteOnMove(forwardMostMove)) {
9610             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9611         } else {
9612             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9613         }
9614         return;
9615     } else if (strstr(message, "Draw") != NULL ||
9616                strstr(message, "game is a draw") != NULL) {
9617         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9618         return;
9619     } else if (strstr(message, "offer") != NULL &&
9620                strstr(message, "draw") != NULL) {
9621 #if ZIPPY
9622         if (appData.zippyPlay && first.initDone) {
9623             /* Relay offer to ICS */
9624             SendToICS(ics_prefix);
9625             SendToICS("draw\n");
9626         }
9627 #endif
9628         cps->offeredDraw = 2; /* valid until this engine moves twice */
9629         if (gameMode == TwoMachinesPlay) {
9630             if (cps->other->offeredDraw) {
9631                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9632             /* [HGM] in two-machine mode we delay relaying draw offer      */
9633             /* until after we also have move, to see if it is really claim */
9634             }
9635         } else if (gameMode == MachinePlaysWhite ||
9636                    gameMode == MachinePlaysBlack) {
9637           if (userOfferedDraw) {
9638             DisplayInformation(_("Machine accepts your draw offer"));
9639             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9640           } else {
9641             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9642           }
9643         }
9644     }
9645
9646
9647     /*
9648      * Look for thinking output
9649      */
9650     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9651           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9652                                 ) {
9653         int plylev, mvleft, mvtot, curscore, time;
9654         char mvname[MOVE_LEN];
9655         u64 nodes; // [DM]
9656         char plyext;
9657         int ignore = FALSE;
9658         int prefixHint = FALSE;
9659         mvname[0] = NULLCHAR;
9660
9661         switch (gameMode) {
9662           case MachinePlaysBlack:
9663           case IcsPlayingBlack:
9664             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9665             break;
9666           case MachinePlaysWhite:
9667           case IcsPlayingWhite:
9668             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9669             break;
9670           case AnalyzeMode:
9671           case AnalyzeFile:
9672             break;
9673           case IcsObserving: /* [DM] icsEngineAnalyze */
9674             if (!appData.icsEngineAnalyze) ignore = TRUE;
9675             break;
9676           case TwoMachinesPlay:
9677             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9678                 ignore = TRUE;
9679             }
9680             break;
9681           default:
9682             ignore = TRUE;
9683             break;
9684         }
9685
9686         if (!ignore) {
9687             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9688             buf1[0] = NULLCHAR;
9689             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9690                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9691                 char score_buf[MSG_SIZ];
9692
9693                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9694                     nodes += u64Const(0x100000000);
9695
9696                 if (plyext != ' ' && plyext != '\t') {
9697                     time *= 100;
9698                 }
9699
9700                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9701                 if( cps->scoreIsAbsolute &&
9702                     ( gameMode == MachinePlaysBlack ||
9703                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9704                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9705                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9706                      !WhiteOnMove(currentMove)
9707                     ) )
9708                 {
9709                     curscore = -curscore;
9710                 }
9711
9712                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9713
9714                 if(*bestMove) { // rememer time best EPD move was first found
9715                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9716                     ChessMove mt;
9717                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9718                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9719                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9720                 }
9721
9722                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9723                         char buf[MSG_SIZ];
9724                         FILE *f;
9725                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9726                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9727                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9728                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9729                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9730                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9731                                 fclose(f);
9732                         }
9733                         else
9734                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9735                           DisplayError(_("failed writing PV"), 0);
9736                 }
9737
9738                 tempStats.depth = plylev;
9739                 tempStats.nodes = nodes;
9740                 tempStats.time = time;
9741                 tempStats.score = curscore;
9742                 tempStats.got_only_move = 0;
9743
9744                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9745                         int ticklen;
9746
9747                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9748                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9749                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9750                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9751                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9752                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9753                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9754                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9755                 }
9756
9757                 /* Buffer overflow protection */
9758                 if (pv[0] != NULLCHAR) {
9759                     if (strlen(pv) >= sizeof(tempStats.movelist)
9760                         && appData.debugMode) {
9761                         fprintf(debugFP,
9762                                 "PV is too long; using the first %u bytes.\n",
9763                                 (unsigned) sizeof(tempStats.movelist) - 1);
9764                     }
9765
9766                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9767                 } else {
9768                     sprintf(tempStats.movelist, " no PV\n");
9769                 }
9770
9771                 if (tempStats.seen_stat) {
9772                     tempStats.ok_to_send = 1;
9773                 }
9774
9775                 if (strchr(tempStats.movelist, '(') != NULL) {
9776                     tempStats.line_is_book = 1;
9777                     tempStats.nr_moves = 0;
9778                     tempStats.moves_left = 0;
9779                 } else {
9780                     tempStats.line_is_book = 0;
9781                 }
9782
9783                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9784                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9785
9786                 SendProgramStatsToFrontend( cps, &tempStats );
9787
9788                 /*
9789                     [AS] Protect the thinkOutput buffer from overflow... this
9790                     is only useful if buf1 hasn't overflowed first!
9791                 */
9792                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9793                 if(curscore >= MATE_SCORE) 
9794                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9795                 else if(curscore <= -MATE_SCORE) 
9796                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9797                 else
9798                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9799                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9800                          plylev,
9801                          (gameMode == TwoMachinesPlay ?
9802                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9803                          score_buf,
9804                          prefixHint ? lastHint : "",
9805                          prefixHint ? " " : "" );
9806
9807                 if( buf1[0] != NULLCHAR ) {
9808                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9809
9810                     if( strlen(pv) > max_len ) {
9811                         if( appData.debugMode) {
9812                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9813                         }
9814                         pv[max_len+1] = '\0';
9815                     }
9816
9817                     strcat( thinkOutput, pv);
9818                 }
9819
9820                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9821                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9822                     DisplayMove(currentMove - 1);
9823                 }
9824                 return;
9825
9826             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9827                 /* crafty (9.25+) says "(only move) <move>"
9828                  * if there is only 1 legal move
9829                  */
9830                 sscanf(p, "(only move) %s", buf1);
9831                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9832                 sprintf(programStats.movelist, "%s (only move)", buf1);
9833                 programStats.depth = 1;
9834                 programStats.nr_moves = 1;
9835                 programStats.moves_left = 1;
9836                 programStats.nodes = 1;
9837                 programStats.time = 1;
9838                 programStats.got_only_move = 1;
9839
9840                 /* Not really, but we also use this member to
9841                    mean "line isn't going to change" (Crafty
9842                    isn't searching, so stats won't change) */
9843                 programStats.line_is_book = 1;
9844
9845                 SendProgramStatsToFrontend( cps, &programStats );
9846
9847                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9848                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9849                     DisplayMove(currentMove - 1);
9850                 }
9851                 return;
9852             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9853                               &time, &nodes, &plylev, &mvleft,
9854                               &mvtot, mvname) >= 5) {
9855                 /* The stat01: line is from Crafty (9.29+) in response
9856                    to the "." command */
9857                 programStats.seen_stat = 1;
9858                 cps->maybeThinking = TRUE;
9859
9860                 if (programStats.got_only_move || !appData.periodicUpdates)
9861                   return;
9862
9863                 programStats.depth = plylev;
9864                 programStats.time = time;
9865                 programStats.nodes = nodes;
9866                 programStats.moves_left = mvleft;
9867                 programStats.nr_moves = mvtot;
9868                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9869                 programStats.ok_to_send = 1;
9870                 programStats.movelist[0] = '\0';
9871
9872                 SendProgramStatsToFrontend( cps, &programStats );
9873
9874                 return;
9875
9876             } else if (strncmp(message,"++",2) == 0) {
9877                 /* Crafty 9.29+ outputs this */
9878                 programStats.got_fail = 2;
9879                 return;
9880
9881             } else if (strncmp(message,"--",2) == 0) {
9882                 /* Crafty 9.29+ outputs this */
9883                 programStats.got_fail = 1;
9884                 return;
9885
9886             } else if (thinkOutput[0] != NULLCHAR &&
9887                        strncmp(message, "    ", 4) == 0) {
9888                 unsigned message_len;
9889
9890                 p = message;
9891                 while (*p && *p == ' ') p++;
9892
9893                 message_len = strlen( p );
9894
9895                 /* [AS] Avoid buffer overflow */
9896                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9897                     strcat(thinkOutput, " ");
9898                     strcat(thinkOutput, p);
9899                 }
9900
9901                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9902                     strcat(programStats.movelist, " ");
9903                     strcat(programStats.movelist, p);
9904                 }
9905
9906                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9907                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9908                     DisplayMove(currentMove - 1);
9909                 }
9910                 return;
9911             }
9912         }
9913         else {
9914             buf1[0] = NULLCHAR;
9915
9916             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9917                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9918             {
9919                 ChessProgramStats cpstats;
9920
9921                 if (plyext != ' ' && plyext != '\t') {
9922                     time *= 100;
9923                 }
9924
9925                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9926                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9927                     curscore = -curscore;
9928                 }
9929
9930                 cpstats.depth = plylev;
9931                 cpstats.nodes = nodes;
9932                 cpstats.time = time;
9933                 cpstats.score = curscore;
9934                 cpstats.got_only_move = 0;
9935                 cpstats.movelist[0] = '\0';
9936
9937                 if (buf1[0] != NULLCHAR) {
9938                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9939                 }
9940
9941                 cpstats.ok_to_send = 0;
9942                 cpstats.line_is_book = 0;
9943                 cpstats.nr_moves = 0;
9944                 cpstats.moves_left = 0;
9945
9946                 SendProgramStatsToFrontend( cps, &cpstats );
9947             }
9948         }
9949     }
9950 }
9951
9952
9953 /* Parse a game score from the character string "game", and
9954    record it as the history of the current game.  The game
9955    score is NOT assumed to start from the standard position.
9956    The display is not updated in any way.
9957    */
9958 void
9959 ParseGameHistory (char *game)
9960 {
9961     ChessMove moveType;
9962     int fromX, fromY, toX, toY, boardIndex;
9963     char promoChar;
9964     char *p, *q;
9965     char buf[MSG_SIZ];
9966
9967     if (appData.debugMode)
9968       fprintf(debugFP, "Parsing game history: %s\n", game);
9969
9970     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9971     gameInfo.site = StrSave(appData.icsHost);
9972     gameInfo.date = PGNDate();
9973     gameInfo.round = StrSave("-");
9974
9975     /* Parse out names of players */
9976     while (*game == ' ') game++;
9977     p = buf;
9978     while (*game != ' ') *p++ = *game++;
9979     *p = NULLCHAR;
9980     gameInfo.white = StrSave(buf);
9981     while (*game == ' ') game++;
9982     p = buf;
9983     while (*game != ' ' && *game != '\n') *p++ = *game++;
9984     *p = NULLCHAR;
9985     gameInfo.black = StrSave(buf);
9986
9987     /* Parse moves */
9988     boardIndex = blackPlaysFirst ? 1 : 0;
9989     yynewstr(game);
9990     for (;;) {
9991         yyboardindex = boardIndex;
9992         moveType = (ChessMove) Myylex();
9993         switch (moveType) {
9994           case IllegalMove:             /* maybe suicide chess, etc. */
9995   if (appData.debugMode) {
9996     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9997     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9998     setbuf(debugFP, NULL);
9999   }
10000           case WhitePromotion:
10001           case BlackPromotion:
10002           case WhiteNonPromotion:
10003           case BlackNonPromotion:
10004           case NormalMove:
10005           case FirstLeg:
10006           case WhiteCapturesEnPassant:
10007           case BlackCapturesEnPassant:
10008           case WhiteKingSideCastle:
10009           case WhiteQueenSideCastle:
10010           case BlackKingSideCastle:
10011           case BlackQueenSideCastle:
10012           case WhiteKingSideCastleWild:
10013           case WhiteQueenSideCastleWild:
10014           case BlackKingSideCastleWild:
10015           case BlackQueenSideCastleWild:
10016           /* PUSH Fabien */
10017           case WhiteHSideCastleFR:
10018           case WhiteASideCastleFR:
10019           case BlackHSideCastleFR:
10020           case BlackASideCastleFR:
10021           /* POP Fabien */
10022             fromX = currentMoveString[0] - AAA;
10023             fromY = currentMoveString[1] - ONE;
10024             toX = currentMoveString[2] - AAA;
10025             toY = currentMoveString[3] - ONE;
10026             promoChar = currentMoveString[4];
10027             break;
10028           case WhiteDrop:
10029           case BlackDrop:
10030             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10031             fromX = moveType == WhiteDrop ?
10032               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10033             (int) CharToPiece(ToLower(currentMoveString[0]));
10034             fromY = DROP_RANK;
10035             toX = currentMoveString[2] - AAA;
10036             toY = currentMoveString[3] - ONE;
10037             promoChar = NULLCHAR;
10038             break;
10039           case AmbiguousMove:
10040             /* bug? */
10041             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10042   if (appData.debugMode) {
10043     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10044     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10045     setbuf(debugFP, NULL);
10046   }
10047             DisplayError(buf, 0);
10048             return;
10049           case ImpossibleMove:
10050             /* bug? */
10051             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10052   if (appData.debugMode) {
10053     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10054     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10055     setbuf(debugFP, NULL);
10056   }
10057             DisplayError(buf, 0);
10058             return;
10059           case EndOfFile:
10060             if (boardIndex < backwardMostMove) {
10061                 /* Oops, gap.  How did that happen? */
10062                 DisplayError(_("Gap in move list"), 0);
10063                 return;
10064             }
10065             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10066             if (boardIndex > forwardMostMove) {
10067                 forwardMostMove = boardIndex;
10068             }
10069             return;
10070           case ElapsedTime:
10071             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10072                 strcat(parseList[boardIndex-1], " ");
10073                 strcat(parseList[boardIndex-1], yy_text);
10074             }
10075             continue;
10076           case Comment:
10077           case PGNTag:
10078           case NAG:
10079           default:
10080             /* ignore */
10081             continue;
10082           case WhiteWins:
10083           case BlackWins:
10084           case GameIsDrawn:
10085           case GameUnfinished:
10086             if (gameMode == IcsExamining) {
10087                 if (boardIndex < backwardMostMove) {
10088                     /* Oops, gap.  How did that happen? */
10089                     return;
10090                 }
10091                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10092                 return;
10093             }
10094             gameInfo.result = moveType;
10095             p = strchr(yy_text, '{');
10096             if (p == NULL) p = strchr(yy_text, '(');
10097             if (p == NULL) {
10098                 p = yy_text;
10099                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10100             } else {
10101                 q = strchr(p, *p == '{' ? '}' : ')');
10102                 if (q != NULL) *q = NULLCHAR;
10103                 p++;
10104             }
10105             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10106             gameInfo.resultDetails = StrSave(p);
10107             continue;
10108         }
10109         if (boardIndex >= forwardMostMove &&
10110             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10111             backwardMostMove = blackPlaysFirst ? 1 : 0;
10112             return;
10113         }
10114         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10115                                  fromY, fromX, toY, toX, promoChar,
10116                                  parseList[boardIndex]);
10117         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10118         /* currentMoveString is set as a side-effect of yylex */
10119         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10120         strcat(moveList[boardIndex], "\n");
10121         boardIndex++;
10122         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10123         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10124           case MT_NONE:
10125           case MT_STALEMATE:
10126           default:
10127             break;
10128           case MT_CHECK:
10129             if(!IS_SHOGI(gameInfo.variant))
10130                 strcat(parseList[boardIndex - 1], "+");
10131             break;
10132           case MT_CHECKMATE:
10133           case MT_STAINMATE:
10134             strcat(parseList[boardIndex - 1], "#");
10135             break;
10136         }
10137     }
10138 }
10139
10140
10141 /* Apply a move to the given board  */
10142 void
10143 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10144 {
10145   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10146   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10147
10148     /* [HGM] compute & store e.p. status and castling rights for new position */
10149     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10150
10151       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10152       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10153       board[EP_STATUS] = EP_NONE;
10154       board[EP_FILE] = board[EP_RANK] = 100;
10155
10156   if (fromY == DROP_RANK) {
10157         /* must be first */
10158         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10159             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10160             return;
10161         }
10162         piece = board[toY][toX] = (ChessSquare) fromX;
10163   } else {
10164 //      ChessSquare victim;
10165       int i;
10166
10167       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10168 //           victim = board[killY][killX],
10169            killed = board[killY][killX],
10170            board[killY][killX] = EmptySquare,
10171            board[EP_STATUS] = EP_CAPTURE;
10172            if( kill2X >= 0 && kill2Y >= 0)
10173              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10174       }
10175
10176       if( board[toY][toX] != EmptySquare ) {
10177            board[EP_STATUS] = EP_CAPTURE;
10178            if( (fromX != toX || fromY != toY) && // not igui!
10179                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10180                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10181                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10182            }
10183       }
10184
10185       pawn = board[fromY][fromX];
10186       if( pawn == WhiteLance || pawn == BlackLance ) {
10187            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10188                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10189                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10190            }
10191       }
10192       if( pawn == WhitePawn ) {
10193            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10194                board[EP_STATUS] = EP_PAWN_MOVE;
10195            if( toY-fromY>=2) {
10196                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10197                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10198                         gameInfo.variant != VariantBerolina || toX < fromX)
10199                       board[EP_STATUS] = toX | berolina;
10200                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10201                         gameInfo.variant != VariantBerolina || toX > fromX)
10202                       board[EP_STATUS] = toX;
10203            }
10204       } else
10205       if( pawn == BlackPawn ) {
10206            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10207                board[EP_STATUS] = EP_PAWN_MOVE;
10208            if( toY-fromY<= -2) {
10209                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10210                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10211                         gameInfo.variant != VariantBerolina || toX < fromX)
10212                       board[EP_STATUS] = toX | berolina;
10213                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10214                         gameInfo.variant != VariantBerolina || toX > fromX)
10215                       board[EP_STATUS] = toX;
10216            }
10217        }
10218
10219        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10220        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10221        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10222        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10223
10224        for(i=0; i<nrCastlingRights; i++) {
10225            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10226               board[CASTLING][i] == toX   && castlingRank[i] == toY
10227              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10228        }
10229
10230        if(gameInfo.variant == VariantSChess) { // update virginity
10231            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10232            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10233            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10234            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10235        }
10236
10237      if (fromX == toX && fromY == toY && killX < 0) return;
10238
10239      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10240      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10241      if(gameInfo.variant == VariantKnightmate)
10242          king += (int) WhiteUnicorn - (int) WhiteKing;
10243
10244     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10245        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10246         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10247         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10248         board[EP_STATUS] = EP_NONE; // capture was fake!
10249     } else
10250     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10251         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10252         board[toY][toX] = piece;
10253         board[EP_STATUS] = EP_NONE; // capture was fake!
10254     } else
10255     /* Code added by Tord: */
10256     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10257     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10258         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10259       board[EP_STATUS] = EP_NONE; // capture was fake!
10260       board[fromY][fromX] = EmptySquare;
10261       board[toY][toX] = EmptySquare;
10262       if((toX > fromX) != (piece == WhiteRook)) {
10263         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10264       } else {
10265         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10266       }
10267     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10268                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10269       board[EP_STATUS] = EP_NONE;
10270       board[fromY][fromX] = EmptySquare;
10271       board[toY][toX] = EmptySquare;
10272       if((toX > fromX) != (piece == BlackRook)) {
10273         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10274       } else {
10275         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10276       }
10277     /* End of code added by Tord */
10278
10279     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10280         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10281         board[toY][toX] = piece;
10282     } else if (board[fromY][fromX] == king
10283         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10284         && toY == fromY && toX > fromX+1) {
10285         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10286         board[fromY][toX-1] = board[fromY][rookX];
10287         board[fromY][rookX] = EmptySquare;
10288         board[fromY][fromX] = EmptySquare;
10289         board[toY][toX] = king;
10290     } else if (board[fromY][fromX] == king
10291         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10292                && toY == fromY && toX < fromX-1) {
10293         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10294         board[fromY][toX+1] = board[fromY][rookX];
10295         board[fromY][rookX] = EmptySquare;
10296         board[fromY][fromX] = EmptySquare;
10297         board[toY][toX] = king;
10298     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10299                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10300                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10301                ) {
10302         /* white pawn promotion */
10303         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10304         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10305             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10306         board[fromY][fromX] = EmptySquare;
10307     } else if ((fromY >= BOARD_HEIGHT>>1)
10308                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10309                && (toX != fromX)
10310                && gameInfo.variant != VariantXiangqi
10311                && gameInfo.variant != VariantBerolina
10312                && (pawn == WhitePawn)
10313                && (board[toY][toX] == EmptySquare)) {
10314         board[fromY][fromX] = EmptySquare;
10315         board[toY][toX] = piece;
10316         if(toY == epRank - 128 + 1)
10317             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10318         else
10319             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10320     } else if ((fromY == BOARD_HEIGHT-4)
10321                && (toX == fromX)
10322                && gameInfo.variant == VariantBerolina
10323                && (board[fromY][fromX] == WhitePawn)
10324                && (board[toY][toX] == EmptySquare)) {
10325         board[fromY][fromX] = EmptySquare;
10326         board[toY][toX] = WhitePawn;
10327         if(oldEP & EP_BEROLIN_A) {
10328                 captured = board[fromY][fromX-1];
10329                 board[fromY][fromX-1] = EmptySquare;
10330         }else{  captured = board[fromY][fromX+1];
10331                 board[fromY][fromX+1] = EmptySquare;
10332         }
10333     } else if (board[fromY][fromX] == king
10334         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10335                && toY == fromY && toX > fromX+1) {
10336         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10337         board[fromY][toX-1] = board[fromY][rookX];
10338         board[fromY][rookX] = EmptySquare;
10339         board[fromY][fromX] = EmptySquare;
10340         board[toY][toX] = king;
10341     } else if (board[fromY][fromX] == king
10342         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10343                && toY == fromY && toX < fromX-1) {
10344         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10345         board[fromY][toX+1] = board[fromY][rookX];
10346         board[fromY][rookX] = EmptySquare;
10347         board[fromY][fromX] = EmptySquare;
10348         board[toY][toX] = king;
10349     } else if (fromY == 7 && fromX == 3
10350                && board[fromY][fromX] == BlackKing
10351                && toY == 7 && toX == 5) {
10352         board[fromY][fromX] = EmptySquare;
10353         board[toY][toX] = BlackKing;
10354         board[fromY][7] = EmptySquare;
10355         board[toY][4] = BlackRook;
10356     } else if (fromY == 7 && fromX == 3
10357                && board[fromY][fromX] == BlackKing
10358                && toY == 7 && toX == 1) {
10359         board[fromY][fromX] = EmptySquare;
10360         board[toY][toX] = BlackKing;
10361         board[fromY][0] = EmptySquare;
10362         board[toY][2] = BlackRook;
10363     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10364                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10365                && toY < promoRank && promoChar
10366                ) {
10367         /* black pawn promotion */
10368         board[toY][toX] = CharToPiece(ToLower(promoChar));
10369         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10370             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10371         board[fromY][fromX] = EmptySquare;
10372     } else if ((fromY < BOARD_HEIGHT>>1)
10373                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10374                && (toX != fromX)
10375                && gameInfo.variant != VariantXiangqi
10376                && gameInfo.variant != VariantBerolina
10377                && (pawn == BlackPawn)
10378                && (board[toY][toX] == EmptySquare)) {
10379         board[fromY][fromX] = EmptySquare;
10380         board[toY][toX] = piece;
10381         if(toY == epRank - 128 - 1)
10382             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10383         else
10384             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10385     } else if ((fromY == 3)
10386                && (toX == fromX)
10387                && gameInfo.variant == VariantBerolina
10388                && (board[fromY][fromX] == BlackPawn)
10389                && (board[toY][toX] == EmptySquare)) {
10390         board[fromY][fromX] = EmptySquare;
10391         board[toY][toX] = BlackPawn;
10392         if(oldEP & EP_BEROLIN_A) {
10393                 captured = board[fromY][fromX-1];
10394                 board[fromY][fromX-1] = EmptySquare;
10395         }else{  captured = board[fromY][fromX+1];
10396                 board[fromY][fromX+1] = EmptySquare;
10397         }
10398     } else {
10399         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10400         board[fromY][fromX] = EmptySquare;
10401         board[toY][toX] = piece;
10402     }
10403   }
10404
10405     if (gameInfo.holdingsWidth != 0) {
10406
10407       /* !!A lot more code needs to be written to support holdings  */
10408       /* [HGM] OK, so I have written it. Holdings are stored in the */
10409       /* penultimate board files, so they are automaticlly stored   */
10410       /* in the game history.                                       */
10411       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10412                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10413         /* Delete from holdings, by decreasing count */
10414         /* and erasing image if necessary            */
10415         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10416         if(p < (int) BlackPawn) { /* white drop */
10417              p -= (int)WhitePawn;
10418                  p = PieceToNumber((ChessSquare)p);
10419              if(p >= gameInfo.holdingsSize) p = 0;
10420              if(--board[p][BOARD_WIDTH-2] <= 0)
10421                   board[p][BOARD_WIDTH-1] = EmptySquare;
10422              if((int)board[p][BOARD_WIDTH-2] < 0)
10423                         board[p][BOARD_WIDTH-2] = 0;
10424         } else {                  /* black drop */
10425              p -= (int)BlackPawn;
10426                  p = PieceToNumber((ChessSquare)p);
10427              if(p >= gameInfo.holdingsSize) p = 0;
10428              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10429                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10430              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10431                         board[BOARD_HEIGHT-1-p][1] = 0;
10432         }
10433       }
10434       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10435           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10436         /* [HGM] holdings: Add to holdings, if holdings exist */
10437         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10438                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10439                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10440         }
10441         p = (int) captured;
10442         if (p >= (int) BlackPawn) {
10443           p -= (int)BlackPawn;
10444           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10445                   /* Restore shogi-promoted piece to its original  first */
10446                   captured = (ChessSquare) (DEMOTED(captured));
10447                   p = DEMOTED(p);
10448           }
10449           p = PieceToNumber((ChessSquare)p);
10450           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10451           board[p][BOARD_WIDTH-2]++;
10452           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10453         } else {
10454           p -= (int)WhitePawn;
10455           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10456                   captured = (ChessSquare) (DEMOTED(captured));
10457                   p = DEMOTED(p);
10458           }
10459           p = PieceToNumber((ChessSquare)p);
10460           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10461           board[BOARD_HEIGHT-1-p][1]++;
10462           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10463         }
10464       }
10465     } else if (gameInfo.variant == VariantAtomic) {
10466       if (captured != EmptySquare) {
10467         int y, x;
10468         for (y = toY-1; y <= toY+1; y++) {
10469           for (x = toX-1; x <= toX+1; x++) {
10470             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10471                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10472               board[y][x] = EmptySquare;
10473             }
10474           }
10475         }
10476         board[toY][toX] = EmptySquare;
10477       }
10478     }
10479
10480     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10481         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10482     } else
10483     if(promoChar == '+') {
10484         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10485         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10486         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10487           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10488     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10489         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10490         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10491            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10492         board[toY][toX] = newPiece;
10493     }
10494     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10495                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10496         // [HGM] superchess: take promotion piece out of holdings
10497         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10498         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10499             if(!--board[k][BOARD_WIDTH-2])
10500                 board[k][BOARD_WIDTH-1] = EmptySquare;
10501         } else {
10502             if(!--board[BOARD_HEIGHT-1-k][1])
10503                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10504         }
10505     }
10506 }
10507
10508 /* Updates forwardMostMove */
10509 void
10510 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10511 {
10512     int x = toX, y = toY;
10513     char *s = parseList[forwardMostMove];
10514     ChessSquare p = boards[forwardMostMove][toY][toX];
10515 //    forwardMostMove++; // [HGM] bare: moved downstream
10516
10517     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10518     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10519     (void) CoordsToAlgebraic(boards[forwardMostMove],
10520                              PosFlags(forwardMostMove),
10521                              fromY, fromX, y, x, (killX < 0)*promoChar,
10522                              s);
10523     if(kill2X >= 0 && kill2Y >= 0)
10524         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10525     if(killX >= 0 && killY >= 0)
10526         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10527                                            toX + AAA, toY + ONE - '0', promoChar);
10528
10529     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10530         int timeLeft; static int lastLoadFlag=0; int king, piece;
10531         piece = boards[forwardMostMove][fromY][fromX];
10532         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10533         if(gameInfo.variant == VariantKnightmate)
10534             king += (int) WhiteUnicorn - (int) WhiteKing;
10535         if(forwardMostMove == 0) {
10536             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10537                 fprintf(serverMoves, "%s;", UserName());
10538             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10539                 fprintf(serverMoves, "%s;", second.tidy);
10540             fprintf(serverMoves, "%s;", first.tidy);
10541             if(gameMode == MachinePlaysWhite)
10542                 fprintf(serverMoves, "%s;", UserName());
10543             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10544                 fprintf(serverMoves, "%s;", second.tidy);
10545         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10546         lastLoadFlag = loadFlag;
10547         // print base move
10548         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10549         // print castling suffix
10550         if( toY == fromY && piece == king ) {
10551             if(toX-fromX > 1)
10552                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10553             if(fromX-toX >1)
10554                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10555         }
10556         // e.p. suffix
10557         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10558              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10559              boards[forwardMostMove][toY][toX] == EmptySquare
10560              && fromX != toX && fromY != toY)
10561                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10562         // promotion suffix
10563         if(promoChar != NULLCHAR) {
10564             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10565                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10566                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10567             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10568         }
10569         if(!loadFlag) {
10570                 char buf[MOVE_LEN*2], *p; int len;
10571             fprintf(serverMoves, "/%d/%d",
10572                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10573             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10574             else                      timeLeft = blackTimeRemaining/1000;
10575             fprintf(serverMoves, "/%d", timeLeft);
10576                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10577                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10578                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10579                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10580             fprintf(serverMoves, "/%s", buf);
10581         }
10582         fflush(serverMoves);
10583     }
10584
10585     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10586         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10587       return;
10588     }
10589     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10590     if (commentList[forwardMostMove+1] != NULL) {
10591         free(commentList[forwardMostMove+1]);
10592         commentList[forwardMostMove+1] = NULL;
10593     }
10594     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10595     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10596     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10597     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10598     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10599     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10600     adjustedClock = FALSE;
10601     gameInfo.result = GameUnfinished;
10602     if (gameInfo.resultDetails != NULL) {
10603         free(gameInfo.resultDetails);
10604         gameInfo.resultDetails = NULL;
10605     }
10606     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10607                               moveList[forwardMostMove - 1]);
10608     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10609       case MT_NONE:
10610       case MT_STALEMATE:
10611       default:
10612         break;
10613       case MT_CHECK:
10614         if(!IS_SHOGI(gameInfo.variant))
10615             strcat(parseList[forwardMostMove - 1], "+");
10616         break;
10617       case MT_CHECKMATE:
10618       case MT_STAINMATE:
10619         strcat(parseList[forwardMostMove - 1], "#");
10620         break;
10621     }
10622 }
10623
10624 /* Updates currentMove if not pausing */
10625 void
10626 ShowMove (int fromX, int fromY, int toX, int toY)
10627 {
10628     int instant = (gameMode == PlayFromGameFile) ?
10629         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10630     if(appData.noGUI) return;
10631     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10632         if (!instant) {
10633             if (forwardMostMove == currentMove + 1) {
10634                 AnimateMove(boards[forwardMostMove - 1],
10635                             fromX, fromY, toX, toY);
10636             }
10637         }
10638         currentMove = forwardMostMove;
10639     }
10640
10641     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10642
10643     if (instant) return;
10644
10645     DisplayMove(currentMove - 1);
10646     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10647             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10648                 SetHighlights(fromX, fromY, toX, toY);
10649             }
10650     }
10651     DrawPosition(FALSE, boards[currentMove]);
10652     DisplayBothClocks();
10653     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10654 }
10655
10656 void
10657 SendEgtPath (ChessProgramState *cps)
10658 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10659         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10660
10661         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10662
10663         while(*p) {
10664             char c, *q = name+1, *r, *s;
10665
10666             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10667             while(*p && *p != ',') *q++ = *p++;
10668             *q++ = ':'; *q = 0;
10669             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10670                 strcmp(name, ",nalimov:") == 0 ) {
10671                 // take nalimov path from the menu-changeable option first, if it is defined
10672               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10673                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10674             } else
10675             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10676                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10677                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10678                 s = r = StrStr(s, ":") + 1; // beginning of path info
10679                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10680                 c = *r; *r = 0;             // temporarily null-terminate path info
10681                     *--q = 0;               // strip of trailig ':' from name
10682                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10683                 *r = c;
10684                 SendToProgram(buf,cps);     // send egtbpath command for this format
10685             }
10686             if(*p == ',') p++; // read away comma to position for next format name
10687         }
10688 }
10689
10690 static int
10691 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10692 {
10693       int width = 8, height = 8, holdings = 0;             // most common sizes
10694       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10695       // correct the deviations default for each variant
10696       if( v == VariantXiangqi ) width = 9,  height = 10;
10697       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10698       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10699       if( v == VariantCapablanca || v == VariantCapaRandom ||
10700           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10701                                 width = 10;
10702       if( v == VariantCourier ) width = 12;
10703       if( v == VariantSuper )                            holdings = 8;
10704       if( v == VariantGreat )   width = 10,              holdings = 8;
10705       if( v == VariantSChess )                           holdings = 7;
10706       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10707       if( v == VariantChuChess) width = 10, height = 10;
10708       if( v == VariantChu )     width = 12, height = 12;
10709       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10710              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10711              holdingsSize >= 0 && holdingsSize != holdings;
10712 }
10713
10714 char variantError[MSG_SIZ];
10715
10716 char *
10717 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10718 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10719       char *p, *variant = VariantName(v);
10720       static char b[MSG_SIZ];
10721       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10722            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10723                                                holdingsSize, variant); // cook up sized variant name
10724            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10725            if(StrStr(list, b) == NULL) {
10726                // specific sized variant not known, check if general sizing allowed
10727                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10728                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10729                             boardWidth, boardHeight, holdingsSize, engine);
10730                    return NULL;
10731                }
10732                /* [HGM] here we really should compare with the maximum supported board size */
10733            }
10734       } else snprintf(b, MSG_SIZ,"%s", variant);
10735       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10736       p = StrStr(list, b);
10737       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10738       if(p == NULL) {
10739           // occurs not at all in list, or only as sub-string
10740           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10741           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10742               int l = strlen(variantError);
10743               char *q;
10744               while(p != list && p[-1] != ',') p--;
10745               q = strchr(p, ',');
10746               if(q) *q = NULLCHAR;
10747               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10748               if(q) *q= ',';
10749           }
10750           return NULL;
10751       }
10752       return b;
10753 }
10754
10755 void
10756 InitChessProgram (ChessProgramState *cps, int setup)
10757 /* setup needed to setup FRC opening position */
10758 {
10759     char buf[MSG_SIZ], *b;
10760     if (appData.noChessProgram) return;
10761     hintRequested = FALSE;
10762     bookRequested = FALSE;
10763
10764     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10765     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10766     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10767     if(cps->memSize) { /* [HGM] memory */
10768       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10769         SendToProgram(buf, cps);
10770     }
10771     SendEgtPath(cps); /* [HGM] EGT */
10772     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10773       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10774         SendToProgram(buf, cps);
10775     }
10776
10777     setboardSpoiledMachineBlack = FALSE;
10778     SendToProgram(cps->initString, cps);
10779     if (gameInfo.variant != VariantNormal &&
10780         gameInfo.variant != VariantLoadable
10781         /* [HGM] also send variant if board size non-standard */
10782         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10783
10784       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10785                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10786       if (b == NULL) {
10787         VariantClass v;
10788         char c, *q = cps->variants, *p = strchr(q, ',');
10789         if(p) *p = NULLCHAR;
10790         v = StringToVariant(q);
10791         DisplayError(variantError, 0);
10792         if(v != VariantUnknown && cps == &first) {
10793             int w, h, s;
10794             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10795                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10796             ASSIGN(appData.variant, q);
10797             Reset(TRUE, FALSE);
10798         }
10799         if(p) *p = ',';
10800         return;
10801       }
10802
10803       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10804       SendToProgram(buf, cps);
10805     }
10806     currentlyInitializedVariant = gameInfo.variant;
10807
10808     /* [HGM] send opening position in FRC to first engine */
10809     if(setup) {
10810           SendToProgram("force\n", cps);
10811           SendBoard(cps, 0);
10812           /* engine is now in force mode! Set flag to wake it up after first move. */
10813           setboardSpoiledMachineBlack = 1;
10814     }
10815
10816     if (cps->sendICS) {
10817       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10818       SendToProgram(buf, cps);
10819     }
10820     cps->maybeThinking = FALSE;
10821     cps->offeredDraw = 0;
10822     if (!appData.icsActive) {
10823         SendTimeControl(cps, movesPerSession, timeControl,
10824                         timeIncrement, appData.searchDepth,
10825                         searchTime);
10826     }
10827     if (appData.showThinking
10828         // [HGM] thinking: four options require thinking output to be sent
10829         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10830                                 ) {
10831         SendToProgram("post\n", cps);
10832     }
10833     SendToProgram("hard\n", cps);
10834     if (!appData.ponderNextMove) {
10835         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10836            it without being sure what state we are in first.  "hard"
10837            is not a toggle, so that one is OK.
10838          */
10839         SendToProgram("easy\n", cps);
10840     }
10841     if (cps->usePing) {
10842       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10843       SendToProgram(buf, cps);
10844     }
10845     cps->initDone = TRUE;
10846     ClearEngineOutputPane(cps == &second);
10847 }
10848
10849
10850 void
10851 ResendOptions (ChessProgramState *cps)
10852 { // send the stored value of the options
10853   int i;
10854   char buf[MSG_SIZ];
10855   Option *opt = cps->option;
10856   for(i=0; i<cps->nrOptions; i++, opt++) {
10857       switch(opt->type) {
10858         case Spin:
10859         case Slider:
10860         case CheckBox:
10861             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10862           break;
10863         case ComboBox:
10864           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10865           break;
10866         default:
10867             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10868           break;
10869         case Button:
10870         case SaveButton:
10871           continue;
10872       }
10873       SendToProgram(buf, cps);
10874   }
10875 }
10876
10877 void
10878 StartChessProgram (ChessProgramState *cps)
10879 {
10880     char buf[MSG_SIZ];
10881     int err;
10882
10883     if (appData.noChessProgram) return;
10884     cps->initDone = FALSE;
10885
10886     if (strcmp(cps->host, "localhost") == 0) {
10887         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10888     } else if (*appData.remoteShell == NULLCHAR) {
10889         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10890     } else {
10891         if (*appData.remoteUser == NULLCHAR) {
10892           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10893                     cps->program);
10894         } else {
10895           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10896                     cps->host, appData.remoteUser, cps->program);
10897         }
10898         err = StartChildProcess(buf, "", &cps->pr);
10899     }
10900
10901     if (err != 0) {
10902       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10903         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10904         if(cps != &first) return;
10905         appData.noChessProgram = TRUE;
10906         ThawUI();
10907         SetNCPMode();
10908 //      DisplayFatalError(buf, err, 1);
10909 //      cps->pr = NoProc;
10910 //      cps->isr = NULL;
10911         return;
10912     }
10913
10914     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10915     if (cps->protocolVersion > 1) {
10916       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10917       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10918         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10919         cps->comboCnt = 0;  //                and values of combo boxes
10920       }
10921       SendToProgram(buf, cps);
10922       if(cps->reload) ResendOptions(cps);
10923     } else {
10924       SendToProgram("xboard\n", cps);
10925     }
10926 }
10927
10928 void
10929 TwoMachinesEventIfReady P((void))
10930 {
10931   static int curMess = 0;
10932   if (first.lastPing != first.lastPong) {
10933     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10934     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10935     return;
10936   }
10937   if (second.lastPing != second.lastPong) {
10938     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10939     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10940     return;
10941   }
10942   DisplayMessage("", ""); curMess = 0;
10943   TwoMachinesEvent();
10944 }
10945
10946 char *
10947 MakeName (char *template)
10948 {
10949     time_t clock;
10950     struct tm *tm;
10951     static char buf[MSG_SIZ];
10952     char *p = buf;
10953     int i;
10954
10955     clock = time((time_t *)NULL);
10956     tm = localtime(&clock);
10957
10958     while(*p++ = *template++) if(p[-1] == '%') {
10959         switch(*template++) {
10960           case 0:   *p = 0; return buf;
10961           case 'Y': i = tm->tm_year+1900; break;
10962           case 'y': i = tm->tm_year-100; break;
10963           case 'M': i = tm->tm_mon+1; break;
10964           case 'd': i = tm->tm_mday; break;
10965           case 'h': i = tm->tm_hour; break;
10966           case 'm': i = tm->tm_min; break;
10967           case 's': i = tm->tm_sec; break;
10968           default:  i = 0;
10969         }
10970         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10971     }
10972     return buf;
10973 }
10974
10975 int
10976 CountPlayers (char *p)
10977 {
10978     int n = 0;
10979     while(p = strchr(p, '\n')) p++, n++; // count participants
10980     return n;
10981 }
10982
10983 FILE *
10984 WriteTourneyFile (char *results, FILE *f)
10985 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10986     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10987     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10988         // create a file with tournament description
10989         fprintf(f, "-participants {%s}\n", appData.participants);
10990         fprintf(f, "-seedBase %d\n", appData.seedBase);
10991         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10992         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10993         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10994         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10995         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10996         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10997         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10998         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10999         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11000         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11001         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11002         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11003         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11004         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11005         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11006         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11007         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11008         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11009         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11010         fprintf(f, "-smpCores %d\n", appData.smpCores);
11011         if(searchTime > 0)
11012                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11013         else {
11014                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11015                 fprintf(f, "-tc %s\n", appData.timeControl);
11016                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11017         }
11018         fprintf(f, "-results \"%s\"\n", results);
11019     }
11020     return f;
11021 }
11022
11023 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11024
11025 void
11026 Substitute (char *participants, int expunge)
11027 {
11028     int i, changed, changes=0, nPlayers=0;
11029     char *p, *q, *r, buf[MSG_SIZ];
11030     if(participants == NULL) return;
11031     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11032     r = p = participants; q = appData.participants;
11033     while(*p && *p == *q) {
11034         if(*p == '\n') r = p+1, nPlayers++;
11035         p++; q++;
11036     }
11037     if(*p) { // difference
11038         while(*p && *p++ != '\n');
11039         while(*q && *q++ != '\n');
11040       changed = nPlayers;
11041         changes = 1 + (strcmp(p, q) != 0);
11042     }
11043     if(changes == 1) { // a single engine mnemonic was changed
11044         q = r; while(*q) nPlayers += (*q++ == '\n');
11045         p = buf; while(*r && (*p = *r++) != '\n') p++;
11046         *p = NULLCHAR;
11047         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11048         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11049         if(mnemonic[i]) { // The substitute is valid
11050             FILE *f;
11051             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11052                 flock(fileno(f), LOCK_EX);
11053                 ParseArgsFromFile(f);
11054                 fseek(f, 0, SEEK_SET);
11055                 FREE(appData.participants); appData.participants = participants;
11056                 if(expunge) { // erase results of replaced engine
11057                     int len = strlen(appData.results), w, b, dummy;
11058                     for(i=0; i<len; i++) {
11059                         Pairing(i, nPlayers, &w, &b, &dummy);
11060                         if((w == changed || b == changed) && appData.results[i] == '*') {
11061                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11062                             fclose(f);
11063                             return;
11064                         }
11065                     }
11066                     for(i=0; i<len; i++) {
11067                         Pairing(i, nPlayers, &w, &b, &dummy);
11068                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11069                     }
11070                 }
11071                 WriteTourneyFile(appData.results, f);
11072                 fclose(f); // release lock
11073                 return;
11074             }
11075         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11076     }
11077     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11078     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11079     free(participants);
11080     return;
11081 }
11082
11083 int
11084 CheckPlayers (char *participants)
11085 {
11086         int i;
11087         char buf[MSG_SIZ], *p;
11088         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11089         while(p = strchr(participants, '\n')) {
11090             *p = NULLCHAR;
11091             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11092             if(!mnemonic[i]) {
11093                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11094                 *p = '\n';
11095                 DisplayError(buf, 0);
11096                 return 1;
11097             }
11098             *p = '\n';
11099             participants = p + 1;
11100         }
11101         return 0;
11102 }
11103
11104 int
11105 CreateTourney (char *name)
11106 {
11107         FILE *f;
11108         if(matchMode && strcmp(name, appData.tourneyFile)) {
11109              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11110         }
11111         if(name[0] == NULLCHAR) {
11112             if(appData.participants[0])
11113                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11114             return 0;
11115         }
11116         f = fopen(name, "r");
11117         if(f) { // file exists
11118             ASSIGN(appData.tourneyFile, name);
11119             ParseArgsFromFile(f); // parse it
11120         } else {
11121             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11122             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11123                 DisplayError(_("Not enough participants"), 0);
11124                 return 0;
11125             }
11126             if(CheckPlayers(appData.participants)) return 0;
11127             ASSIGN(appData.tourneyFile, name);
11128             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11129             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11130         }
11131         fclose(f);
11132         appData.noChessProgram = FALSE;
11133         appData.clockMode = TRUE;
11134         SetGNUMode();
11135         return 1;
11136 }
11137
11138 int
11139 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11140 {
11141     char buf[MSG_SIZ], *p, *q;
11142     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11143     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11144     skip = !all && group[0]; // if group requested, we start in skip mode
11145     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11146         p = names; q = buf; header = 0;
11147         while(*p && *p != '\n') *q++ = *p++;
11148         *q = 0;
11149         if(*p == '\n') p++;
11150         if(buf[0] == '#') {
11151             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11152             depth++; // we must be entering a new group
11153             if(all) continue; // suppress printing group headers when complete list requested
11154             header = 1;
11155             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11156         }
11157         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11158         if(engineList[i]) free(engineList[i]);
11159         engineList[i] = strdup(buf);
11160         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11161         if(engineMnemonic[i]) free(engineMnemonic[i]);
11162         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11163             strcat(buf, " (");
11164             sscanf(q + 8, "%s", buf + strlen(buf));
11165             strcat(buf, ")");
11166         }
11167         engineMnemonic[i] = strdup(buf);
11168         i++;
11169     }
11170     engineList[i] = engineMnemonic[i] = NULL;
11171     return i;
11172 }
11173
11174 // following implemented as macro to avoid type limitations
11175 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11176
11177 void
11178 SwapEngines (int n)
11179 {   // swap settings for first engine and other engine (so far only some selected options)
11180     int h;
11181     char *p;
11182     if(n == 0) return;
11183     SWAP(directory, p)
11184     SWAP(chessProgram, p)
11185     SWAP(isUCI, h)
11186     SWAP(hasOwnBookUCI, h)
11187     SWAP(protocolVersion, h)
11188     SWAP(reuse, h)
11189     SWAP(scoreIsAbsolute, h)
11190     SWAP(timeOdds, h)
11191     SWAP(logo, p)
11192     SWAP(pgnName, p)
11193     SWAP(pvSAN, h)
11194     SWAP(engOptions, p)
11195     SWAP(engInitString, p)
11196     SWAP(computerString, p)
11197     SWAP(features, p)
11198     SWAP(fenOverride, p)
11199     SWAP(NPS, h)
11200     SWAP(accumulateTC, h)
11201     SWAP(drawDepth, h)
11202     SWAP(host, p)
11203     SWAP(pseudo, h)
11204 }
11205
11206 int
11207 GetEngineLine (char *s, int n)
11208 {
11209     int i;
11210     char buf[MSG_SIZ];
11211     extern char *icsNames;
11212     if(!s || !*s) return 0;
11213     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11214     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11215     if(!mnemonic[i]) return 0;
11216     if(n == 11) return 1; // just testing if there was a match
11217     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11218     if(n == 1) SwapEngines(n);
11219     ParseArgsFromString(buf);
11220     if(n == 1) SwapEngines(n);
11221     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11222         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11223         ParseArgsFromString(buf);
11224     }
11225     return 1;
11226 }
11227
11228 int
11229 SetPlayer (int player, char *p)
11230 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11231     int i;
11232     char buf[MSG_SIZ], *engineName;
11233     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11234     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11235     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11236     if(mnemonic[i]) {
11237         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11238         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11239         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11240         ParseArgsFromString(buf);
11241     } else { // no engine with this nickname is installed!
11242         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11243         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11244         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11245         ModeHighlight();
11246         DisplayError(buf, 0);
11247         return 0;
11248     }
11249     free(engineName);
11250     return i;
11251 }
11252
11253 char *recentEngines;
11254
11255 void
11256 RecentEngineEvent (int nr)
11257 {
11258     int n;
11259 //    SwapEngines(1); // bump first to second
11260 //    ReplaceEngine(&second, 1); // and load it there
11261     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11262     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11263     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11264         ReplaceEngine(&first, 0);
11265         FloatToFront(&appData.recentEngineList, command[n]);
11266     }
11267 }
11268
11269 int
11270 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11271 {   // determine players from game number
11272     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11273
11274     if(appData.tourneyType == 0) {
11275         roundsPerCycle = (nPlayers - 1) | 1;
11276         pairingsPerRound = nPlayers / 2;
11277     } else if(appData.tourneyType > 0) {
11278         roundsPerCycle = nPlayers - appData.tourneyType;
11279         pairingsPerRound = appData.tourneyType;
11280     }
11281     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11282     gamesPerCycle = gamesPerRound * roundsPerCycle;
11283     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11284     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11285     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11286     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11287     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11288     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11289
11290     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11291     if(appData.roundSync) *syncInterval = gamesPerRound;
11292
11293     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11294
11295     if(appData.tourneyType == 0) {
11296         if(curPairing == (nPlayers-1)/2 ) {
11297             *whitePlayer = curRound;
11298             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11299         } else {
11300             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11301             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11302             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11303             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11304         }
11305     } else if(appData.tourneyType > 1) {
11306         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11307         *whitePlayer = curRound + appData.tourneyType;
11308     } else if(appData.tourneyType > 0) {
11309         *whitePlayer = curPairing;
11310         *blackPlayer = curRound + appData.tourneyType;
11311     }
11312
11313     // take care of white/black alternation per round.
11314     // For cycles and games this is already taken care of by default, derived from matchGame!
11315     return curRound & 1;
11316 }
11317
11318 int
11319 NextTourneyGame (int nr, int *swapColors)
11320 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11321     char *p, *q;
11322     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11323     FILE *tf;
11324     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11325     tf = fopen(appData.tourneyFile, "r");
11326     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11327     ParseArgsFromFile(tf); fclose(tf);
11328     InitTimeControls(); // TC might be altered from tourney file
11329
11330     nPlayers = CountPlayers(appData.participants); // count participants
11331     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11332     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11333
11334     if(syncInterval) {
11335         p = q = appData.results;
11336         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11337         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11338             DisplayMessage(_("Waiting for other game(s)"),"");
11339             waitingForGame = TRUE;
11340             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11341             return 0;
11342         }
11343         waitingForGame = FALSE;
11344     }
11345
11346     if(appData.tourneyType < 0) {
11347         if(nr>=0 && !pairingReceived) {
11348             char buf[1<<16];
11349             if(pairing.pr == NoProc) {
11350                 if(!appData.pairingEngine[0]) {
11351                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11352                     return 0;
11353                 }
11354                 StartChessProgram(&pairing); // starts the pairing engine
11355             }
11356             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11357             SendToProgram(buf, &pairing);
11358             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11359             SendToProgram(buf, &pairing);
11360             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11361         }
11362         pairingReceived = 0;                              // ... so we continue here
11363         *swapColors = 0;
11364         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11365         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11366         matchGame = 1; roundNr = nr / syncInterval + 1;
11367     }
11368
11369     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11370
11371     // redefine engines, engine dir, etc.
11372     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11373     if(first.pr == NoProc) {
11374       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11375       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11376     }
11377     if(second.pr == NoProc) {
11378       SwapEngines(1);
11379       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11380       SwapEngines(1);         // and make that valid for second engine by swapping
11381       InitEngine(&second, 1);
11382     }
11383     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11384     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11385     return OK;
11386 }
11387
11388 void
11389 NextMatchGame ()
11390 {   // performs game initialization that does not invoke engines, and then tries to start the game
11391     int res, firstWhite, swapColors = 0;
11392     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11393     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
11394         char buf[MSG_SIZ];
11395         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11396         if(strcmp(buf, currentDebugFile)) { // name has changed
11397             FILE *f = fopen(buf, "w");
11398             if(f) { // if opening the new file failed, just keep using the old one
11399                 ASSIGN(currentDebugFile, buf);
11400                 fclose(debugFP);
11401                 debugFP = f;
11402             }
11403             if(appData.serverFileName) {
11404                 if(serverFP) fclose(serverFP);
11405                 serverFP = fopen(appData.serverFileName, "w");
11406                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11407                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11408             }
11409         }
11410     }
11411     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11412     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11413     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11414     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11415     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11416     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11417     Reset(FALSE, first.pr != NoProc);
11418     res = LoadGameOrPosition(matchGame); // setup game
11419     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11420     if(!res) return; // abort when bad game/pos file
11421     TwoMachinesEvent();
11422 }
11423
11424 void
11425 UserAdjudicationEvent (int result)
11426 {
11427     ChessMove gameResult = GameIsDrawn;
11428
11429     if( result > 0 ) {
11430         gameResult = WhiteWins;
11431     }
11432     else if( result < 0 ) {
11433         gameResult = BlackWins;
11434     }
11435
11436     if( gameMode == TwoMachinesPlay ) {
11437         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11438     }
11439 }
11440
11441
11442 // [HGM] save: calculate checksum of game to make games easily identifiable
11443 int
11444 StringCheckSum (char *s)
11445 {
11446         int i = 0;
11447         if(s==NULL) return 0;
11448         while(*s) i = i*259 + *s++;
11449         return i;
11450 }
11451
11452 int
11453 GameCheckSum ()
11454 {
11455         int i, sum=0;
11456         for(i=backwardMostMove; i<forwardMostMove; i++) {
11457                 sum += pvInfoList[i].depth;
11458                 sum += StringCheckSum(parseList[i]);
11459                 sum += StringCheckSum(commentList[i]);
11460                 sum *= 261;
11461         }
11462         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11463         return sum + StringCheckSum(commentList[i]);
11464 } // end of save patch
11465
11466 void
11467 GameEnds (ChessMove result, char *resultDetails, int whosays)
11468 {
11469     GameMode nextGameMode;
11470     int isIcsGame;
11471     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11472
11473     if(endingGame) return; /* [HGM] crash: forbid recursion */
11474     endingGame = 1;
11475     if(twoBoards) { // [HGM] dual: switch back to one board
11476         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11477         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11478     }
11479     if (appData.debugMode) {
11480       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11481               result, resultDetails ? resultDetails : "(null)", whosays);
11482     }
11483
11484     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11485
11486     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11487
11488     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11489         /* If we are playing on ICS, the server decides when the
11490            game is over, but the engine can offer to draw, claim
11491            a draw, or resign.
11492          */
11493 #if ZIPPY
11494         if (appData.zippyPlay && first.initDone) {
11495             if (result == GameIsDrawn) {
11496                 /* In case draw still needs to be claimed */
11497                 SendToICS(ics_prefix);
11498                 SendToICS("draw\n");
11499             } else if (StrCaseStr(resultDetails, "resign")) {
11500                 SendToICS(ics_prefix);
11501                 SendToICS("resign\n");
11502             }
11503         }
11504 #endif
11505         endingGame = 0; /* [HGM] crash */
11506         return;
11507     }
11508
11509     /* If we're loading the game from a file, stop */
11510     if (whosays == GE_FILE) {
11511       (void) StopLoadGameTimer();
11512       gameFileFP = NULL;
11513     }
11514
11515     /* Cancel draw offers */
11516     first.offeredDraw = second.offeredDraw = 0;
11517
11518     /* If this is an ICS game, only ICS can really say it's done;
11519        if not, anyone can. */
11520     isIcsGame = (gameMode == IcsPlayingWhite ||
11521                  gameMode == IcsPlayingBlack ||
11522                  gameMode == IcsObserving    ||
11523                  gameMode == IcsExamining);
11524
11525     if (!isIcsGame || whosays == GE_ICS) {
11526         /* OK -- not an ICS game, or ICS said it was done */
11527         StopClocks();
11528         if (!isIcsGame && !appData.noChessProgram)
11529           SetUserThinkingEnables();
11530
11531         /* [HGM] if a machine claims the game end we verify this claim */
11532         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11533             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11534                 char claimer;
11535                 ChessMove trueResult = (ChessMove) -1;
11536
11537                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11538                                             first.twoMachinesColor[0] :
11539                                             second.twoMachinesColor[0] ;
11540
11541                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11542                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11543                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11544                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11545                 } else
11546                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11547                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11548                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11549                 } else
11550                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11551                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11552                 }
11553
11554                 // now verify win claims, but not in drop games, as we don't understand those yet
11555                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11556                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11557                     (result == WhiteWins && claimer == 'w' ||
11558                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11559                       if (appData.debugMode) {
11560                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11561                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11562                       }
11563                       if(result != trueResult) {
11564                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11565                               result = claimer == 'w' ? BlackWins : WhiteWins;
11566                               resultDetails = buf;
11567                       }
11568                 } else
11569                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11570                     && (forwardMostMove <= backwardMostMove ||
11571                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11572                         (claimer=='b')==(forwardMostMove&1))
11573                                                                                   ) {
11574                       /* [HGM] verify: draws that were not flagged are false claims */
11575                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11576                       result = claimer == 'w' ? BlackWins : WhiteWins;
11577                       resultDetails = buf;
11578                 }
11579                 /* (Claiming a loss is accepted no questions asked!) */
11580             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11581                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11582                 result = GameUnfinished;
11583                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11584             }
11585             /* [HGM] bare: don't allow bare King to win */
11586             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11587                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11588                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11589                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11590                && result != GameIsDrawn)
11591             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11592                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11593                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11594                         if(p >= 0 && p <= (int)WhiteKing) k++;
11595                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11596                 }
11597                 if (appData.debugMode) {
11598                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11599                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11600                 }
11601                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11602                         result = GameIsDrawn;
11603                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11604                         resultDetails = buf;
11605                 }
11606             }
11607         }
11608
11609
11610         if(serverMoves != NULL && !loadFlag) { char c = '=';
11611             if(result==WhiteWins) c = '+';
11612             if(result==BlackWins) c = '-';
11613             if(resultDetails != NULL)
11614                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11615         }
11616         if (resultDetails != NULL) {
11617             gameInfo.result = result;
11618             gameInfo.resultDetails = StrSave(resultDetails);
11619
11620             /* display last move only if game was not loaded from file */
11621             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11622                 DisplayMove(currentMove - 1);
11623
11624             if (forwardMostMove != 0) {
11625                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11626                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11627                                                                 ) {
11628                     if (*appData.saveGameFile != NULLCHAR) {
11629                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11630                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11631                         else
11632                         SaveGameToFile(appData.saveGameFile, TRUE);
11633                     } else if (appData.autoSaveGames) {
11634                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11635                     }
11636                     if (*appData.savePositionFile != NULLCHAR) {
11637                         SavePositionToFile(appData.savePositionFile);
11638                     }
11639                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11640                 }
11641             }
11642
11643             /* Tell program how game ended in case it is learning */
11644             /* [HGM] Moved this to after saving the PGN, just in case */
11645             /* engine died and we got here through time loss. In that */
11646             /* case we will get a fatal error writing the pipe, which */
11647             /* would otherwise lose us the PGN.                       */
11648             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11649             /* output during GameEnds should never be fatal anymore   */
11650             if (gameMode == MachinePlaysWhite ||
11651                 gameMode == MachinePlaysBlack ||
11652                 gameMode == TwoMachinesPlay ||
11653                 gameMode == IcsPlayingWhite ||
11654                 gameMode == IcsPlayingBlack ||
11655                 gameMode == BeginningOfGame) {
11656                 char buf[MSG_SIZ];
11657                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11658                         resultDetails);
11659                 if (first.pr != NoProc) {
11660                     SendToProgram(buf, &first);
11661                 }
11662                 if (second.pr != NoProc &&
11663                     gameMode == TwoMachinesPlay) {
11664                     SendToProgram(buf, &second);
11665                 }
11666             }
11667         }
11668
11669         if (appData.icsActive) {
11670             if (appData.quietPlay &&
11671                 (gameMode == IcsPlayingWhite ||
11672                  gameMode == IcsPlayingBlack)) {
11673                 SendToICS(ics_prefix);
11674                 SendToICS("set shout 1\n");
11675             }
11676             nextGameMode = IcsIdle;
11677             ics_user_moved = FALSE;
11678             /* clean up premove.  It's ugly when the game has ended and the
11679              * premove highlights are still on the board.
11680              */
11681             if (gotPremove) {
11682               gotPremove = FALSE;
11683               ClearPremoveHighlights();
11684               DrawPosition(FALSE, boards[currentMove]);
11685             }
11686             if (whosays == GE_ICS) {
11687                 switch (result) {
11688                 case WhiteWins:
11689                     if (gameMode == IcsPlayingWhite)
11690                         PlayIcsWinSound();
11691                     else if(gameMode == IcsPlayingBlack)
11692                         PlayIcsLossSound();
11693                     break;
11694                 case BlackWins:
11695                     if (gameMode == IcsPlayingBlack)
11696                         PlayIcsWinSound();
11697                     else if(gameMode == IcsPlayingWhite)
11698                         PlayIcsLossSound();
11699                     break;
11700                 case GameIsDrawn:
11701                     PlayIcsDrawSound();
11702                     break;
11703                 default:
11704                     PlayIcsUnfinishedSound();
11705                 }
11706             }
11707             if(appData.quitNext) { ExitEvent(0); return; }
11708         } else if (gameMode == EditGame ||
11709                    gameMode == PlayFromGameFile ||
11710                    gameMode == AnalyzeMode ||
11711                    gameMode == AnalyzeFile) {
11712             nextGameMode = gameMode;
11713         } else {
11714             nextGameMode = EndOfGame;
11715         }
11716         pausing = FALSE;
11717         ModeHighlight();
11718     } else {
11719         nextGameMode = gameMode;
11720     }
11721
11722     if (appData.noChessProgram) {
11723         gameMode = nextGameMode;
11724         ModeHighlight();
11725         endingGame = 0; /* [HGM] crash */
11726         return;
11727     }
11728
11729     if (first.reuse) {
11730         /* Put first chess program into idle state */
11731         if (first.pr != NoProc &&
11732             (gameMode == MachinePlaysWhite ||
11733              gameMode == MachinePlaysBlack ||
11734              gameMode == TwoMachinesPlay ||
11735              gameMode == IcsPlayingWhite ||
11736              gameMode == IcsPlayingBlack ||
11737              gameMode == BeginningOfGame)) {
11738             SendToProgram("force\n", &first);
11739             if (first.usePing) {
11740               char buf[MSG_SIZ];
11741               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11742               SendToProgram(buf, &first);
11743             }
11744         }
11745     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11746         /* Kill off first chess program */
11747         if (first.isr != NULL)
11748           RemoveInputSource(first.isr);
11749         first.isr = NULL;
11750
11751         if (first.pr != NoProc) {
11752             ExitAnalyzeMode();
11753             DoSleep( appData.delayBeforeQuit );
11754             SendToProgram("quit\n", &first);
11755             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11756             first.reload = TRUE;
11757         }
11758         first.pr = NoProc;
11759     }
11760     if (second.reuse) {
11761         /* Put second chess program into idle state */
11762         if (second.pr != NoProc &&
11763             gameMode == TwoMachinesPlay) {
11764             SendToProgram("force\n", &second);
11765             if (second.usePing) {
11766               char buf[MSG_SIZ];
11767               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11768               SendToProgram(buf, &second);
11769             }
11770         }
11771     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11772         /* Kill off second chess program */
11773         if (second.isr != NULL)
11774           RemoveInputSource(second.isr);
11775         second.isr = NULL;
11776
11777         if (second.pr != NoProc) {
11778             DoSleep( appData.delayBeforeQuit );
11779             SendToProgram("quit\n", &second);
11780             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11781             second.reload = TRUE;
11782         }
11783         second.pr = NoProc;
11784     }
11785
11786     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11787         char resChar = '=';
11788         switch (result) {
11789         case WhiteWins:
11790           resChar = '+';
11791           if (first.twoMachinesColor[0] == 'w') {
11792             first.matchWins++;
11793           } else {
11794             second.matchWins++;
11795           }
11796           break;
11797         case BlackWins:
11798           resChar = '-';
11799           if (first.twoMachinesColor[0] == 'b') {
11800             first.matchWins++;
11801           } else {
11802             second.matchWins++;
11803           }
11804           break;
11805         case GameUnfinished:
11806           resChar = ' ';
11807         default:
11808           break;
11809         }
11810
11811         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11812         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11813             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11814             ReserveGame(nextGame, resChar); // sets nextGame
11815             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11816             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11817         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11818
11819         if (nextGame <= appData.matchGames && !abortMatch) {
11820             gameMode = nextGameMode;
11821             matchGame = nextGame; // this will be overruled in tourney mode!
11822             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11823             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11824             endingGame = 0; /* [HGM] crash */
11825             return;
11826         } else {
11827             gameMode = nextGameMode;
11828             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11829                      first.tidy, second.tidy,
11830                      first.matchWins, second.matchWins,
11831                      appData.matchGames - (first.matchWins + second.matchWins));
11832             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11833             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11834             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11835             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11836                 first.twoMachinesColor = "black\n";
11837                 second.twoMachinesColor = "white\n";
11838             } else {
11839                 first.twoMachinesColor = "white\n";
11840                 second.twoMachinesColor = "black\n";
11841             }
11842         }
11843     }
11844     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11845         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11846       ExitAnalyzeMode();
11847     gameMode = nextGameMode;
11848     ModeHighlight();
11849     endingGame = 0;  /* [HGM] crash */
11850     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11851         if(matchMode == TRUE) { // match through command line: exit with or without popup
11852             if(ranking) {
11853                 ToNrEvent(forwardMostMove);
11854                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11855                 else ExitEvent(0);
11856             } else DisplayFatalError(buf, 0, 0);
11857         } else { // match through menu; just stop, with or without popup
11858             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11859             ModeHighlight();
11860             if(ranking){
11861                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11862             } else DisplayNote(buf);
11863       }
11864       if(ranking) free(ranking);
11865     }
11866 }
11867
11868 /* Assumes program was just initialized (initString sent).
11869    Leaves program in force mode. */
11870 void
11871 FeedMovesToProgram (ChessProgramState *cps, int upto)
11872 {
11873     int i;
11874
11875     if (appData.debugMode)
11876       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11877               startedFromSetupPosition ? "position and " : "",
11878               backwardMostMove, upto, cps->which);
11879     if(currentlyInitializedVariant != gameInfo.variant) {
11880       char buf[MSG_SIZ];
11881         // [HGM] variantswitch: make engine aware of new variant
11882         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11883                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11884                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11885         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11886         SendToProgram(buf, cps);
11887         currentlyInitializedVariant = gameInfo.variant;
11888     }
11889     SendToProgram("force\n", cps);
11890     if (startedFromSetupPosition) {
11891         SendBoard(cps, backwardMostMove);
11892     if (appData.debugMode) {
11893         fprintf(debugFP, "feedMoves\n");
11894     }
11895     }
11896     for (i = backwardMostMove; i < upto; i++) {
11897         SendMoveToProgram(i, cps);
11898     }
11899 }
11900
11901
11902 int
11903 ResurrectChessProgram ()
11904 {
11905      /* The chess program may have exited.
11906         If so, restart it and feed it all the moves made so far. */
11907     static int doInit = 0;
11908
11909     if (appData.noChessProgram) return 1;
11910
11911     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11912         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11913         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11914         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11915     } else {
11916         if (first.pr != NoProc) return 1;
11917         StartChessProgram(&first);
11918     }
11919     InitChessProgram(&first, FALSE);
11920     FeedMovesToProgram(&first, currentMove);
11921
11922     if (!first.sendTime) {
11923         /* can't tell gnuchess what its clock should read,
11924            so we bow to its notion. */
11925         ResetClocks();
11926         timeRemaining[0][currentMove] = whiteTimeRemaining;
11927         timeRemaining[1][currentMove] = blackTimeRemaining;
11928     }
11929
11930     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11931                 appData.icsEngineAnalyze) && first.analysisSupport) {
11932       SendToProgram("analyze\n", &first);
11933       first.analyzing = TRUE;
11934     }
11935     return 1;
11936 }
11937
11938 /*
11939  * Button procedures
11940  */
11941 void
11942 Reset (int redraw, int init)
11943 {
11944     int i;
11945
11946     if (appData.debugMode) {
11947         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11948                 redraw, init, gameMode);
11949     }
11950     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11951     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11952     CleanupTail(); // [HGM] vari: delete any stored variations
11953     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11954     pausing = pauseExamInvalid = FALSE;
11955     startedFromSetupPosition = blackPlaysFirst = FALSE;
11956     firstMove = TRUE;
11957     whiteFlag = blackFlag = FALSE;
11958     userOfferedDraw = FALSE;
11959     hintRequested = bookRequested = FALSE;
11960     first.maybeThinking = FALSE;
11961     second.maybeThinking = FALSE;
11962     first.bookSuspend = FALSE; // [HGM] book
11963     second.bookSuspend = FALSE;
11964     thinkOutput[0] = NULLCHAR;
11965     lastHint[0] = NULLCHAR;
11966     ClearGameInfo(&gameInfo);
11967     gameInfo.variant = StringToVariant(appData.variant);
11968     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11969     ics_user_moved = ics_clock_paused = FALSE;
11970     ics_getting_history = H_FALSE;
11971     ics_gamenum = -1;
11972     white_holding[0] = black_holding[0] = NULLCHAR;
11973     ClearProgramStats();
11974     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11975
11976     ResetFrontEnd();
11977     ClearHighlights();
11978     flipView = appData.flipView;
11979     ClearPremoveHighlights();
11980     gotPremove = FALSE;
11981     alarmSounded = FALSE;
11982     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
11983
11984     GameEnds(EndOfFile, NULL, GE_PLAYER);
11985     if(appData.serverMovesName != NULL) {
11986         /* [HGM] prepare to make moves file for broadcasting */
11987         clock_t t = clock();
11988         if(serverMoves != NULL) fclose(serverMoves);
11989         serverMoves = fopen(appData.serverMovesName, "r");
11990         if(serverMoves != NULL) {
11991             fclose(serverMoves);
11992             /* delay 15 sec before overwriting, so all clients can see end */
11993             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11994         }
11995         serverMoves = fopen(appData.serverMovesName, "w");
11996     }
11997
11998     ExitAnalyzeMode();
11999     gameMode = BeginningOfGame;
12000     ModeHighlight();
12001     if(appData.icsActive) gameInfo.variant = VariantNormal;
12002     currentMove = forwardMostMove = backwardMostMove = 0;
12003     MarkTargetSquares(1);
12004     InitPosition(redraw);
12005     for (i = 0; i < MAX_MOVES; i++) {
12006         if (commentList[i] != NULL) {
12007             free(commentList[i]);
12008             commentList[i] = NULL;
12009         }
12010     }
12011     ResetClocks();
12012     timeRemaining[0][0] = whiteTimeRemaining;
12013     timeRemaining[1][0] = blackTimeRemaining;
12014
12015     if (first.pr == NoProc) {
12016         StartChessProgram(&first);
12017     }
12018     if (init) {
12019             InitChessProgram(&first, startedFromSetupPosition);
12020     }
12021     DisplayTitle("");
12022     DisplayMessage("", "");
12023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12024     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12025     ClearMap();        // [HGM] exclude: invalidate map
12026 }
12027
12028 void
12029 AutoPlayGameLoop ()
12030 {
12031     for (;;) {
12032         if (!AutoPlayOneMove())
12033           return;
12034         if (matchMode || appData.timeDelay == 0)
12035           continue;
12036         if (appData.timeDelay < 0)
12037           return;
12038         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12039         break;
12040     }
12041 }
12042
12043 void
12044 AnalyzeNextGame()
12045 {
12046     ReloadGame(1); // next game
12047 }
12048
12049 int
12050 AutoPlayOneMove ()
12051 {
12052     int fromX, fromY, toX, toY;
12053
12054     if (appData.debugMode) {
12055       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12056     }
12057
12058     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12059       return FALSE;
12060
12061     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12062       pvInfoList[currentMove].depth = programStats.depth;
12063       pvInfoList[currentMove].score = programStats.score;
12064       pvInfoList[currentMove].time  = 0;
12065       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12066       else { // append analysis of final position as comment
12067         char buf[MSG_SIZ];
12068         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12069         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12070       }
12071       programStats.depth = 0;
12072     }
12073
12074     if (currentMove >= forwardMostMove) {
12075       if(gameMode == AnalyzeFile) {
12076           if(appData.loadGameIndex == -1) {
12077             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12078           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12079           } else {
12080           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12081         }
12082       }
12083 //      gameMode = EndOfGame;
12084 //      ModeHighlight();
12085
12086       /* [AS] Clear current move marker at the end of a game */
12087       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12088
12089       return FALSE;
12090     }
12091
12092     toX = moveList[currentMove][2] - AAA;
12093     toY = moveList[currentMove][3] - ONE;
12094
12095     if (moveList[currentMove][1] == '@') {
12096         if (appData.highlightLastMove) {
12097             SetHighlights(-1, -1, toX, toY);
12098         }
12099     } else {
12100         int viaX = moveList[currentMove][5] - AAA;
12101         int viaY = moveList[currentMove][6] - ONE;
12102         fromX = moveList[currentMove][0] - AAA;
12103         fromY = moveList[currentMove][1] - ONE;
12104
12105         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12106
12107         if(moveList[currentMove][4] == ';') { // multi-leg
12108             ChessSquare piece = boards[currentMove][viaY][viaX];
12109             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12110             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12111             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12112             boards[currentMove][viaY][viaX] = piece;
12113         } else
12114         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12115
12116         if (appData.highlightLastMove) {
12117             SetHighlights(fromX, fromY, toX, toY);
12118         }
12119     }
12120     DisplayMove(currentMove);
12121     SendMoveToProgram(currentMove++, &first);
12122     DisplayBothClocks();
12123     DrawPosition(FALSE, boards[currentMove]);
12124     // [HGM] PV info: always display, routine tests if empty
12125     DisplayComment(currentMove - 1, commentList[currentMove]);
12126     return TRUE;
12127 }
12128
12129
12130 int
12131 LoadGameOneMove (ChessMove readAhead)
12132 {
12133     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12134     char promoChar = NULLCHAR;
12135     ChessMove moveType;
12136     char move[MSG_SIZ];
12137     char *p, *q;
12138
12139     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12140         gameMode != AnalyzeMode && gameMode != Training) {
12141         gameFileFP = NULL;
12142         return FALSE;
12143     }
12144
12145     yyboardindex = forwardMostMove;
12146     if (readAhead != EndOfFile) {
12147       moveType = readAhead;
12148     } else {
12149       if (gameFileFP == NULL)
12150           return FALSE;
12151       moveType = (ChessMove) Myylex();
12152     }
12153
12154     done = FALSE;
12155     switch (moveType) {
12156       case Comment:
12157         if (appData.debugMode)
12158           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12159         p = yy_text;
12160
12161         /* append the comment but don't display it */
12162         AppendComment(currentMove, p, FALSE);
12163         return TRUE;
12164
12165       case WhiteCapturesEnPassant:
12166       case BlackCapturesEnPassant:
12167       case WhitePromotion:
12168       case BlackPromotion:
12169       case WhiteNonPromotion:
12170       case BlackNonPromotion:
12171       case NormalMove:
12172       case FirstLeg:
12173       case WhiteKingSideCastle:
12174       case WhiteQueenSideCastle:
12175       case BlackKingSideCastle:
12176       case BlackQueenSideCastle:
12177       case WhiteKingSideCastleWild:
12178       case WhiteQueenSideCastleWild:
12179       case BlackKingSideCastleWild:
12180       case BlackQueenSideCastleWild:
12181       /* PUSH Fabien */
12182       case WhiteHSideCastleFR:
12183       case WhiteASideCastleFR:
12184       case BlackHSideCastleFR:
12185       case BlackASideCastleFR:
12186       /* POP Fabien */
12187         if (appData.debugMode)
12188           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12189         fromX = currentMoveString[0] - AAA;
12190         fromY = currentMoveString[1] - ONE;
12191         toX = currentMoveString[2] - AAA;
12192         toY = currentMoveString[3] - ONE;
12193         promoChar = currentMoveString[4];
12194         if(promoChar == ';') promoChar = currentMoveString[7];
12195         break;
12196
12197       case WhiteDrop:
12198       case BlackDrop:
12199         if (appData.debugMode)
12200           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12201         fromX = moveType == WhiteDrop ?
12202           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12203         (int) CharToPiece(ToLower(currentMoveString[0]));
12204         fromY = DROP_RANK;
12205         toX = currentMoveString[2] - AAA;
12206         toY = currentMoveString[3] - ONE;
12207         break;
12208
12209       case WhiteWins:
12210       case BlackWins:
12211       case GameIsDrawn:
12212       case GameUnfinished:
12213         if (appData.debugMode)
12214           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12215         p = strchr(yy_text, '{');
12216         if (p == NULL) p = strchr(yy_text, '(');
12217         if (p == NULL) {
12218             p = yy_text;
12219             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12220         } else {
12221             q = strchr(p, *p == '{' ? '}' : ')');
12222             if (q != NULL) *q = NULLCHAR;
12223             p++;
12224         }
12225         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12226         GameEnds(moveType, p, GE_FILE);
12227         done = TRUE;
12228         if (cmailMsgLoaded) {
12229             ClearHighlights();
12230             flipView = WhiteOnMove(currentMove);
12231             if (moveType == GameUnfinished) flipView = !flipView;
12232             if (appData.debugMode)
12233               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12234         }
12235         break;
12236
12237       case EndOfFile:
12238         if (appData.debugMode)
12239           fprintf(debugFP, "Parser hit end of file\n");
12240         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12241           case MT_NONE:
12242           case MT_CHECK:
12243             break;
12244           case MT_CHECKMATE:
12245           case MT_STAINMATE:
12246             if (WhiteOnMove(currentMove)) {
12247                 GameEnds(BlackWins, "Black mates", GE_FILE);
12248             } else {
12249                 GameEnds(WhiteWins, "White mates", GE_FILE);
12250             }
12251             break;
12252           case MT_STALEMATE:
12253             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12254             break;
12255         }
12256         done = TRUE;
12257         break;
12258
12259       case MoveNumberOne:
12260         if (lastLoadGameStart == GNUChessGame) {
12261             /* GNUChessGames have numbers, but they aren't move numbers */
12262             if (appData.debugMode)
12263               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12264                       yy_text, (int) moveType);
12265             return LoadGameOneMove(EndOfFile); /* tail recursion */
12266         }
12267         /* else fall thru */
12268
12269       case XBoardGame:
12270       case GNUChessGame:
12271       case PGNTag:
12272         /* Reached start of next game in file */
12273         if (appData.debugMode)
12274           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12275         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12276           case MT_NONE:
12277           case MT_CHECK:
12278             break;
12279           case MT_CHECKMATE:
12280           case MT_STAINMATE:
12281             if (WhiteOnMove(currentMove)) {
12282                 GameEnds(BlackWins, "Black mates", GE_FILE);
12283             } else {
12284                 GameEnds(WhiteWins, "White mates", GE_FILE);
12285             }
12286             break;
12287           case MT_STALEMATE:
12288             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12289             break;
12290         }
12291         done = TRUE;
12292         break;
12293
12294       case PositionDiagram:     /* should not happen; ignore */
12295       case ElapsedTime:         /* ignore */
12296       case NAG:                 /* ignore */
12297         if (appData.debugMode)
12298           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12299                   yy_text, (int) moveType);
12300         return LoadGameOneMove(EndOfFile); /* tail recursion */
12301
12302       case IllegalMove:
12303         if (appData.testLegality) {
12304             if (appData.debugMode)
12305               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12306             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12307                     (forwardMostMove / 2) + 1,
12308                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12309             DisplayError(move, 0);
12310             done = TRUE;
12311         } else {
12312             if (appData.debugMode)
12313               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12314                       yy_text, currentMoveString);
12315             if(currentMoveString[1] == '@') {
12316                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12317                 fromY = DROP_RANK;
12318             } else {
12319                 fromX = currentMoveString[0] - AAA;
12320                 fromY = currentMoveString[1] - ONE;
12321             }
12322             toX = currentMoveString[2] - AAA;
12323             toY = currentMoveString[3] - ONE;
12324             promoChar = currentMoveString[4];
12325         }
12326         break;
12327
12328       case AmbiguousMove:
12329         if (appData.debugMode)
12330           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12331         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12332                 (forwardMostMove / 2) + 1,
12333                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12334         DisplayError(move, 0);
12335         done = TRUE;
12336         break;
12337
12338       default:
12339       case ImpossibleMove:
12340         if (appData.debugMode)
12341           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12342         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12343                 (forwardMostMove / 2) + 1,
12344                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12345         DisplayError(move, 0);
12346         done = TRUE;
12347         break;
12348     }
12349
12350     if (done) {
12351         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12352             DrawPosition(FALSE, boards[currentMove]);
12353             DisplayBothClocks();
12354             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12355               DisplayComment(currentMove - 1, commentList[currentMove]);
12356         }
12357         (void) StopLoadGameTimer();
12358         gameFileFP = NULL;
12359         cmailOldMove = forwardMostMove;
12360         return FALSE;
12361     } else {
12362         /* currentMoveString is set as a side-effect of yylex */
12363
12364         thinkOutput[0] = NULLCHAR;
12365         MakeMove(fromX, fromY, toX, toY, promoChar);
12366         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12367         currentMove = forwardMostMove;
12368         return TRUE;
12369     }
12370 }
12371
12372 /* Load the nth game from the given file */
12373 int
12374 LoadGameFromFile (char *filename, int n, char *title, int useList)
12375 {
12376     FILE *f;
12377     char buf[MSG_SIZ];
12378
12379     if (strcmp(filename, "-") == 0) {
12380         f = stdin;
12381         title = "stdin";
12382     } else {
12383         f = fopen(filename, "rb");
12384         if (f == NULL) {
12385           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12386             DisplayError(buf, errno);
12387             return FALSE;
12388         }
12389     }
12390     if (fseek(f, 0, 0) == -1) {
12391         /* f is not seekable; probably a pipe */
12392         useList = FALSE;
12393     }
12394     if (useList && n == 0) {
12395         int error = GameListBuild(f);
12396         if (error) {
12397             DisplayError(_("Cannot build game list"), error);
12398         } else if (!ListEmpty(&gameList) &&
12399                    ((ListGame *) gameList.tailPred)->number > 1) {
12400             GameListPopUp(f, title);
12401             return TRUE;
12402         }
12403         GameListDestroy();
12404         n = 1;
12405     }
12406     if (n == 0) n = 1;
12407     return LoadGame(f, n, title, FALSE);
12408 }
12409
12410
12411 void
12412 MakeRegisteredMove ()
12413 {
12414     int fromX, fromY, toX, toY;
12415     char promoChar;
12416     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12417         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12418           case CMAIL_MOVE:
12419           case CMAIL_DRAW:
12420             if (appData.debugMode)
12421               fprintf(debugFP, "Restoring %s for game %d\n",
12422                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12423
12424             thinkOutput[0] = NULLCHAR;
12425             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12426             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12427             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12428             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12429             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12430             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12431             MakeMove(fromX, fromY, toX, toY, promoChar);
12432             ShowMove(fromX, fromY, toX, toY);
12433
12434             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12435               case MT_NONE:
12436               case MT_CHECK:
12437                 break;
12438
12439               case MT_CHECKMATE:
12440               case MT_STAINMATE:
12441                 if (WhiteOnMove(currentMove)) {
12442                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12443                 } else {
12444                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12445                 }
12446                 break;
12447
12448               case MT_STALEMATE:
12449                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12450                 break;
12451             }
12452
12453             break;
12454
12455           case CMAIL_RESIGN:
12456             if (WhiteOnMove(currentMove)) {
12457                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12458             } else {
12459                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12460             }
12461             break;
12462
12463           case CMAIL_ACCEPT:
12464             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12465             break;
12466
12467           default:
12468             break;
12469         }
12470     }
12471
12472     return;
12473 }
12474
12475 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12476 int
12477 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12478 {
12479     int retVal;
12480
12481     if (gameNumber > nCmailGames) {
12482         DisplayError(_("No more games in this message"), 0);
12483         return FALSE;
12484     }
12485     if (f == lastLoadGameFP) {
12486         int offset = gameNumber - lastLoadGameNumber;
12487         if (offset == 0) {
12488             cmailMsg[0] = NULLCHAR;
12489             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12490                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12491                 nCmailMovesRegistered--;
12492             }
12493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12494             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12495                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12496             }
12497         } else {
12498             if (! RegisterMove()) return FALSE;
12499         }
12500     }
12501
12502     retVal = LoadGame(f, gameNumber, title, useList);
12503
12504     /* Make move registered during previous look at this game, if any */
12505     MakeRegisteredMove();
12506
12507     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12508         commentList[currentMove]
12509           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12510         DisplayComment(currentMove - 1, commentList[currentMove]);
12511     }
12512
12513     return retVal;
12514 }
12515
12516 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12517 int
12518 ReloadGame (int offset)
12519 {
12520     int gameNumber = lastLoadGameNumber + offset;
12521     if (lastLoadGameFP == NULL) {
12522         DisplayError(_("No game has been loaded yet"), 0);
12523         return FALSE;
12524     }
12525     if (gameNumber <= 0) {
12526         DisplayError(_("Can't back up any further"), 0);
12527         return FALSE;
12528     }
12529     if (cmailMsgLoaded) {
12530         return CmailLoadGame(lastLoadGameFP, gameNumber,
12531                              lastLoadGameTitle, lastLoadGameUseList);
12532     } else {
12533         return LoadGame(lastLoadGameFP, gameNumber,
12534                         lastLoadGameTitle, lastLoadGameUseList);
12535     }
12536 }
12537
12538 int keys[EmptySquare+1];
12539
12540 int
12541 PositionMatches (Board b1, Board b2)
12542 {
12543     int r, f, sum=0;
12544     switch(appData.searchMode) {
12545         case 1: return CompareWithRights(b1, b2);
12546         case 2:
12547             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12548                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12549             }
12550             return TRUE;
12551         case 3:
12552             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12553               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12554                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12555             }
12556             return sum==0;
12557         case 4:
12558             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12559                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12560             }
12561             return sum==0;
12562     }
12563     return TRUE;
12564 }
12565
12566 #define Q_PROMO  4
12567 #define Q_EP     3
12568 #define Q_BCASTL 2
12569 #define Q_WCASTL 1
12570
12571 int pieceList[256], quickBoard[256];
12572 ChessSquare pieceType[256] = { EmptySquare };
12573 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12574 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12575 int soughtTotal, turn;
12576 Boolean epOK, flipSearch;
12577
12578 typedef struct {
12579     unsigned char piece, to;
12580 } Move;
12581
12582 #define DSIZE (250000)
12583
12584 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12585 Move *moveDatabase = initialSpace;
12586 unsigned int movePtr, dataSize = DSIZE;
12587
12588 int
12589 MakePieceList (Board board, int *counts)
12590 {
12591     int r, f, n=Q_PROMO, total=0;
12592     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12593     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12594         int sq = f + (r<<4);
12595         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12596             quickBoard[sq] = ++n;
12597             pieceList[n] = sq;
12598             pieceType[n] = board[r][f];
12599             counts[board[r][f]]++;
12600             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12601             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12602             total++;
12603         }
12604     }
12605     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12606     return total;
12607 }
12608
12609 void
12610 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12611 {
12612     int sq = fromX + (fromY<<4);
12613     int piece = quickBoard[sq], rook;
12614     quickBoard[sq] = 0;
12615     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12616     if(piece == pieceList[1] && fromY == toY) {
12617       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12618         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12619         moveDatabase[movePtr++].piece = Q_WCASTL;
12620         quickBoard[sq] = piece;
12621         piece = quickBoard[from]; quickBoard[from] = 0;
12622         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12623       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12624         quickBoard[sq] = 0; // remove Rook
12625         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12626         moveDatabase[movePtr++].piece = Q_WCASTL;
12627         quickBoard[sq] = pieceList[1]; // put King
12628         piece = rook;
12629         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12630       }
12631     } else
12632     if(piece == pieceList[2] && fromY == toY) {
12633       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12634         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12635         moveDatabase[movePtr++].piece = Q_BCASTL;
12636         quickBoard[sq] = piece;
12637         piece = quickBoard[from]; quickBoard[from] = 0;
12638         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12639       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12640         quickBoard[sq] = 0; // remove Rook
12641         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12642         moveDatabase[movePtr++].piece = Q_BCASTL;
12643         quickBoard[sq] = pieceList[2]; // put King
12644         piece = rook;
12645         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12646       }
12647     } else
12648     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12649         quickBoard[(fromY<<4)+toX] = 0;
12650         moveDatabase[movePtr].piece = Q_EP;
12651         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12652         moveDatabase[movePtr].to = sq;
12653     } else
12654     if(promoPiece != pieceType[piece]) {
12655         moveDatabase[movePtr++].piece = Q_PROMO;
12656         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12657     }
12658     moveDatabase[movePtr].piece = piece;
12659     quickBoard[sq] = piece;
12660     movePtr++;
12661 }
12662
12663 int
12664 PackGame (Board board)
12665 {
12666     Move *newSpace = NULL;
12667     moveDatabase[movePtr].piece = 0; // terminate previous game
12668     if(movePtr > dataSize) {
12669         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12670         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12671         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12672         if(newSpace) {
12673             int i;
12674             Move *p = moveDatabase, *q = newSpace;
12675             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12676             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12677             moveDatabase = newSpace;
12678         } else { // calloc failed, we must be out of memory. Too bad...
12679             dataSize = 0; // prevent calloc events for all subsequent games
12680             return 0;     // and signal this one isn't cached
12681         }
12682     }
12683     movePtr++;
12684     MakePieceList(board, counts);
12685     return movePtr;
12686 }
12687
12688 int
12689 QuickCompare (Board board, int *minCounts, int *maxCounts)
12690 {   // compare according to search mode
12691     int r, f;
12692     switch(appData.searchMode)
12693     {
12694       case 1: // exact position match
12695         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12696         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12697             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12698         }
12699         break;
12700       case 2: // can have extra material on empty squares
12701         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12702             if(board[r][f] == EmptySquare) continue;
12703             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12704         }
12705         break;
12706       case 3: // material with exact Pawn structure
12707         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12708             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12709             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12710         } // fall through to material comparison
12711       case 4: // exact material
12712         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12713         break;
12714       case 6: // material range with given imbalance
12715         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12716         // fall through to range comparison
12717       case 5: // material range
12718         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12719     }
12720     return TRUE;
12721 }
12722
12723 int
12724 QuickScan (Board board, Move *move)
12725 {   // reconstruct game,and compare all positions in it
12726     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12727     do {
12728         int piece = move->piece;
12729         int to = move->to, from = pieceList[piece];
12730         if(found < 0) { // if already found just scan to game end for final piece count
12731           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12732            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12733            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12734                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12735             ) {
12736             static int lastCounts[EmptySquare+1];
12737             int i;
12738             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12739             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12740           } else stretch = 0;
12741           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12742           if(found >= 0 && !appData.minPieces) return found;
12743         }
12744         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12745           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12746           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12747             piece = (++move)->piece;
12748             from = pieceList[piece];
12749             counts[pieceType[piece]]--;
12750             pieceType[piece] = (ChessSquare) move->to;
12751             counts[move->to]++;
12752           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12753             counts[pieceType[quickBoard[to]]]--;
12754             quickBoard[to] = 0; total--;
12755             move++;
12756             continue;
12757           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12758             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12759             from  = pieceList[piece]; // so this must be King
12760             quickBoard[from] = 0;
12761             pieceList[piece] = to;
12762             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12763             quickBoard[from] = 0; // rook
12764             quickBoard[to] = piece;
12765             to = move->to; piece = move->piece;
12766             goto aftercastle;
12767           }
12768         }
12769         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12770         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12771         quickBoard[from] = 0;
12772       aftercastle:
12773         quickBoard[to] = piece;
12774         pieceList[piece] = to;
12775         cnt++; turn ^= 3;
12776         move++;
12777     } while(1);
12778 }
12779
12780 void
12781 InitSearch ()
12782 {
12783     int r, f;
12784     flipSearch = FALSE;
12785     CopyBoard(soughtBoard, boards[currentMove]);
12786     soughtTotal = MakePieceList(soughtBoard, maxSought);
12787     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12788     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12789     CopyBoard(reverseBoard, boards[currentMove]);
12790     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12791         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12792         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12793         reverseBoard[r][f] = piece;
12794     }
12795     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12796     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12797     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12798                  || (boards[currentMove][CASTLING][2] == NoRights ||
12799                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12800                  && (boards[currentMove][CASTLING][5] == NoRights ||
12801                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12802       ) {
12803         flipSearch = TRUE;
12804         CopyBoard(flipBoard, soughtBoard);
12805         CopyBoard(rotateBoard, reverseBoard);
12806         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12807             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12808             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12809         }
12810     }
12811     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12812     if(appData.searchMode >= 5) {
12813         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12814         MakePieceList(soughtBoard, minSought);
12815         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12816     }
12817     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12818         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12819 }
12820
12821 GameInfo dummyInfo;
12822 static int creatingBook;
12823
12824 int
12825 GameContainsPosition (FILE *f, ListGame *lg)
12826 {
12827     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12828     int fromX, fromY, toX, toY;
12829     char promoChar;
12830     static int initDone=FALSE;
12831
12832     // weed out games based on numerical tag comparison
12833     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12834     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12835     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12836     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12837     if(!initDone) {
12838         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12839         initDone = TRUE;
12840     }
12841     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12842     else CopyBoard(boards[scratch], initialPosition); // default start position
12843     if(lg->moves) {
12844         turn = btm + 1;
12845         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12846         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12847     }
12848     if(btm) plyNr++;
12849     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12850     fseek(f, lg->offset, 0);
12851     yynewfile(f);
12852     while(1) {
12853         yyboardindex = scratch;
12854         quickFlag = plyNr+1;
12855         next = Myylex();
12856         quickFlag = 0;
12857         switch(next) {
12858             case PGNTag:
12859                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12860             default:
12861                 continue;
12862
12863             case XBoardGame:
12864             case GNUChessGame:
12865                 if(plyNr) return -1; // after we have seen moves, this is for new game
12866               continue;
12867
12868             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12869             case ImpossibleMove:
12870             case WhiteWins: // game ends here with these four
12871             case BlackWins:
12872             case GameIsDrawn:
12873             case GameUnfinished:
12874                 return -1;
12875
12876             case IllegalMove:
12877                 if(appData.testLegality) return -1;
12878             case WhiteCapturesEnPassant:
12879             case BlackCapturesEnPassant:
12880             case WhitePromotion:
12881             case BlackPromotion:
12882             case WhiteNonPromotion:
12883             case BlackNonPromotion:
12884             case NormalMove:
12885             case FirstLeg:
12886             case WhiteKingSideCastle:
12887             case WhiteQueenSideCastle:
12888             case BlackKingSideCastle:
12889             case BlackQueenSideCastle:
12890             case WhiteKingSideCastleWild:
12891             case WhiteQueenSideCastleWild:
12892             case BlackKingSideCastleWild:
12893             case BlackQueenSideCastleWild:
12894             case WhiteHSideCastleFR:
12895             case WhiteASideCastleFR:
12896             case BlackHSideCastleFR:
12897             case BlackASideCastleFR:
12898                 fromX = currentMoveString[0] - AAA;
12899                 fromY = currentMoveString[1] - ONE;
12900                 toX = currentMoveString[2] - AAA;
12901                 toY = currentMoveString[3] - ONE;
12902                 promoChar = currentMoveString[4];
12903                 break;
12904             case WhiteDrop:
12905             case BlackDrop:
12906                 fromX = next == WhiteDrop ?
12907                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12908                   (int) CharToPiece(ToLower(currentMoveString[0]));
12909                 fromY = DROP_RANK;
12910                 toX = currentMoveString[2] - AAA;
12911                 toY = currentMoveString[3] - ONE;
12912                 promoChar = 0;
12913                 break;
12914         }
12915         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12916         plyNr++;
12917         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12918         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12919         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12920         if(appData.findMirror) {
12921             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12922             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12923         }
12924     }
12925 }
12926
12927 /* Load the nth game from open file f */
12928 int
12929 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12930 {
12931     ChessMove cm;
12932     char buf[MSG_SIZ];
12933     int gn = gameNumber;
12934     ListGame *lg = NULL;
12935     int numPGNTags = 0;
12936     int err, pos = -1;
12937     GameMode oldGameMode;
12938     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12939     char oldName[MSG_SIZ];
12940
12941     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12942
12943     if (appData.debugMode)
12944         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12945
12946     if (gameMode == Training )
12947         SetTrainingModeOff();
12948
12949     oldGameMode = gameMode;
12950     if (gameMode != BeginningOfGame) {
12951       Reset(FALSE, TRUE);
12952     }
12953     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
12954
12955     gameFileFP = f;
12956     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12957         fclose(lastLoadGameFP);
12958     }
12959
12960     if (useList) {
12961         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12962
12963         if (lg) {
12964             fseek(f, lg->offset, 0);
12965             GameListHighlight(gameNumber);
12966             pos = lg->position;
12967             gn = 1;
12968         }
12969         else {
12970             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12971               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12972             else
12973             DisplayError(_("Game number out of range"), 0);
12974             return FALSE;
12975         }
12976     } else {
12977         GameListDestroy();
12978         if (fseek(f, 0, 0) == -1) {
12979             if (f == lastLoadGameFP ?
12980                 gameNumber == lastLoadGameNumber + 1 :
12981                 gameNumber == 1) {
12982                 gn = 1;
12983             } else {
12984                 DisplayError(_("Can't seek on game file"), 0);
12985                 return FALSE;
12986             }
12987         }
12988     }
12989     lastLoadGameFP = f;
12990     lastLoadGameNumber = gameNumber;
12991     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12992     lastLoadGameUseList = useList;
12993
12994     yynewfile(f);
12995
12996     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12997       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12998                 lg->gameInfo.black);
12999             DisplayTitle(buf);
13000     } else if (*title != NULLCHAR) {
13001         if (gameNumber > 1) {
13002           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13003             DisplayTitle(buf);
13004         } else {
13005             DisplayTitle(title);
13006         }
13007     }
13008
13009     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13010         gameMode = PlayFromGameFile;
13011         ModeHighlight();
13012     }
13013
13014     currentMove = forwardMostMove = backwardMostMove = 0;
13015     CopyBoard(boards[0], initialPosition);
13016     StopClocks();
13017
13018     /*
13019      * Skip the first gn-1 games in the file.
13020      * Also skip over anything that precedes an identifiable
13021      * start of game marker, to avoid being confused by
13022      * garbage at the start of the file.  Currently
13023      * recognized start of game markers are the move number "1",
13024      * the pattern "gnuchess .* game", the pattern
13025      * "^[#;%] [^ ]* game file", and a PGN tag block.
13026      * A game that starts with one of the latter two patterns
13027      * will also have a move number 1, possibly
13028      * following a position diagram.
13029      * 5-4-02: Let's try being more lenient and allowing a game to
13030      * start with an unnumbered move.  Does that break anything?
13031      */
13032     cm = lastLoadGameStart = EndOfFile;
13033     while (gn > 0) {
13034         yyboardindex = forwardMostMove;
13035         cm = (ChessMove) Myylex();
13036         switch (cm) {
13037           case EndOfFile:
13038             if (cmailMsgLoaded) {
13039                 nCmailGames = CMAIL_MAX_GAMES - gn;
13040             } else {
13041                 Reset(TRUE, TRUE);
13042                 DisplayError(_("Game not found in file"), 0);
13043             }
13044             return FALSE;
13045
13046           case GNUChessGame:
13047           case XBoardGame:
13048             gn--;
13049             lastLoadGameStart = cm;
13050             break;
13051
13052           case MoveNumberOne:
13053             switch (lastLoadGameStart) {
13054               case GNUChessGame:
13055               case XBoardGame:
13056               case PGNTag:
13057                 break;
13058               case MoveNumberOne:
13059               case EndOfFile:
13060                 gn--;           /* count this game */
13061                 lastLoadGameStart = cm;
13062                 break;
13063               default:
13064                 /* impossible */
13065                 break;
13066             }
13067             break;
13068
13069           case PGNTag:
13070             switch (lastLoadGameStart) {
13071               case GNUChessGame:
13072               case PGNTag:
13073               case MoveNumberOne:
13074               case EndOfFile:
13075                 gn--;           /* count this game */
13076                 lastLoadGameStart = cm;
13077                 break;
13078               case XBoardGame:
13079                 lastLoadGameStart = cm; /* game counted already */
13080                 break;
13081               default:
13082                 /* impossible */
13083                 break;
13084             }
13085             if (gn > 0) {
13086                 do {
13087                     yyboardindex = forwardMostMove;
13088                     cm = (ChessMove) Myylex();
13089                 } while (cm == PGNTag || cm == Comment);
13090             }
13091             break;
13092
13093           case WhiteWins:
13094           case BlackWins:
13095           case GameIsDrawn:
13096             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13097                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13098                     != CMAIL_OLD_RESULT) {
13099                     nCmailResults ++ ;
13100                     cmailResult[  CMAIL_MAX_GAMES
13101                                 - gn - 1] = CMAIL_OLD_RESULT;
13102                 }
13103             }
13104             break;
13105
13106           case NormalMove:
13107           case FirstLeg:
13108             /* Only a NormalMove can be at the start of a game
13109              * without a position diagram. */
13110             if (lastLoadGameStart == EndOfFile ) {
13111               gn--;
13112               lastLoadGameStart = MoveNumberOne;
13113             }
13114             break;
13115
13116           default:
13117             break;
13118         }
13119     }
13120
13121     if (appData.debugMode)
13122       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13123
13124     if (cm == XBoardGame) {
13125         /* Skip any header junk before position diagram and/or move 1 */
13126         for (;;) {
13127             yyboardindex = forwardMostMove;
13128             cm = (ChessMove) Myylex();
13129
13130             if (cm == EndOfFile ||
13131                 cm == GNUChessGame || cm == XBoardGame) {
13132                 /* Empty game; pretend end-of-file and handle later */
13133                 cm = EndOfFile;
13134                 break;
13135             }
13136
13137             if (cm == MoveNumberOne || cm == PositionDiagram ||
13138                 cm == PGNTag || cm == Comment)
13139               break;
13140         }
13141     } else if (cm == GNUChessGame) {
13142         if (gameInfo.event != NULL) {
13143             free(gameInfo.event);
13144         }
13145         gameInfo.event = StrSave(yy_text);
13146     }
13147
13148     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13149     while (cm == PGNTag) {
13150         if (appData.debugMode)
13151           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13152         err = ParsePGNTag(yy_text, &gameInfo);
13153         if (!err) numPGNTags++;
13154
13155         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13156         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13157             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13158             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13159             InitPosition(TRUE);
13160             oldVariant = gameInfo.variant;
13161             if (appData.debugMode)
13162               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13163         }
13164
13165
13166         if (gameInfo.fen != NULL) {
13167           Board initial_position;
13168           startedFromSetupPosition = TRUE;
13169           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13170             Reset(TRUE, TRUE);
13171             DisplayError(_("Bad FEN position in file"), 0);
13172             return FALSE;
13173           }
13174           CopyBoard(boards[0], initial_position);
13175           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13176             CopyBoard(initialPosition, initial_position);
13177           if (blackPlaysFirst) {
13178             currentMove = forwardMostMove = backwardMostMove = 1;
13179             CopyBoard(boards[1], initial_position);
13180             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13181             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13182             timeRemaining[0][1] = whiteTimeRemaining;
13183             timeRemaining[1][1] = blackTimeRemaining;
13184             if (commentList[0] != NULL) {
13185               commentList[1] = commentList[0];
13186               commentList[0] = NULL;
13187             }
13188           } else {
13189             currentMove = forwardMostMove = backwardMostMove = 0;
13190           }
13191           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13192           {   int i;
13193               initialRulePlies = FENrulePlies;
13194               for( i=0; i< nrCastlingRights; i++ )
13195                   initialRights[i] = initial_position[CASTLING][i];
13196           }
13197           yyboardindex = forwardMostMove;
13198           free(gameInfo.fen);
13199           gameInfo.fen = NULL;
13200         }
13201
13202         yyboardindex = forwardMostMove;
13203         cm = (ChessMove) Myylex();
13204
13205         /* Handle comments interspersed among the tags */
13206         while (cm == Comment) {
13207             char *p;
13208             if (appData.debugMode)
13209               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13210             p = yy_text;
13211             AppendComment(currentMove, p, FALSE);
13212             yyboardindex = forwardMostMove;
13213             cm = (ChessMove) Myylex();
13214         }
13215     }
13216
13217     /* don't rely on existence of Event tag since if game was
13218      * pasted from clipboard the Event tag may not exist
13219      */
13220     if (numPGNTags > 0){
13221         char *tags;
13222         if (gameInfo.variant == VariantNormal) {
13223           VariantClass v = StringToVariant(gameInfo.event);
13224           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13225           if(v < VariantShogi) gameInfo.variant = v;
13226         }
13227         if (!matchMode) {
13228           if( appData.autoDisplayTags ) {
13229             tags = PGNTags(&gameInfo);
13230             TagsPopUp(tags, CmailMsg());
13231             free(tags);
13232           }
13233         }
13234     } else {
13235         /* Make something up, but don't display it now */
13236         SetGameInfo();
13237         TagsPopDown();
13238     }
13239
13240     if (cm == PositionDiagram) {
13241         int i, j;
13242         char *p;
13243         Board initial_position;
13244
13245         if (appData.debugMode)
13246           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13247
13248         if (!startedFromSetupPosition) {
13249             p = yy_text;
13250             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13251               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13252                 switch (*p) {
13253                   case '{':
13254                   case '[':
13255                   case '-':
13256                   case ' ':
13257                   case '\t':
13258                   case '\n':
13259                   case '\r':
13260                     break;
13261                   default:
13262                     initial_position[i][j++] = CharToPiece(*p);
13263                     break;
13264                 }
13265             while (*p == ' ' || *p == '\t' ||
13266                    *p == '\n' || *p == '\r') p++;
13267
13268             if (strncmp(p, "black", strlen("black"))==0)
13269               blackPlaysFirst = TRUE;
13270             else
13271               blackPlaysFirst = FALSE;
13272             startedFromSetupPosition = TRUE;
13273
13274             CopyBoard(boards[0], initial_position);
13275             if (blackPlaysFirst) {
13276                 currentMove = forwardMostMove = backwardMostMove = 1;
13277                 CopyBoard(boards[1], initial_position);
13278                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13279                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13280                 timeRemaining[0][1] = whiteTimeRemaining;
13281                 timeRemaining[1][1] = blackTimeRemaining;
13282                 if (commentList[0] != NULL) {
13283                     commentList[1] = commentList[0];
13284                     commentList[0] = NULL;
13285                 }
13286             } else {
13287                 currentMove = forwardMostMove = backwardMostMove = 0;
13288             }
13289         }
13290         yyboardindex = forwardMostMove;
13291         cm = (ChessMove) Myylex();
13292     }
13293
13294   if(!creatingBook) {
13295     if (first.pr == NoProc) {
13296         StartChessProgram(&first);
13297     }
13298     InitChessProgram(&first, FALSE);
13299     if(gameInfo.variant == VariantUnknown && *oldName) {
13300         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13301         gameInfo.variant = v;
13302     }
13303     SendToProgram("force\n", &first);
13304     if (startedFromSetupPosition) {
13305         SendBoard(&first, forwardMostMove);
13306     if (appData.debugMode) {
13307         fprintf(debugFP, "Load Game\n");
13308     }
13309         DisplayBothClocks();
13310     }
13311   }
13312
13313     /* [HGM] server: flag to write setup moves in broadcast file as one */
13314     loadFlag = appData.suppressLoadMoves;
13315
13316     while (cm == Comment) {
13317         char *p;
13318         if (appData.debugMode)
13319           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13320         p = yy_text;
13321         AppendComment(currentMove, p, FALSE);
13322         yyboardindex = forwardMostMove;
13323         cm = (ChessMove) Myylex();
13324     }
13325
13326     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13327         cm == WhiteWins || cm == BlackWins ||
13328         cm == GameIsDrawn || cm == GameUnfinished) {
13329         DisplayMessage("", _("No moves in game"));
13330         if (cmailMsgLoaded) {
13331             if (appData.debugMode)
13332               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13333             ClearHighlights();
13334             flipView = FALSE;
13335         }
13336         DrawPosition(FALSE, boards[currentMove]);
13337         DisplayBothClocks();
13338         gameMode = EditGame;
13339         ModeHighlight();
13340         gameFileFP = NULL;
13341         cmailOldMove = 0;
13342         return TRUE;
13343     }
13344
13345     // [HGM] PV info: routine tests if comment empty
13346     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13347         DisplayComment(currentMove - 1, commentList[currentMove]);
13348     }
13349     if (!matchMode && appData.timeDelay != 0)
13350       DrawPosition(FALSE, boards[currentMove]);
13351
13352     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13353       programStats.ok_to_send = 1;
13354     }
13355
13356     /* if the first token after the PGN tags is a move
13357      * and not move number 1, retrieve it from the parser
13358      */
13359     if (cm != MoveNumberOne)
13360         LoadGameOneMove(cm);
13361
13362     /* load the remaining moves from the file */
13363     while (LoadGameOneMove(EndOfFile)) {
13364       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13365       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13366     }
13367
13368     /* rewind to the start of the game */
13369     currentMove = backwardMostMove;
13370
13371     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13372
13373     if (oldGameMode == AnalyzeFile) {
13374       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13375       AnalyzeFileEvent();
13376     } else
13377     if (oldGameMode == AnalyzeMode) {
13378       AnalyzeFileEvent();
13379     }
13380
13381     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13382         long int w, b; // [HGM] adjourn: restore saved clock times
13383         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13384         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13385             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13386             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13387         }
13388     }
13389
13390     if(creatingBook) return TRUE;
13391     if (!matchMode && pos > 0) {
13392         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13393     } else
13394     if (matchMode || appData.timeDelay == 0) {
13395       ToEndEvent();
13396     } else if (appData.timeDelay > 0) {
13397       AutoPlayGameLoop();
13398     }
13399
13400     if (appData.debugMode)
13401         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13402
13403     loadFlag = 0; /* [HGM] true game starts */
13404     return TRUE;
13405 }
13406
13407 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13408 int
13409 ReloadPosition (int offset)
13410 {
13411     int positionNumber = lastLoadPositionNumber + offset;
13412     if (lastLoadPositionFP == NULL) {
13413         DisplayError(_("No position has been loaded yet"), 0);
13414         return FALSE;
13415     }
13416     if (positionNumber <= 0) {
13417         DisplayError(_("Can't back up any further"), 0);
13418         return FALSE;
13419     }
13420     return LoadPosition(lastLoadPositionFP, positionNumber,
13421                         lastLoadPositionTitle);
13422 }
13423
13424 /* Load the nth position from the given file */
13425 int
13426 LoadPositionFromFile (char *filename, int n, char *title)
13427 {
13428     FILE *f;
13429     char buf[MSG_SIZ];
13430
13431     if (strcmp(filename, "-") == 0) {
13432         return LoadPosition(stdin, n, "stdin");
13433     } else {
13434         f = fopen(filename, "rb");
13435         if (f == NULL) {
13436             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13437             DisplayError(buf, errno);
13438             return FALSE;
13439         } else {
13440             return LoadPosition(f, n, title);
13441         }
13442     }
13443 }
13444
13445 /* Load the nth position from the given open file, and close it */
13446 int
13447 LoadPosition (FILE *f, int positionNumber, char *title)
13448 {
13449     char *p, line[MSG_SIZ];
13450     Board initial_position;
13451     int i, j, fenMode, pn;
13452
13453     if (gameMode == Training )
13454         SetTrainingModeOff();
13455
13456     if (gameMode != BeginningOfGame) {
13457         Reset(FALSE, TRUE);
13458     }
13459     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13460         fclose(lastLoadPositionFP);
13461     }
13462     if (positionNumber == 0) positionNumber = 1;
13463     lastLoadPositionFP = f;
13464     lastLoadPositionNumber = positionNumber;
13465     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13466     if (first.pr == NoProc && !appData.noChessProgram) {
13467       StartChessProgram(&first);
13468       InitChessProgram(&first, FALSE);
13469     }
13470     pn = positionNumber;
13471     if (positionNumber < 0) {
13472         /* Negative position number means to seek to that byte offset */
13473         if (fseek(f, -positionNumber, 0) == -1) {
13474             DisplayError(_("Can't seek on position file"), 0);
13475             return FALSE;
13476         };
13477         pn = 1;
13478     } else {
13479         if (fseek(f, 0, 0) == -1) {
13480             if (f == lastLoadPositionFP ?
13481                 positionNumber == lastLoadPositionNumber + 1 :
13482                 positionNumber == 1) {
13483                 pn = 1;
13484             } else {
13485                 DisplayError(_("Can't seek on position file"), 0);
13486                 return FALSE;
13487             }
13488         }
13489     }
13490     /* See if this file is FEN or old-style xboard */
13491     if (fgets(line, MSG_SIZ, f) == NULL) {
13492         DisplayError(_("Position not found in file"), 0);
13493         return FALSE;
13494     }
13495     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13496     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13497
13498     if (pn >= 2) {
13499         if (fenMode || line[0] == '#') pn--;
13500         while (pn > 0) {
13501             /* skip positions before number pn */
13502             if (fgets(line, MSG_SIZ, f) == NULL) {
13503                 Reset(TRUE, TRUE);
13504                 DisplayError(_("Position not found in file"), 0);
13505                 return FALSE;
13506             }
13507             if (fenMode || line[0] == '#') pn--;
13508         }
13509     }
13510
13511     if (fenMode) {
13512         char *p;
13513         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13514             DisplayError(_("Bad FEN position in file"), 0);
13515             return FALSE;
13516         }
13517         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13518             sscanf(p+3, "%s", bestMove);
13519         } else *bestMove = NULLCHAR;
13520     } else {
13521         (void) fgets(line, MSG_SIZ, f);
13522         (void) fgets(line, MSG_SIZ, f);
13523
13524         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13525             (void) fgets(line, MSG_SIZ, f);
13526             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13527                 if (*p == ' ')
13528                   continue;
13529                 initial_position[i][j++] = CharToPiece(*p);
13530             }
13531         }
13532
13533         blackPlaysFirst = FALSE;
13534         if (!feof(f)) {
13535             (void) fgets(line, MSG_SIZ, f);
13536             if (strncmp(line, "black", strlen("black"))==0)
13537               blackPlaysFirst = TRUE;
13538         }
13539     }
13540     startedFromSetupPosition = TRUE;
13541
13542     CopyBoard(boards[0], initial_position);
13543     if (blackPlaysFirst) {
13544         currentMove = forwardMostMove = backwardMostMove = 1;
13545         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13546         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13547         CopyBoard(boards[1], initial_position);
13548         DisplayMessage("", _("Black to play"));
13549     } else {
13550         currentMove = forwardMostMove = backwardMostMove = 0;
13551         DisplayMessage("", _("White to play"));
13552     }
13553     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13554     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13555         SendToProgram("force\n", &first);
13556         SendBoard(&first, forwardMostMove);
13557     }
13558     if (appData.debugMode) {
13559 int i, j;
13560   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13561   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13562         fprintf(debugFP, "Load Position\n");
13563     }
13564
13565     if (positionNumber > 1) {
13566       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13567         DisplayTitle(line);
13568     } else {
13569         DisplayTitle(title);
13570     }
13571     gameMode = EditGame;
13572     ModeHighlight();
13573     ResetClocks();
13574     timeRemaining[0][1] = whiteTimeRemaining;
13575     timeRemaining[1][1] = blackTimeRemaining;
13576     DrawPosition(FALSE, boards[currentMove]);
13577
13578     return TRUE;
13579 }
13580
13581
13582 void
13583 CopyPlayerNameIntoFileName (char **dest, char *src)
13584 {
13585     while (*src != NULLCHAR && *src != ',') {
13586         if (*src == ' ') {
13587             *(*dest)++ = '_';
13588             src++;
13589         } else {
13590             *(*dest)++ = *src++;
13591         }
13592     }
13593 }
13594
13595 char *
13596 DefaultFileName (char *ext)
13597 {
13598     static char def[MSG_SIZ];
13599     char *p;
13600
13601     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13602         p = def;
13603         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13604         *p++ = '-';
13605         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13606         *p++ = '.';
13607         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13608     } else {
13609         def[0] = NULLCHAR;
13610     }
13611     return def;
13612 }
13613
13614 /* Save the current game to the given file */
13615 int
13616 SaveGameToFile (char *filename, int append)
13617 {
13618     FILE *f;
13619     char buf[MSG_SIZ];
13620     int result, i, t,tot=0;
13621
13622     if (strcmp(filename, "-") == 0) {
13623         return SaveGame(stdout, 0, NULL);
13624     } else {
13625         for(i=0; i<10; i++) { // upto 10 tries
13626              f = fopen(filename, append ? "a" : "w");
13627              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13628              if(f || errno != 13) break;
13629              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13630              tot += t;
13631         }
13632         if (f == NULL) {
13633             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13634             DisplayError(buf, errno);
13635             return FALSE;
13636         } else {
13637             safeStrCpy(buf, lastMsg, MSG_SIZ);
13638             DisplayMessage(_("Waiting for access to save file"), "");
13639             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13640             DisplayMessage(_("Saving game"), "");
13641             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13642             result = SaveGame(f, 0, NULL);
13643             DisplayMessage(buf, "");
13644             return result;
13645         }
13646     }
13647 }
13648
13649 char *
13650 SavePart (char *str)
13651 {
13652     static char buf[MSG_SIZ];
13653     char *p;
13654
13655     p = strchr(str, ' ');
13656     if (p == NULL) return str;
13657     strncpy(buf, str, p - str);
13658     buf[p - str] = NULLCHAR;
13659     return buf;
13660 }
13661
13662 #define PGN_MAX_LINE 75
13663
13664 #define PGN_SIDE_WHITE  0
13665 #define PGN_SIDE_BLACK  1
13666
13667 static int
13668 FindFirstMoveOutOfBook (int side)
13669 {
13670     int result = -1;
13671
13672     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13673         int index = backwardMostMove;
13674         int has_book_hit = 0;
13675
13676         if( (index % 2) != side ) {
13677             index++;
13678         }
13679
13680         while( index < forwardMostMove ) {
13681             /* Check to see if engine is in book */
13682             int depth = pvInfoList[index].depth;
13683             int score = pvInfoList[index].score;
13684             int in_book = 0;
13685
13686             if( depth <= 2 ) {
13687                 in_book = 1;
13688             }
13689             else if( score == 0 && depth == 63 ) {
13690                 in_book = 1; /* Zappa */
13691             }
13692             else if( score == 2 && depth == 99 ) {
13693                 in_book = 1; /* Abrok */
13694             }
13695
13696             has_book_hit += in_book;
13697
13698             if( ! in_book ) {
13699                 result = index;
13700
13701                 break;
13702             }
13703
13704             index += 2;
13705         }
13706     }
13707
13708     return result;
13709 }
13710
13711 void
13712 GetOutOfBookInfo (char * buf)
13713 {
13714     int oob[2];
13715     int i;
13716     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13717
13718     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13719     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13720
13721     *buf = '\0';
13722
13723     if( oob[0] >= 0 || oob[1] >= 0 ) {
13724         for( i=0; i<2; i++ ) {
13725             int idx = oob[i];
13726
13727             if( idx >= 0 ) {
13728                 if( i > 0 && oob[0] >= 0 ) {
13729                     strcat( buf, "   " );
13730                 }
13731
13732                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13733                 sprintf( buf+strlen(buf), "%s%.2f",
13734                     pvInfoList[idx].score >= 0 ? "+" : "",
13735                     pvInfoList[idx].score / 100.0 );
13736             }
13737         }
13738     }
13739 }
13740
13741 /* Save game in PGN style */
13742 static void
13743 SaveGamePGN2 (FILE *f)
13744 {
13745     int i, offset, linelen, newblock;
13746 //    char *movetext;
13747     char numtext[32];
13748     int movelen, numlen, blank;
13749     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13750
13751     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13752
13753     PrintPGNTags(f, &gameInfo);
13754
13755     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13756
13757     if (backwardMostMove > 0 || startedFromSetupPosition) {
13758         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13759         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13760         fprintf(f, "\n{--------------\n");
13761         PrintPosition(f, backwardMostMove);
13762         fprintf(f, "--------------}\n");
13763         free(fen);
13764     }
13765     else {
13766         /* [AS] Out of book annotation */
13767         if( appData.saveOutOfBookInfo ) {
13768             char buf[64];
13769
13770             GetOutOfBookInfo( buf );
13771
13772             if( buf[0] != '\0' ) {
13773                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13774             }
13775         }
13776
13777         fprintf(f, "\n");
13778     }
13779
13780     i = backwardMostMove;
13781     linelen = 0;
13782     newblock = TRUE;
13783
13784     while (i < forwardMostMove) {
13785         /* Print comments preceding this move */
13786         if (commentList[i] != NULL) {
13787             if (linelen > 0) fprintf(f, "\n");
13788             fprintf(f, "%s", commentList[i]);
13789             linelen = 0;
13790             newblock = TRUE;
13791         }
13792
13793         /* Format move number */
13794         if ((i % 2) == 0)
13795           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13796         else
13797           if (newblock)
13798             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13799           else
13800             numtext[0] = NULLCHAR;
13801
13802         numlen = strlen(numtext);
13803         newblock = FALSE;
13804
13805         /* Print move number */
13806         blank = linelen > 0 && numlen > 0;
13807         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13808             fprintf(f, "\n");
13809             linelen = 0;
13810             blank = 0;
13811         }
13812         if (blank) {
13813             fprintf(f, " ");
13814             linelen++;
13815         }
13816         fprintf(f, "%s", numtext);
13817         linelen += numlen;
13818
13819         /* Get move */
13820         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13821         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13822
13823         /* Print move */
13824         blank = linelen > 0 && movelen > 0;
13825         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13826             fprintf(f, "\n");
13827             linelen = 0;
13828             blank = 0;
13829         }
13830         if (blank) {
13831             fprintf(f, " ");
13832             linelen++;
13833         }
13834         fprintf(f, "%s", move_buffer);
13835         linelen += movelen;
13836
13837         /* [AS] Add PV info if present */
13838         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13839             /* [HGM] add time */
13840             char buf[MSG_SIZ]; int seconds;
13841
13842             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13843
13844             if( seconds <= 0)
13845               buf[0] = 0;
13846             else
13847               if( seconds < 30 )
13848                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13849               else
13850                 {
13851                   seconds = (seconds + 4)/10; // round to full seconds
13852                   if( seconds < 60 )
13853                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13854                   else
13855                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13856                 }
13857
13858             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13859                       pvInfoList[i].score >= 0 ? "+" : "",
13860                       pvInfoList[i].score / 100.0,
13861                       pvInfoList[i].depth,
13862                       buf );
13863
13864             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13865
13866             /* Print score/depth */
13867             blank = linelen > 0 && movelen > 0;
13868             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13869                 fprintf(f, "\n");
13870                 linelen = 0;
13871                 blank = 0;
13872             }
13873             if (blank) {
13874                 fprintf(f, " ");
13875                 linelen++;
13876             }
13877             fprintf(f, "%s", move_buffer);
13878             linelen += movelen;
13879         }
13880
13881         i++;
13882     }
13883
13884     /* Start a new line */
13885     if (linelen > 0) fprintf(f, "\n");
13886
13887     /* Print comments after last move */
13888     if (commentList[i] != NULL) {
13889         fprintf(f, "%s\n", commentList[i]);
13890     }
13891
13892     /* Print result */
13893     if (gameInfo.resultDetails != NULL &&
13894         gameInfo.resultDetails[0] != NULLCHAR) {
13895         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13896         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13897            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13898             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13899         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13900     } else {
13901         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13902     }
13903 }
13904
13905 /* Save game in PGN style and close the file */
13906 int
13907 SaveGamePGN (FILE *f)
13908 {
13909     SaveGamePGN2(f);
13910     fclose(f);
13911     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13912     return TRUE;
13913 }
13914
13915 /* Save game in old style and close the file */
13916 int
13917 SaveGameOldStyle (FILE *f)
13918 {
13919     int i, offset;
13920     time_t tm;
13921
13922     tm = time((time_t *) NULL);
13923
13924     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13925     PrintOpponents(f);
13926
13927     if (backwardMostMove > 0 || startedFromSetupPosition) {
13928         fprintf(f, "\n[--------------\n");
13929         PrintPosition(f, backwardMostMove);
13930         fprintf(f, "--------------]\n");
13931     } else {
13932         fprintf(f, "\n");
13933     }
13934
13935     i = backwardMostMove;
13936     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13937
13938     while (i < forwardMostMove) {
13939         if (commentList[i] != NULL) {
13940             fprintf(f, "[%s]\n", commentList[i]);
13941         }
13942
13943         if ((i % 2) == 1) {
13944             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13945             i++;
13946         } else {
13947             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13948             i++;
13949             if (commentList[i] != NULL) {
13950                 fprintf(f, "\n");
13951                 continue;
13952             }
13953             if (i >= forwardMostMove) {
13954                 fprintf(f, "\n");
13955                 break;
13956             }
13957             fprintf(f, "%s\n", parseList[i]);
13958             i++;
13959         }
13960     }
13961
13962     if (commentList[i] != NULL) {
13963         fprintf(f, "[%s]\n", commentList[i]);
13964     }
13965
13966     /* This isn't really the old style, but it's close enough */
13967     if (gameInfo.resultDetails != NULL &&
13968         gameInfo.resultDetails[0] != NULLCHAR) {
13969         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13970                 gameInfo.resultDetails);
13971     } else {
13972         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13973     }
13974
13975     fclose(f);
13976     return TRUE;
13977 }
13978
13979 /* Save the current game to open file f and close the file */
13980 int
13981 SaveGame (FILE *f, int dummy, char *dummy2)
13982 {
13983     if (gameMode == EditPosition) EditPositionDone(TRUE);
13984     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13985     if (appData.oldSaveStyle)
13986       return SaveGameOldStyle(f);
13987     else
13988       return SaveGamePGN(f);
13989 }
13990
13991 /* Save the current position to the given file */
13992 int
13993 SavePositionToFile (char *filename)
13994 {
13995     FILE *f;
13996     char buf[MSG_SIZ];
13997
13998     if (strcmp(filename, "-") == 0) {
13999         return SavePosition(stdout, 0, NULL);
14000     } else {
14001         f = fopen(filename, "a");
14002         if (f == NULL) {
14003             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14004             DisplayError(buf, errno);
14005             return FALSE;
14006         } else {
14007             safeStrCpy(buf, lastMsg, MSG_SIZ);
14008             DisplayMessage(_("Waiting for access to save file"), "");
14009             flock(fileno(f), LOCK_EX); // [HGM] lock
14010             DisplayMessage(_("Saving position"), "");
14011             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14012             SavePosition(f, 0, NULL);
14013             DisplayMessage(buf, "");
14014             return TRUE;
14015         }
14016     }
14017 }
14018
14019 /* Save the current position to the given open file and close the file */
14020 int
14021 SavePosition (FILE *f, int dummy, char *dummy2)
14022 {
14023     time_t tm;
14024     char *fen;
14025
14026     if (gameMode == EditPosition) EditPositionDone(TRUE);
14027     if (appData.oldSaveStyle) {
14028         tm = time((time_t *) NULL);
14029
14030         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14031         PrintOpponents(f);
14032         fprintf(f, "[--------------\n");
14033         PrintPosition(f, currentMove);
14034         fprintf(f, "--------------]\n");
14035     } else {
14036         fen = PositionToFEN(currentMove, NULL, 1);
14037         fprintf(f, "%s\n", fen);
14038         free(fen);
14039     }
14040     fclose(f);
14041     return TRUE;
14042 }
14043
14044 void
14045 ReloadCmailMsgEvent (int unregister)
14046 {
14047 #if !WIN32
14048     static char *inFilename = NULL;
14049     static char *outFilename;
14050     int i;
14051     struct stat inbuf, outbuf;
14052     int status;
14053
14054     /* Any registered moves are unregistered if unregister is set, */
14055     /* i.e. invoked by the signal handler */
14056     if (unregister) {
14057         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14058             cmailMoveRegistered[i] = FALSE;
14059             if (cmailCommentList[i] != NULL) {
14060                 free(cmailCommentList[i]);
14061                 cmailCommentList[i] = NULL;
14062             }
14063         }
14064         nCmailMovesRegistered = 0;
14065     }
14066
14067     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14068         cmailResult[i] = CMAIL_NOT_RESULT;
14069     }
14070     nCmailResults = 0;
14071
14072     if (inFilename == NULL) {
14073         /* Because the filenames are static they only get malloced once  */
14074         /* and they never get freed                                      */
14075         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14076         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14077
14078         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14079         sprintf(outFilename, "%s.out", appData.cmailGameName);
14080     }
14081
14082     status = stat(outFilename, &outbuf);
14083     if (status < 0) {
14084         cmailMailedMove = FALSE;
14085     } else {
14086         status = stat(inFilename, &inbuf);
14087         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14088     }
14089
14090     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14091        counts the games, notes how each one terminated, etc.
14092
14093        It would be nice to remove this kludge and instead gather all
14094        the information while building the game list.  (And to keep it
14095        in the game list nodes instead of having a bunch of fixed-size
14096        parallel arrays.)  Note this will require getting each game's
14097        termination from the PGN tags, as the game list builder does
14098        not process the game moves.  --mann
14099        */
14100     cmailMsgLoaded = TRUE;
14101     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14102
14103     /* Load first game in the file or popup game menu */
14104     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14105
14106 #endif /* !WIN32 */
14107     return;
14108 }
14109
14110 int
14111 RegisterMove ()
14112 {
14113     FILE *f;
14114     char string[MSG_SIZ];
14115
14116     if (   cmailMailedMove
14117         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14118         return TRUE;            /* Allow free viewing  */
14119     }
14120
14121     /* Unregister move to ensure that we don't leave RegisterMove        */
14122     /* with the move registered when the conditions for registering no   */
14123     /* longer hold                                                       */
14124     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14125         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14126         nCmailMovesRegistered --;
14127
14128         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14129           {
14130               free(cmailCommentList[lastLoadGameNumber - 1]);
14131               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14132           }
14133     }
14134
14135     if (cmailOldMove == -1) {
14136         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14137         return FALSE;
14138     }
14139
14140     if (currentMove > cmailOldMove + 1) {
14141         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14142         return FALSE;
14143     }
14144
14145     if (currentMove < cmailOldMove) {
14146         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14147         return FALSE;
14148     }
14149
14150     if (forwardMostMove > currentMove) {
14151         /* Silently truncate extra moves */
14152         TruncateGame();
14153     }
14154
14155     if (   (currentMove == cmailOldMove + 1)
14156         || (   (currentMove == cmailOldMove)
14157             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14158                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14159         if (gameInfo.result != GameUnfinished) {
14160             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14161         }
14162
14163         if (commentList[currentMove] != NULL) {
14164             cmailCommentList[lastLoadGameNumber - 1]
14165               = StrSave(commentList[currentMove]);
14166         }
14167         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14168
14169         if (appData.debugMode)
14170           fprintf(debugFP, "Saving %s for game %d\n",
14171                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14172
14173         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14174
14175         f = fopen(string, "w");
14176         if (appData.oldSaveStyle) {
14177             SaveGameOldStyle(f); /* also closes the file */
14178
14179             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14180             f = fopen(string, "w");
14181             SavePosition(f, 0, NULL); /* also closes the file */
14182         } else {
14183             fprintf(f, "{--------------\n");
14184             PrintPosition(f, currentMove);
14185             fprintf(f, "--------------}\n\n");
14186
14187             SaveGame(f, 0, NULL); /* also closes the file*/
14188         }
14189
14190         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14191         nCmailMovesRegistered ++;
14192     } else if (nCmailGames == 1) {
14193         DisplayError(_("You have not made a move yet"), 0);
14194         return FALSE;
14195     }
14196
14197     return TRUE;
14198 }
14199
14200 void
14201 MailMoveEvent ()
14202 {
14203 #if !WIN32
14204     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14205     FILE *commandOutput;
14206     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14207     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14208     int nBuffers;
14209     int i;
14210     int archived;
14211     char *arcDir;
14212
14213     if (! cmailMsgLoaded) {
14214         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14215         return;
14216     }
14217
14218     if (nCmailGames == nCmailResults) {
14219         DisplayError(_("No unfinished games"), 0);
14220         return;
14221     }
14222
14223 #if CMAIL_PROHIBIT_REMAIL
14224     if (cmailMailedMove) {
14225       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);
14226         DisplayError(msg, 0);
14227         return;
14228     }
14229 #endif
14230
14231     if (! (cmailMailedMove || RegisterMove())) return;
14232
14233     if (   cmailMailedMove
14234         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14235       snprintf(string, MSG_SIZ, partCommandString,
14236                appData.debugMode ? " -v" : "", appData.cmailGameName);
14237         commandOutput = popen(string, "r");
14238
14239         if (commandOutput == NULL) {
14240             DisplayError(_("Failed to invoke cmail"), 0);
14241         } else {
14242             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14243                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14244             }
14245             if (nBuffers > 1) {
14246                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14247                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14248                 nBytes = MSG_SIZ - 1;
14249             } else {
14250                 (void) memcpy(msg, buffer, nBytes);
14251             }
14252             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14253
14254             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14255                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14256
14257                 archived = TRUE;
14258                 for (i = 0; i < nCmailGames; i ++) {
14259                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14260                         archived = FALSE;
14261                     }
14262                 }
14263                 if (   archived
14264                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14265                         != NULL)) {
14266                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14267                            arcDir,
14268                            appData.cmailGameName,
14269                            gameInfo.date);
14270                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14271                     cmailMsgLoaded = FALSE;
14272                 }
14273             }
14274
14275             DisplayInformation(msg);
14276             pclose(commandOutput);
14277         }
14278     } else {
14279         if ((*cmailMsg) != '\0') {
14280             DisplayInformation(cmailMsg);
14281         }
14282     }
14283
14284     return;
14285 #endif /* !WIN32 */
14286 }
14287
14288 char *
14289 CmailMsg ()
14290 {
14291 #if WIN32
14292     return NULL;
14293 #else
14294     int  prependComma = 0;
14295     char number[5];
14296     char string[MSG_SIZ];       /* Space for game-list */
14297     int  i;
14298
14299     if (!cmailMsgLoaded) return "";
14300
14301     if (cmailMailedMove) {
14302       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14303     } else {
14304         /* Create a list of games left */
14305       snprintf(string, MSG_SIZ, "[");
14306         for (i = 0; i < nCmailGames; i ++) {
14307             if (! (   cmailMoveRegistered[i]
14308                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14309                 if (prependComma) {
14310                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14311                 } else {
14312                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14313                     prependComma = 1;
14314                 }
14315
14316                 strcat(string, number);
14317             }
14318         }
14319         strcat(string, "]");
14320
14321         if (nCmailMovesRegistered + nCmailResults == 0) {
14322             switch (nCmailGames) {
14323               case 1:
14324                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14325                 break;
14326
14327               case 2:
14328                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14329                 break;
14330
14331               default:
14332                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14333                          nCmailGames);
14334                 break;
14335             }
14336         } else {
14337             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14338               case 1:
14339                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14340                          string);
14341                 break;
14342
14343               case 0:
14344                 if (nCmailResults == nCmailGames) {
14345                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14346                 } else {
14347                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14348                 }
14349                 break;
14350
14351               default:
14352                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14353                          string);
14354             }
14355         }
14356     }
14357     return cmailMsg;
14358 #endif /* WIN32 */
14359 }
14360
14361 void
14362 ResetGameEvent ()
14363 {
14364     if (gameMode == Training)
14365       SetTrainingModeOff();
14366
14367     Reset(TRUE, TRUE);
14368     cmailMsgLoaded = FALSE;
14369     if (appData.icsActive) {
14370       SendToICS(ics_prefix);
14371       SendToICS("refresh\n");
14372     }
14373 }
14374
14375 void
14376 ExitEvent (int status)
14377 {
14378     exiting++;
14379     if (exiting > 2) {
14380       /* Give up on clean exit */
14381       exit(status);
14382     }
14383     if (exiting > 1) {
14384       /* Keep trying for clean exit */
14385       return;
14386     }
14387
14388     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14389     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14390
14391     if (telnetISR != NULL) {
14392       RemoveInputSource(telnetISR);
14393     }
14394     if (icsPR != NoProc) {
14395       DestroyChildProcess(icsPR, TRUE);
14396     }
14397
14398     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14399     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14400
14401     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14402     /* make sure this other one finishes before killing it!                  */
14403     if(endingGame) { int count = 0;
14404         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14405         while(endingGame && count++ < 10) DoSleep(1);
14406         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14407     }
14408
14409     /* Kill off chess programs */
14410     if (first.pr != NoProc) {
14411         ExitAnalyzeMode();
14412
14413         DoSleep( appData.delayBeforeQuit );
14414         SendToProgram("quit\n", &first);
14415         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14416     }
14417     if (second.pr != NoProc) {
14418         DoSleep( appData.delayBeforeQuit );
14419         SendToProgram("quit\n", &second);
14420         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14421     }
14422     if (first.isr != NULL) {
14423         RemoveInputSource(first.isr);
14424     }
14425     if (second.isr != NULL) {
14426         RemoveInputSource(second.isr);
14427     }
14428
14429     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14430     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14431
14432     ShutDownFrontEnd();
14433     exit(status);
14434 }
14435
14436 void
14437 PauseEngine (ChessProgramState *cps)
14438 {
14439     SendToProgram("pause\n", cps);
14440     cps->pause = 2;
14441 }
14442
14443 void
14444 UnPauseEngine (ChessProgramState *cps)
14445 {
14446     SendToProgram("resume\n", cps);
14447     cps->pause = 1;
14448 }
14449
14450 void
14451 PauseEvent ()
14452 {
14453     if (appData.debugMode)
14454         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14455     if (pausing) {
14456         pausing = FALSE;
14457         ModeHighlight();
14458         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14459             StartClocks();
14460             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14461                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14462                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14463             }
14464             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14465             HandleMachineMove(stashedInputMove, stalledEngine);
14466             stalledEngine = NULL;
14467             return;
14468         }
14469         if (gameMode == MachinePlaysWhite ||
14470             gameMode == TwoMachinesPlay   ||
14471             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14472             if(first.pause)  UnPauseEngine(&first);
14473             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14474             if(second.pause) UnPauseEngine(&second);
14475             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14476             StartClocks();
14477         } else {
14478             DisplayBothClocks();
14479         }
14480         if (gameMode == PlayFromGameFile) {
14481             if (appData.timeDelay >= 0)
14482                 AutoPlayGameLoop();
14483         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14484             Reset(FALSE, TRUE);
14485             SendToICS(ics_prefix);
14486             SendToICS("refresh\n");
14487         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14488             ForwardInner(forwardMostMove);
14489         }
14490         pauseExamInvalid = FALSE;
14491     } else {
14492         switch (gameMode) {
14493           default:
14494             return;
14495           case IcsExamining:
14496             pauseExamForwardMostMove = forwardMostMove;
14497             pauseExamInvalid = FALSE;
14498             /* fall through */
14499           case IcsObserving:
14500           case IcsPlayingWhite:
14501           case IcsPlayingBlack:
14502             pausing = TRUE;
14503             ModeHighlight();
14504             return;
14505           case PlayFromGameFile:
14506             (void) StopLoadGameTimer();
14507             pausing = TRUE;
14508             ModeHighlight();
14509             break;
14510           case BeginningOfGame:
14511             if (appData.icsActive) return;
14512             /* else fall through */
14513           case MachinePlaysWhite:
14514           case MachinePlaysBlack:
14515           case TwoMachinesPlay:
14516             if (forwardMostMove == 0)
14517               return;           /* don't pause if no one has moved */
14518             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14519                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14520                 if(onMove->pause) {           // thinking engine can be paused
14521                     PauseEngine(onMove);      // do it
14522                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14523                         PauseEngine(onMove->other);
14524                     else
14525                         SendToProgram("easy\n", onMove->other);
14526                     StopClocks();
14527                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14528             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14529                 if(first.pause) {
14530                     PauseEngine(&first);
14531                     StopClocks();
14532                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14533             } else { // human on move, pause pondering by either method
14534                 if(first.pause)
14535                     PauseEngine(&first);
14536                 else if(appData.ponderNextMove)
14537                     SendToProgram("easy\n", &first);
14538                 StopClocks();
14539             }
14540             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14541           case AnalyzeMode:
14542             pausing = TRUE;
14543             ModeHighlight();
14544             break;
14545         }
14546     }
14547 }
14548
14549 void
14550 EditCommentEvent ()
14551 {
14552     char title[MSG_SIZ];
14553
14554     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14555       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14556     } else {
14557       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14558                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14559                parseList[currentMove - 1]);
14560     }
14561
14562     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14563 }
14564
14565
14566 void
14567 EditTagsEvent ()
14568 {
14569     char *tags = PGNTags(&gameInfo);
14570     bookUp = FALSE;
14571     EditTagsPopUp(tags, NULL);
14572     free(tags);
14573 }
14574
14575 void
14576 ToggleSecond ()
14577 {
14578   if(second.analyzing) {
14579     SendToProgram("exit\n", &second);
14580     second.analyzing = FALSE;
14581   } else {
14582     if (second.pr == NoProc) StartChessProgram(&second);
14583     InitChessProgram(&second, FALSE);
14584     FeedMovesToProgram(&second, currentMove);
14585
14586     SendToProgram("analyze\n", &second);
14587     second.analyzing = TRUE;
14588   }
14589 }
14590
14591 /* Toggle ShowThinking */
14592 void
14593 ToggleShowThinking()
14594 {
14595   appData.showThinking = !appData.showThinking;
14596   ShowThinkingEvent();
14597 }
14598
14599 int
14600 AnalyzeModeEvent ()
14601 {
14602     char buf[MSG_SIZ];
14603
14604     if (!first.analysisSupport) {
14605       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14606       DisplayError(buf, 0);
14607       return 0;
14608     }
14609     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14610     if (appData.icsActive) {
14611         if (gameMode != IcsObserving) {
14612           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14613             DisplayError(buf, 0);
14614             /* secure check */
14615             if (appData.icsEngineAnalyze) {
14616                 if (appData.debugMode)
14617                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14618                 ExitAnalyzeMode();
14619                 ModeHighlight();
14620             }
14621             return 0;
14622         }
14623         /* if enable, user wants to disable icsEngineAnalyze */
14624         if (appData.icsEngineAnalyze) {
14625                 ExitAnalyzeMode();
14626                 ModeHighlight();
14627                 return 0;
14628         }
14629         appData.icsEngineAnalyze = TRUE;
14630         if (appData.debugMode)
14631             fprintf(debugFP, "ICS engine analyze starting... \n");
14632     }
14633
14634     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14635     if (appData.noChessProgram || gameMode == AnalyzeMode)
14636       return 0;
14637
14638     if (gameMode != AnalyzeFile) {
14639         if (!appData.icsEngineAnalyze) {
14640                EditGameEvent();
14641                if (gameMode != EditGame) return 0;
14642         }
14643         if (!appData.showThinking) ToggleShowThinking();
14644         ResurrectChessProgram();
14645         SendToProgram("analyze\n", &first);
14646         first.analyzing = TRUE;
14647         /*first.maybeThinking = TRUE;*/
14648         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14649         EngineOutputPopUp();
14650     }
14651     if (!appData.icsEngineAnalyze) {
14652         gameMode = AnalyzeMode;
14653         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14654     }
14655     pausing = FALSE;
14656     ModeHighlight();
14657     SetGameInfo();
14658
14659     StartAnalysisClock();
14660     GetTimeMark(&lastNodeCountTime);
14661     lastNodeCount = 0;
14662     return 1;
14663 }
14664
14665 void
14666 AnalyzeFileEvent ()
14667 {
14668     if (appData.noChessProgram || gameMode == AnalyzeFile)
14669       return;
14670
14671     if (!first.analysisSupport) {
14672       char buf[MSG_SIZ];
14673       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14674       DisplayError(buf, 0);
14675       return;
14676     }
14677
14678     if (gameMode != AnalyzeMode) {
14679         keepInfo = 1; // mere annotating should not alter PGN tags
14680         EditGameEvent();
14681         keepInfo = 0;
14682         if (gameMode != EditGame) return;
14683         if (!appData.showThinking) ToggleShowThinking();
14684         ResurrectChessProgram();
14685         SendToProgram("analyze\n", &first);
14686         first.analyzing = TRUE;
14687         /*first.maybeThinking = TRUE;*/
14688         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14689         EngineOutputPopUp();
14690     }
14691     gameMode = AnalyzeFile;
14692     pausing = FALSE;
14693     ModeHighlight();
14694
14695     StartAnalysisClock();
14696     GetTimeMark(&lastNodeCountTime);
14697     lastNodeCount = 0;
14698     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14699     AnalysisPeriodicEvent(1);
14700 }
14701
14702 void
14703 MachineWhiteEvent ()
14704 {
14705     char buf[MSG_SIZ];
14706     char *bookHit = NULL;
14707
14708     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14709       return;
14710
14711
14712     if (gameMode == PlayFromGameFile ||
14713         gameMode == TwoMachinesPlay  ||
14714         gameMode == Training         ||
14715         gameMode == AnalyzeMode      ||
14716         gameMode == EndOfGame)
14717         EditGameEvent();
14718
14719     if (gameMode == EditPosition)
14720         EditPositionDone(TRUE);
14721
14722     if (!WhiteOnMove(currentMove)) {
14723         DisplayError(_("It is not White's turn"), 0);
14724         return;
14725     }
14726
14727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14728       ExitAnalyzeMode();
14729
14730     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14731         gameMode == AnalyzeFile)
14732         TruncateGame();
14733
14734     ResurrectChessProgram();    /* in case it isn't running */
14735     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14736         gameMode = MachinePlaysWhite;
14737         ResetClocks();
14738     } else
14739     gameMode = MachinePlaysWhite;
14740     pausing = FALSE;
14741     ModeHighlight();
14742     SetGameInfo();
14743     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14744     DisplayTitle(buf);
14745     if (first.sendName) {
14746       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14747       SendToProgram(buf, &first);
14748     }
14749     if (first.sendTime) {
14750       if (first.useColors) {
14751         SendToProgram("black\n", &first); /*gnu kludge*/
14752       }
14753       SendTimeRemaining(&first, TRUE);
14754     }
14755     if (first.useColors) {
14756       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14757     }
14758     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14759     SetMachineThinkingEnables();
14760     first.maybeThinking = TRUE;
14761     StartClocks();
14762     firstMove = FALSE;
14763
14764     if (appData.autoFlipView && !flipView) {
14765       flipView = !flipView;
14766       DrawPosition(FALSE, NULL);
14767       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14768     }
14769
14770     if(bookHit) { // [HGM] book: simulate book reply
14771         static char bookMove[MSG_SIZ]; // a bit generous?
14772
14773         programStats.nodes = programStats.depth = programStats.time =
14774         programStats.score = programStats.got_only_move = 0;
14775         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14776
14777         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14778         strcat(bookMove, bookHit);
14779         HandleMachineMove(bookMove, &first);
14780     }
14781 }
14782
14783 void
14784 MachineBlackEvent ()
14785 {
14786   char buf[MSG_SIZ];
14787   char *bookHit = NULL;
14788
14789     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14790         return;
14791
14792
14793     if (gameMode == PlayFromGameFile ||
14794         gameMode == TwoMachinesPlay  ||
14795         gameMode == Training         ||
14796         gameMode == AnalyzeMode      ||
14797         gameMode == EndOfGame)
14798         EditGameEvent();
14799
14800     if (gameMode == EditPosition)
14801         EditPositionDone(TRUE);
14802
14803     if (WhiteOnMove(currentMove)) {
14804         DisplayError(_("It is not Black's turn"), 0);
14805         return;
14806     }
14807
14808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14809       ExitAnalyzeMode();
14810
14811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14812         gameMode == AnalyzeFile)
14813         TruncateGame();
14814
14815     ResurrectChessProgram();    /* in case it isn't running */
14816     gameMode = MachinePlaysBlack;
14817     pausing = FALSE;
14818     ModeHighlight();
14819     SetGameInfo();
14820     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14821     DisplayTitle(buf);
14822     if (first.sendName) {
14823       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14824       SendToProgram(buf, &first);
14825     }
14826     if (first.sendTime) {
14827       if (first.useColors) {
14828         SendToProgram("white\n", &first); /*gnu kludge*/
14829       }
14830       SendTimeRemaining(&first, FALSE);
14831     }
14832     if (first.useColors) {
14833       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14834     }
14835     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14836     SetMachineThinkingEnables();
14837     first.maybeThinking = TRUE;
14838     StartClocks();
14839
14840     if (appData.autoFlipView && flipView) {
14841       flipView = !flipView;
14842       DrawPosition(FALSE, NULL);
14843       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14844     }
14845     if(bookHit) { // [HGM] book: simulate book reply
14846         static char bookMove[MSG_SIZ]; // a bit generous?
14847
14848         programStats.nodes = programStats.depth = programStats.time =
14849         programStats.score = programStats.got_only_move = 0;
14850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14851
14852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14853         strcat(bookMove, bookHit);
14854         HandleMachineMove(bookMove, &first);
14855     }
14856 }
14857
14858
14859 void
14860 DisplayTwoMachinesTitle ()
14861 {
14862     char buf[MSG_SIZ];
14863     if (appData.matchGames > 0) {
14864         if(appData.tourneyFile[0]) {
14865           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14866                    gameInfo.white, _("vs."), gameInfo.black,
14867                    nextGame+1, appData.matchGames+1,
14868                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14869         } else
14870         if (first.twoMachinesColor[0] == 'w') {
14871           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14872                    gameInfo.white, _("vs."),  gameInfo.black,
14873                    first.matchWins, second.matchWins,
14874                    matchGame - 1 - (first.matchWins + second.matchWins));
14875         } else {
14876           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14877                    gameInfo.white, _("vs."), gameInfo.black,
14878                    second.matchWins, first.matchWins,
14879                    matchGame - 1 - (first.matchWins + second.matchWins));
14880         }
14881     } else {
14882       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14883     }
14884     DisplayTitle(buf);
14885 }
14886
14887 void
14888 SettingsMenuIfReady ()
14889 {
14890   if (second.lastPing != second.lastPong) {
14891     DisplayMessage("", _("Waiting for second chess program"));
14892     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14893     return;
14894   }
14895   ThawUI();
14896   DisplayMessage("", "");
14897   SettingsPopUp(&second);
14898 }
14899
14900 int
14901 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14902 {
14903     char buf[MSG_SIZ];
14904     if (cps->pr == NoProc) {
14905         StartChessProgram(cps);
14906         if (cps->protocolVersion == 1) {
14907           retry();
14908           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14909         } else {
14910           /* kludge: allow timeout for initial "feature" command */
14911           if(retry != TwoMachinesEventIfReady) FreezeUI();
14912           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14913           DisplayMessage("", buf);
14914           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14915         }
14916         return 1;
14917     }
14918     return 0;
14919 }
14920
14921 void
14922 TwoMachinesEvent P((void))
14923 {
14924     int i;
14925     char buf[MSG_SIZ];
14926     ChessProgramState *onmove;
14927     char *bookHit = NULL;
14928     static int stalling = 0;
14929     TimeMark now;
14930     long wait;
14931
14932     if (appData.noChessProgram) return;
14933
14934     switch (gameMode) {
14935       case TwoMachinesPlay:
14936         return;
14937       case MachinePlaysWhite:
14938       case MachinePlaysBlack:
14939         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14940             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14941             return;
14942         }
14943         /* fall through */
14944       case BeginningOfGame:
14945       case PlayFromGameFile:
14946       case EndOfGame:
14947         EditGameEvent();
14948         if (gameMode != EditGame) return;
14949         break;
14950       case EditPosition:
14951         EditPositionDone(TRUE);
14952         break;
14953       case AnalyzeMode:
14954       case AnalyzeFile:
14955         ExitAnalyzeMode();
14956         break;
14957       case EditGame:
14958       default:
14959         break;
14960     }
14961
14962 //    forwardMostMove = currentMove;
14963     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14964     startingEngine = TRUE;
14965
14966     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14967
14968     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14969     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14970       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14971       return;
14972     }
14973     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14974
14975     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14976                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14977         startingEngine = matchMode = FALSE;
14978         DisplayError("second engine does not play this", 0);
14979         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14980         EditGameEvent(); // switch back to EditGame mode
14981         return;
14982     }
14983
14984     if(!stalling) {
14985       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14986       SendToProgram("force\n", &second);
14987       stalling = 1;
14988       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14989       return;
14990     }
14991     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14992     if(appData.matchPause>10000 || appData.matchPause<10)
14993                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14994     wait = SubtractTimeMarks(&now, &pauseStart);
14995     if(wait < appData.matchPause) {
14996         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14997         return;
14998     }
14999     // we are now committed to starting the game
15000     stalling = 0;
15001     DisplayMessage("", "");
15002     if (startedFromSetupPosition) {
15003         SendBoard(&second, backwardMostMove);
15004     if (appData.debugMode) {
15005         fprintf(debugFP, "Two Machines\n");
15006     }
15007     }
15008     for (i = backwardMostMove; i < forwardMostMove; i++) {
15009         SendMoveToProgram(i, &second);
15010     }
15011
15012     gameMode = TwoMachinesPlay;
15013     pausing = startingEngine = FALSE;
15014     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15015     SetGameInfo();
15016     DisplayTwoMachinesTitle();
15017     firstMove = TRUE;
15018     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15019         onmove = &first;
15020     } else {
15021         onmove = &second;
15022     }
15023     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15024     SendToProgram(first.computerString, &first);
15025     if (first.sendName) {
15026       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15027       SendToProgram(buf, &first);
15028     }
15029     SendToProgram(second.computerString, &second);
15030     if (second.sendName) {
15031       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15032       SendToProgram(buf, &second);
15033     }
15034
15035     ResetClocks();
15036     if (!first.sendTime || !second.sendTime) {
15037         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15038         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15039     }
15040     if (onmove->sendTime) {
15041       if (onmove->useColors) {
15042         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15043       }
15044       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15045     }
15046     if (onmove->useColors) {
15047       SendToProgram(onmove->twoMachinesColor, onmove);
15048     }
15049     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15050 //    SendToProgram("go\n", onmove);
15051     onmove->maybeThinking = TRUE;
15052     SetMachineThinkingEnables();
15053
15054     StartClocks();
15055
15056     if(bookHit) { // [HGM] book: simulate book reply
15057         static char bookMove[MSG_SIZ]; // a bit generous?
15058
15059         programStats.nodes = programStats.depth = programStats.time =
15060         programStats.score = programStats.got_only_move = 0;
15061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15062
15063         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15064         strcat(bookMove, bookHit);
15065         savedMessage = bookMove; // args for deferred call
15066         savedState = onmove;
15067         ScheduleDelayedEvent(DeferredBookMove, 1);
15068     }
15069 }
15070
15071 void
15072 TrainingEvent ()
15073 {
15074     if (gameMode == Training) {
15075       SetTrainingModeOff();
15076       gameMode = PlayFromGameFile;
15077       DisplayMessage("", _("Training mode off"));
15078     } else {
15079       gameMode = Training;
15080       animateTraining = appData.animate;
15081
15082       /* make sure we are not already at the end of the game */
15083       if (currentMove < forwardMostMove) {
15084         SetTrainingModeOn();
15085         DisplayMessage("", _("Training mode on"));
15086       } else {
15087         gameMode = PlayFromGameFile;
15088         DisplayError(_("Already at end of game"), 0);
15089       }
15090     }
15091     ModeHighlight();
15092 }
15093
15094 void
15095 IcsClientEvent ()
15096 {
15097     if (!appData.icsActive) return;
15098     switch (gameMode) {
15099       case IcsPlayingWhite:
15100       case IcsPlayingBlack:
15101       case IcsObserving:
15102       case IcsIdle:
15103       case BeginningOfGame:
15104       case IcsExamining:
15105         return;
15106
15107       case EditGame:
15108         break;
15109
15110       case EditPosition:
15111         EditPositionDone(TRUE);
15112         break;
15113
15114       case AnalyzeMode:
15115       case AnalyzeFile:
15116         ExitAnalyzeMode();
15117         break;
15118
15119       default:
15120         EditGameEvent();
15121         break;
15122     }
15123
15124     gameMode = IcsIdle;
15125     ModeHighlight();
15126     return;
15127 }
15128
15129 void
15130 EditGameEvent ()
15131 {
15132     int i;
15133
15134     switch (gameMode) {
15135       case Training:
15136         SetTrainingModeOff();
15137         break;
15138       case MachinePlaysWhite:
15139       case MachinePlaysBlack:
15140       case BeginningOfGame:
15141         SendToProgram("force\n", &first);
15142         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15143             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15144                 char buf[MSG_SIZ];
15145                 abortEngineThink = TRUE;
15146                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15147                 SendToProgram(buf, &first);
15148                 DisplayMessage("Aborting engine think", "");
15149                 FreezeUI();
15150             }
15151         }
15152         SetUserThinkingEnables();
15153         break;
15154       case PlayFromGameFile:
15155         (void) StopLoadGameTimer();
15156         if (gameFileFP != NULL) {
15157             gameFileFP = NULL;
15158         }
15159         break;
15160       case EditPosition:
15161         EditPositionDone(TRUE);
15162         break;
15163       case AnalyzeMode:
15164       case AnalyzeFile:
15165         ExitAnalyzeMode();
15166         SendToProgram("force\n", &first);
15167         break;
15168       case TwoMachinesPlay:
15169         GameEnds(EndOfFile, NULL, GE_PLAYER);
15170         ResurrectChessProgram();
15171         SetUserThinkingEnables();
15172         break;
15173       case EndOfGame:
15174         ResurrectChessProgram();
15175         break;
15176       case IcsPlayingBlack:
15177       case IcsPlayingWhite:
15178         DisplayError(_("Warning: You are still playing a game"), 0);
15179         break;
15180       case IcsObserving:
15181         DisplayError(_("Warning: You are still observing a game"), 0);
15182         break;
15183       case IcsExamining:
15184         DisplayError(_("Warning: You are still examining a game"), 0);
15185         break;
15186       case IcsIdle:
15187         break;
15188       case EditGame:
15189       default:
15190         return;
15191     }
15192
15193     pausing = FALSE;
15194     StopClocks();
15195     first.offeredDraw = second.offeredDraw = 0;
15196
15197     if (gameMode == PlayFromGameFile) {
15198         whiteTimeRemaining = timeRemaining[0][currentMove];
15199         blackTimeRemaining = timeRemaining[1][currentMove];
15200         DisplayTitle("");
15201     }
15202
15203     if (gameMode == MachinePlaysWhite ||
15204         gameMode == MachinePlaysBlack ||
15205         gameMode == TwoMachinesPlay ||
15206         gameMode == EndOfGame) {
15207         i = forwardMostMove;
15208         while (i > currentMove) {
15209             SendToProgram("undo\n", &first);
15210             i--;
15211         }
15212         if(!adjustedClock) {
15213         whiteTimeRemaining = timeRemaining[0][currentMove];
15214         blackTimeRemaining = timeRemaining[1][currentMove];
15215         DisplayBothClocks();
15216         }
15217         if (whiteFlag || blackFlag) {
15218             whiteFlag = blackFlag = 0;
15219         }
15220         DisplayTitle("");
15221     }
15222
15223     gameMode = EditGame;
15224     ModeHighlight();
15225     SetGameInfo();
15226 }
15227
15228
15229 void
15230 EditPositionEvent ()
15231 {
15232     if (gameMode == EditPosition) {
15233         EditGameEvent();
15234         return;
15235     }
15236
15237     EditGameEvent();
15238     if (gameMode != EditGame) return;
15239
15240     gameMode = EditPosition;
15241     ModeHighlight();
15242     SetGameInfo();
15243     if (currentMove > 0)
15244       CopyBoard(boards[0], boards[currentMove]);
15245
15246     blackPlaysFirst = !WhiteOnMove(currentMove);
15247     ResetClocks();
15248     currentMove = forwardMostMove = backwardMostMove = 0;
15249     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15250     DisplayMove(-1);
15251     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15252 }
15253
15254 void
15255 ExitAnalyzeMode ()
15256 {
15257     /* [DM] icsEngineAnalyze - possible call from other functions */
15258     if (appData.icsEngineAnalyze) {
15259         appData.icsEngineAnalyze = FALSE;
15260
15261         DisplayMessage("",_("Close ICS engine analyze..."));
15262     }
15263     if (first.analysisSupport && first.analyzing) {
15264       SendToBoth("exit\n");
15265       first.analyzing = second.analyzing = FALSE;
15266     }
15267     thinkOutput[0] = NULLCHAR;
15268 }
15269
15270 void
15271 EditPositionDone (Boolean fakeRights)
15272 {
15273     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15274
15275     startedFromSetupPosition = TRUE;
15276     InitChessProgram(&first, FALSE);
15277     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15278       boards[0][EP_STATUS] = EP_NONE;
15279       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15280       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15281         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15282         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15283       } else boards[0][CASTLING][2] = NoRights;
15284       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15285         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15286         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15287       } else boards[0][CASTLING][5] = NoRights;
15288       if(gameInfo.variant == VariantSChess) {
15289         int i;
15290         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15291           boards[0][VIRGIN][i] = 0;
15292           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15293           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15294         }
15295       }
15296     }
15297     SendToProgram("force\n", &first);
15298     if (blackPlaysFirst) {
15299         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15300         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15301         currentMove = forwardMostMove = backwardMostMove = 1;
15302         CopyBoard(boards[1], boards[0]);
15303     } else {
15304         currentMove = forwardMostMove = backwardMostMove = 0;
15305     }
15306     SendBoard(&first, forwardMostMove);
15307     if (appData.debugMode) {
15308         fprintf(debugFP, "EditPosDone\n");
15309     }
15310     DisplayTitle("");
15311     DisplayMessage("", "");
15312     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15313     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15314     gameMode = EditGame;
15315     ModeHighlight();
15316     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15317     ClearHighlights(); /* [AS] */
15318 }
15319
15320 /* Pause for `ms' milliseconds */
15321 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15322 void
15323 TimeDelay (long ms)
15324 {
15325     TimeMark m1, m2;
15326
15327     GetTimeMark(&m1);
15328     do {
15329         GetTimeMark(&m2);
15330     } while (SubtractTimeMarks(&m2, &m1) < ms);
15331 }
15332
15333 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15334 void
15335 SendMultiLineToICS (char *buf)
15336 {
15337     char temp[MSG_SIZ+1], *p;
15338     int len;
15339
15340     len = strlen(buf);
15341     if (len > MSG_SIZ)
15342       len = MSG_SIZ;
15343
15344     strncpy(temp, buf, len);
15345     temp[len] = 0;
15346
15347     p = temp;
15348     while (*p) {
15349         if (*p == '\n' || *p == '\r')
15350           *p = ' ';
15351         ++p;
15352     }
15353
15354     strcat(temp, "\n");
15355     SendToICS(temp);
15356     SendToPlayer(temp, strlen(temp));
15357 }
15358
15359 void
15360 SetWhiteToPlayEvent ()
15361 {
15362     if (gameMode == EditPosition) {
15363         blackPlaysFirst = FALSE;
15364         DisplayBothClocks();    /* works because currentMove is 0 */
15365     } else if (gameMode == IcsExamining) {
15366         SendToICS(ics_prefix);
15367         SendToICS("tomove white\n");
15368     }
15369 }
15370
15371 void
15372 SetBlackToPlayEvent ()
15373 {
15374     if (gameMode == EditPosition) {
15375         blackPlaysFirst = TRUE;
15376         currentMove = 1;        /* kludge */
15377         DisplayBothClocks();
15378         currentMove = 0;
15379     } else if (gameMode == IcsExamining) {
15380         SendToICS(ics_prefix);
15381         SendToICS("tomove black\n");
15382     }
15383 }
15384
15385 void
15386 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15387 {
15388     char buf[MSG_SIZ];
15389     ChessSquare piece = boards[0][y][x];
15390     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15391     static int lastVariant;
15392
15393     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15394
15395     switch (selection) {
15396       case ClearBoard:
15397         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15398         MarkTargetSquares(1);
15399         CopyBoard(currentBoard, boards[0]);
15400         CopyBoard(menuBoard, initialPosition);
15401         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15402             SendToICS(ics_prefix);
15403             SendToICS("bsetup clear\n");
15404         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15405             SendToICS(ics_prefix);
15406             SendToICS("clearboard\n");
15407         } else {
15408             int nonEmpty = 0;
15409             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15410                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15411                 for (y = 0; y < BOARD_HEIGHT; y++) {
15412                     if (gameMode == IcsExamining) {
15413                         if (boards[currentMove][y][x] != EmptySquare) {
15414                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15415                                     AAA + x, ONE + y);
15416                             SendToICS(buf);
15417                         }
15418                     } else if(boards[0][y][x] != DarkSquare) {
15419                         if(boards[0][y][x] != p) nonEmpty++;
15420                         boards[0][y][x] = p;
15421                     }
15422                 }
15423             }
15424             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15425                 int r;
15426                 for(r = 0; r < BOARD_HEIGHT; r++) {
15427                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15428                     ChessSquare p = menuBoard[r][x];
15429                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15430                   }
15431                 }
15432                 DisplayMessage("Clicking clock again restores position", "");
15433                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15434                 if(!nonEmpty) { // asked to clear an empty board
15435                     CopyBoard(boards[0], menuBoard);
15436                 } else
15437                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15438                     CopyBoard(boards[0], initialPosition);
15439                 } else
15440                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15441                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15442                     CopyBoard(boards[0], erasedBoard);
15443                 } else
15444                     CopyBoard(erasedBoard, currentBoard);
15445
15446             }
15447         }
15448         if (gameMode == EditPosition) {
15449             DrawPosition(FALSE, boards[0]);
15450         }
15451         break;
15452
15453       case WhitePlay:
15454         SetWhiteToPlayEvent();
15455         break;
15456
15457       case BlackPlay:
15458         SetBlackToPlayEvent();
15459         break;
15460
15461       case EmptySquare:
15462         if (gameMode == IcsExamining) {
15463             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15464             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15465             SendToICS(buf);
15466         } else {
15467             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15468                 if(x == BOARD_LEFT-2) {
15469                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15470                     boards[0][y][1] = 0;
15471                 } else
15472                 if(x == BOARD_RGHT+1) {
15473                     if(y >= gameInfo.holdingsSize) break;
15474                     boards[0][y][BOARD_WIDTH-2] = 0;
15475                 } else break;
15476             }
15477             boards[0][y][x] = EmptySquare;
15478             DrawPosition(FALSE, boards[0]);
15479         }
15480         break;
15481
15482       case PromotePiece:
15483         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15484            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15485             selection = (ChessSquare) (PROMOTED(piece));
15486         } else if(piece == EmptySquare) selection = WhiteSilver;
15487         else selection = (ChessSquare)((int)piece - 1);
15488         goto defaultlabel;
15489
15490       case DemotePiece:
15491         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15492            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15493             selection = (ChessSquare) (DEMOTED(piece));
15494         } else if(piece == EmptySquare) selection = BlackSilver;
15495         else selection = (ChessSquare)((int)piece + 1);
15496         goto defaultlabel;
15497
15498       case WhiteQueen:
15499       case BlackQueen:
15500         if(gameInfo.variant == VariantShatranj ||
15501            gameInfo.variant == VariantXiangqi  ||
15502            gameInfo.variant == VariantCourier  ||
15503            gameInfo.variant == VariantASEAN    ||
15504            gameInfo.variant == VariantMakruk     )
15505             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15506         goto defaultlabel;
15507
15508       case WhiteKing:
15509       case BlackKing:
15510         if(gameInfo.variant == VariantXiangqi)
15511             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15512         if(gameInfo.variant == VariantKnightmate)
15513             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15514       default:
15515         defaultlabel:
15516         if (gameMode == IcsExamining) {
15517             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15518             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15519                      PieceToChar(selection), AAA + x, ONE + y);
15520             SendToICS(buf);
15521         } else {
15522             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15523                 int n;
15524                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15525                     n = PieceToNumber(selection - BlackPawn);
15526                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15527                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15528                     boards[0][BOARD_HEIGHT-1-n][1]++;
15529                 } else
15530                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15531                     n = PieceToNumber(selection);
15532                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15533                     boards[0][n][BOARD_WIDTH-1] = selection;
15534                     boards[0][n][BOARD_WIDTH-2]++;
15535                 }
15536             } else
15537             boards[0][y][x] = selection;
15538             DrawPosition(TRUE, boards[0]);
15539             ClearHighlights();
15540             fromX = fromY = -1;
15541         }
15542         break;
15543     }
15544 }
15545
15546
15547 void
15548 DropMenuEvent (ChessSquare selection, int x, int y)
15549 {
15550     ChessMove moveType;
15551
15552     switch (gameMode) {
15553       case IcsPlayingWhite:
15554       case MachinePlaysBlack:
15555         if (!WhiteOnMove(currentMove)) {
15556             DisplayMoveError(_("It is Black's turn"));
15557             return;
15558         }
15559         moveType = WhiteDrop;
15560         break;
15561       case IcsPlayingBlack:
15562       case MachinePlaysWhite:
15563         if (WhiteOnMove(currentMove)) {
15564             DisplayMoveError(_("It is White's turn"));
15565             return;
15566         }
15567         moveType = BlackDrop;
15568         break;
15569       case EditGame:
15570         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15571         break;
15572       default:
15573         return;
15574     }
15575
15576     if (moveType == BlackDrop && selection < BlackPawn) {
15577       selection = (ChessSquare) ((int) selection
15578                                  + (int) BlackPawn - (int) WhitePawn);
15579     }
15580     if (boards[currentMove][y][x] != EmptySquare) {
15581         DisplayMoveError(_("That square is occupied"));
15582         return;
15583     }
15584
15585     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15586 }
15587
15588 void
15589 AcceptEvent ()
15590 {
15591     /* Accept a pending offer of any kind from opponent */
15592
15593     if (appData.icsActive) {
15594         SendToICS(ics_prefix);
15595         SendToICS("accept\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             TruncateGame();
15602             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15603             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15604         } else {
15605             DisplayError(_("There is no pending offer on this move"), 0);
15606             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15607         }
15608     } else {
15609         /* Not used for offers from chess program */
15610     }
15611 }
15612
15613 void
15614 DeclineEvent ()
15615 {
15616     /* Decline a pending offer of any kind from opponent */
15617
15618     if (appData.icsActive) {
15619         SendToICS(ics_prefix);
15620         SendToICS("decline\n");
15621     } else if (cmailMsgLoaded) {
15622         if (currentMove == cmailOldMove &&
15623             commentList[cmailOldMove] != NULL &&
15624             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15625                    "Black offers a draw" : "White offers a draw")) {
15626 #ifdef NOTDEF
15627             AppendComment(cmailOldMove, "Draw declined", TRUE);
15628             DisplayComment(cmailOldMove - 1, "Draw declined");
15629 #endif /*NOTDEF*/
15630         } else {
15631             DisplayError(_("There is no pending offer on this move"), 0);
15632         }
15633     } else {
15634         /* Not used for offers from chess program */
15635     }
15636 }
15637
15638 void
15639 RematchEvent ()
15640 {
15641     /* Issue ICS rematch command */
15642     if (appData.icsActive) {
15643         SendToICS(ics_prefix);
15644         SendToICS("rematch\n");
15645     }
15646 }
15647
15648 void
15649 CallFlagEvent ()
15650 {
15651     /* Call your opponent's flag (claim a win on time) */
15652     if (appData.icsActive) {
15653         SendToICS(ics_prefix);
15654         SendToICS("flag\n");
15655     } else {
15656         switch (gameMode) {
15657           default:
15658             return;
15659           case MachinePlaysWhite:
15660             if (whiteFlag) {
15661                 if (blackFlag)
15662                   GameEnds(GameIsDrawn, "Both players ran out of time",
15663                            GE_PLAYER);
15664                 else
15665                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15666             } else {
15667                 DisplayError(_("Your opponent is not out of time"), 0);
15668             }
15669             break;
15670           case MachinePlaysBlack:
15671             if (blackFlag) {
15672                 if (whiteFlag)
15673                   GameEnds(GameIsDrawn, "Both players ran out of time",
15674                            GE_PLAYER);
15675                 else
15676                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15677             } else {
15678                 DisplayError(_("Your opponent is not out of time"), 0);
15679             }
15680             break;
15681         }
15682     }
15683 }
15684
15685 void
15686 ClockClick (int which)
15687 {       // [HGM] code moved to back-end from winboard.c
15688         if(which) { // black clock
15689           if (gameMode == EditPosition || gameMode == IcsExamining) {
15690             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15691             SetBlackToPlayEvent();
15692           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15693                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15694           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15695           } else if (shiftKey) {
15696             AdjustClock(which, -1);
15697           } else if (gameMode == IcsPlayingWhite ||
15698                      gameMode == MachinePlaysBlack) {
15699             CallFlagEvent();
15700           }
15701         } else { // white clock
15702           if (gameMode == EditPosition || gameMode == IcsExamining) {
15703             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15704             SetWhiteToPlayEvent();
15705           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15706                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15707           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15708           } else if (shiftKey) {
15709             AdjustClock(which, -1);
15710           } else if (gameMode == IcsPlayingBlack ||
15711                    gameMode == MachinePlaysWhite) {
15712             CallFlagEvent();
15713           }
15714         }
15715 }
15716
15717 void
15718 DrawEvent ()
15719 {
15720     /* Offer draw or accept pending draw offer from opponent */
15721
15722     if (appData.icsActive) {
15723         /* Note: tournament rules require draw offers to be
15724            made after you make your move but before you punch
15725            your clock.  Currently ICS doesn't let you do that;
15726            instead, you immediately punch your clock after making
15727            a move, but you can offer a draw at any time. */
15728
15729         SendToICS(ics_prefix);
15730         SendToICS("draw\n");
15731         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15732     } else if (cmailMsgLoaded) {
15733         if (currentMove == cmailOldMove &&
15734             commentList[cmailOldMove] != NULL &&
15735             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15736                    "Black offers a draw" : "White offers a draw")) {
15737             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15738             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15739         } else if (currentMove == cmailOldMove + 1) {
15740             char *offer = WhiteOnMove(cmailOldMove) ?
15741               "White offers a draw" : "Black offers a draw";
15742             AppendComment(currentMove, offer, TRUE);
15743             DisplayComment(currentMove - 1, offer);
15744             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15745         } else {
15746             DisplayError(_("You must make your move before offering a draw"), 0);
15747             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15748         }
15749     } else if (first.offeredDraw) {
15750         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15751     } else {
15752         if (first.sendDrawOffers) {
15753             SendToProgram("draw\n", &first);
15754             userOfferedDraw = TRUE;
15755         }
15756     }
15757 }
15758
15759 void
15760 AdjournEvent ()
15761 {
15762     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15763
15764     if (appData.icsActive) {
15765         SendToICS(ics_prefix);
15766         SendToICS("adjourn\n");
15767     } else {
15768         /* Currently GNU Chess doesn't offer or accept Adjourns */
15769     }
15770 }
15771
15772
15773 void
15774 AbortEvent ()
15775 {
15776     /* Offer Abort or accept pending Abort offer from opponent */
15777
15778     if (appData.icsActive) {
15779         SendToICS(ics_prefix);
15780         SendToICS("abort\n");
15781     } else {
15782         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15783     }
15784 }
15785
15786 void
15787 ResignEvent ()
15788 {
15789     /* Resign.  You can do this even if it's not your turn. */
15790
15791     if (appData.icsActive) {
15792         SendToICS(ics_prefix);
15793         SendToICS("resign\n");
15794     } else {
15795         switch (gameMode) {
15796           case MachinePlaysWhite:
15797             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15798             break;
15799           case MachinePlaysBlack:
15800             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15801             break;
15802           case EditGame:
15803             if (cmailMsgLoaded) {
15804                 TruncateGame();
15805                 if (WhiteOnMove(cmailOldMove)) {
15806                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15807                 } else {
15808                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15809                 }
15810                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15811             }
15812             break;
15813           default:
15814             break;
15815         }
15816     }
15817 }
15818
15819
15820 void
15821 StopObservingEvent ()
15822 {
15823     /* Stop observing current games */
15824     SendToICS(ics_prefix);
15825     SendToICS("unobserve\n");
15826 }
15827
15828 void
15829 StopExaminingEvent ()
15830 {
15831     /* Stop observing current game */
15832     SendToICS(ics_prefix);
15833     SendToICS("unexamine\n");
15834 }
15835
15836 void
15837 ForwardInner (int target)
15838 {
15839     int limit; int oldSeekGraphUp = seekGraphUp;
15840
15841     if (appData.debugMode)
15842         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15843                 target, currentMove, forwardMostMove);
15844
15845     if (gameMode == EditPosition)
15846       return;
15847
15848     seekGraphUp = FALSE;
15849     MarkTargetSquares(1);
15850     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15851
15852     if (gameMode == PlayFromGameFile && !pausing)
15853       PauseEvent();
15854
15855     if (gameMode == IcsExamining && pausing)
15856       limit = pauseExamForwardMostMove;
15857     else
15858       limit = forwardMostMove;
15859
15860     if (target > limit) target = limit;
15861
15862     if (target > 0 && moveList[target - 1][0]) {
15863         int fromX, fromY, toX, toY;
15864         toX = moveList[target - 1][2] - AAA;
15865         toY = moveList[target - 1][3] - ONE;
15866         if (moveList[target - 1][1] == '@') {
15867             if (appData.highlightLastMove) {
15868                 SetHighlights(-1, -1, toX, toY);
15869             }
15870         } else {
15871             int viaX = moveList[target - 1][5] - AAA;
15872             int viaY = moveList[target - 1][6] - ONE;
15873             fromX = moveList[target - 1][0] - AAA;
15874             fromY = moveList[target - 1][1] - ONE;
15875             if (target == currentMove + 1) {
15876                 if(moveList[target - 1][4] == ';') { // multi-leg
15877                     ChessSquare piece = boards[currentMove][viaY][viaX];
15878                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15879                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15880                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15881                     boards[currentMove][viaY][viaX] = piece;
15882                 } else
15883                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15884             }
15885             if (appData.highlightLastMove) {
15886                 SetHighlights(fromX, fromY, toX, toY);
15887             }
15888         }
15889     }
15890     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15891         gameMode == Training || gameMode == PlayFromGameFile ||
15892         gameMode == AnalyzeFile) {
15893         while (currentMove < target) {
15894             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15895             SendMoveToProgram(currentMove++, &first);
15896         }
15897     } else {
15898         currentMove = target;
15899     }
15900
15901     if (gameMode == EditGame || gameMode == EndOfGame) {
15902         whiteTimeRemaining = timeRemaining[0][currentMove];
15903         blackTimeRemaining = timeRemaining[1][currentMove];
15904     }
15905     DisplayBothClocks();
15906     DisplayMove(currentMove - 1);
15907     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15908     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15909     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15910         DisplayComment(currentMove - 1, commentList[currentMove]);
15911     }
15912     ClearMap(); // [HGM] exclude: invalidate map
15913 }
15914
15915
15916 void
15917 ForwardEvent ()
15918 {
15919     if (gameMode == IcsExamining && !pausing) {
15920         SendToICS(ics_prefix);
15921         SendToICS("forward\n");
15922     } else {
15923         ForwardInner(currentMove + 1);
15924     }
15925 }
15926
15927 void
15928 ToEndEvent ()
15929 {
15930     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15931         /* to optimze, we temporarily turn off analysis mode while we feed
15932          * the remaining moves to the engine. Otherwise we get analysis output
15933          * after each move.
15934          */
15935         if (first.analysisSupport) {
15936           SendToProgram("exit\nforce\n", &first);
15937           first.analyzing = FALSE;
15938         }
15939     }
15940
15941     if (gameMode == IcsExamining && !pausing) {
15942         SendToICS(ics_prefix);
15943         SendToICS("forward 999999\n");
15944     } else {
15945         ForwardInner(forwardMostMove);
15946     }
15947
15948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15949         /* we have fed all the moves, so reactivate analysis mode */
15950         SendToProgram("analyze\n", &first);
15951         first.analyzing = TRUE;
15952         /*first.maybeThinking = TRUE;*/
15953         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15954     }
15955 }
15956
15957 void
15958 BackwardInner (int target)
15959 {
15960     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15961
15962     if (appData.debugMode)
15963         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15964                 target, currentMove, forwardMostMove);
15965
15966     if (gameMode == EditPosition) return;
15967     seekGraphUp = FALSE;
15968     MarkTargetSquares(1);
15969     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15970     if (currentMove <= backwardMostMove) {
15971         ClearHighlights();
15972         DrawPosition(full_redraw, boards[currentMove]);
15973         return;
15974     }
15975     if (gameMode == PlayFromGameFile && !pausing)
15976       PauseEvent();
15977
15978     if (moveList[target][0]) {
15979         int fromX, fromY, toX, toY;
15980         toX = moveList[target][2] - AAA;
15981         toY = moveList[target][3] - ONE;
15982         if (moveList[target][1] == '@') {
15983             if (appData.highlightLastMove) {
15984                 SetHighlights(-1, -1, toX, toY);
15985             }
15986         } else {
15987             fromX = moveList[target][0] - AAA;
15988             fromY = moveList[target][1] - ONE;
15989             if (target == currentMove - 1) {
15990                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15991             }
15992             if (appData.highlightLastMove) {
15993                 SetHighlights(fromX, fromY, toX, toY);
15994             }
15995         }
15996     }
15997     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15998         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15999         while (currentMove > target) {
16000             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16001                 // null move cannot be undone. Reload program with move history before it.
16002                 int i;
16003                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16004                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16005                 }
16006                 SendBoard(&first, i);
16007               if(second.analyzing) SendBoard(&second, i);
16008                 for(currentMove=i; currentMove<target; currentMove++) {
16009                     SendMoveToProgram(currentMove, &first);
16010                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16011                 }
16012                 break;
16013             }
16014             SendToBoth("undo\n");
16015             currentMove--;
16016         }
16017     } else {
16018         currentMove = target;
16019     }
16020
16021     if (gameMode == EditGame || gameMode == EndOfGame) {
16022         whiteTimeRemaining = timeRemaining[0][currentMove];
16023         blackTimeRemaining = timeRemaining[1][currentMove];
16024     }
16025     DisplayBothClocks();
16026     DisplayMove(currentMove - 1);
16027     DrawPosition(full_redraw, boards[currentMove]);
16028     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16029     // [HGM] PV info: routine tests if comment empty
16030     DisplayComment(currentMove - 1, commentList[currentMove]);
16031     ClearMap(); // [HGM] exclude: invalidate map
16032 }
16033
16034 void
16035 BackwardEvent ()
16036 {
16037     if (gameMode == IcsExamining && !pausing) {
16038         SendToICS(ics_prefix);
16039         SendToICS("backward\n");
16040     } else {
16041         BackwardInner(currentMove - 1);
16042     }
16043 }
16044
16045 void
16046 ToStartEvent ()
16047 {
16048     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16049         /* to optimize, we temporarily turn off analysis mode while we undo
16050          * all the moves. Otherwise we get analysis output after each undo.
16051          */
16052         if (first.analysisSupport) {
16053           SendToProgram("exit\nforce\n", &first);
16054           first.analyzing = FALSE;
16055         }
16056     }
16057
16058     if (gameMode == IcsExamining && !pausing) {
16059         SendToICS(ics_prefix);
16060         SendToICS("backward 999999\n");
16061     } else {
16062         BackwardInner(backwardMostMove);
16063     }
16064
16065     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16066         /* we have fed all the moves, so reactivate analysis mode */
16067         SendToProgram("analyze\n", &first);
16068         first.analyzing = TRUE;
16069         /*first.maybeThinking = TRUE;*/
16070         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16071     }
16072 }
16073
16074 void
16075 ToNrEvent (int to)
16076 {
16077   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16078   if (to >= forwardMostMove) to = forwardMostMove;
16079   if (to <= backwardMostMove) to = backwardMostMove;
16080   if (to < currentMove) {
16081     BackwardInner(to);
16082   } else {
16083     ForwardInner(to);
16084   }
16085 }
16086
16087 void
16088 RevertEvent (Boolean annotate)
16089 {
16090     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16091         return;
16092     }
16093     if (gameMode != IcsExamining) {
16094         DisplayError(_("You are not examining a game"), 0);
16095         return;
16096     }
16097     if (pausing) {
16098         DisplayError(_("You can't revert while pausing"), 0);
16099         return;
16100     }
16101     SendToICS(ics_prefix);
16102     SendToICS("revert\n");
16103 }
16104
16105 void
16106 RetractMoveEvent ()
16107 {
16108     switch (gameMode) {
16109       case MachinePlaysWhite:
16110       case MachinePlaysBlack:
16111         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16112             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16113             return;
16114         }
16115         if (forwardMostMove < 2) return;
16116         currentMove = forwardMostMove = forwardMostMove - 2;
16117         whiteTimeRemaining = timeRemaining[0][currentMove];
16118         blackTimeRemaining = timeRemaining[1][currentMove];
16119         DisplayBothClocks();
16120         DisplayMove(currentMove - 1);
16121         ClearHighlights();/*!! could figure this out*/
16122         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16123         SendToProgram("remove\n", &first);
16124         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16125         break;
16126
16127       case BeginningOfGame:
16128       default:
16129         break;
16130
16131       case IcsPlayingWhite:
16132       case IcsPlayingBlack:
16133         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16134             SendToICS(ics_prefix);
16135             SendToICS("takeback 2\n");
16136         } else {
16137             SendToICS(ics_prefix);
16138             SendToICS("takeback 1\n");
16139         }
16140         break;
16141     }
16142 }
16143
16144 void
16145 MoveNowEvent ()
16146 {
16147     ChessProgramState *cps;
16148
16149     switch (gameMode) {
16150       case MachinePlaysWhite:
16151         if (!WhiteOnMove(forwardMostMove)) {
16152             DisplayError(_("It is your turn"), 0);
16153             return;
16154         }
16155         cps = &first;
16156         break;
16157       case MachinePlaysBlack:
16158         if (WhiteOnMove(forwardMostMove)) {
16159             DisplayError(_("It is your turn"), 0);
16160             return;
16161         }
16162         cps = &first;
16163         break;
16164       case TwoMachinesPlay:
16165         if (WhiteOnMove(forwardMostMove) ==
16166             (first.twoMachinesColor[0] == 'w')) {
16167             cps = &first;
16168         } else {
16169             cps = &second;
16170         }
16171         break;
16172       case BeginningOfGame:
16173       default:
16174         return;
16175     }
16176     SendToProgram("?\n", cps);
16177 }
16178
16179 void
16180 TruncateGameEvent ()
16181 {
16182     EditGameEvent();
16183     if (gameMode != EditGame) return;
16184     TruncateGame();
16185 }
16186
16187 void
16188 TruncateGame ()
16189 {
16190     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16191     if (forwardMostMove > currentMove) {
16192         if (gameInfo.resultDetails != NULL) {
16193             free(gameInfo.resultDetails);
16194             gameInfo.resultDetails = NULL;
16195             gameInfo.result = GameUnfinished;
16196         }
16197         forwardMostMove = currentMove;
16198         HistorySet(parseList, backwardMostMove, forwardMostMove,
16199                    currentMove-1);
16200     }
16201 }
16202
16203 void
16204 HintEvent ()
16205 {
16206     if (appData.noChessProgram) return;
16207     switch (gameMode) {
16208       case MachinePlaysWhite:
16209         if (WhiteOnMove(forwardMostMove)) {
16210             DisplayError(_("Wait until your turn."), 0);
16211             return;
16212         }
16213         break;
16214       case BeginningOfGame:
16215       case MachinePlaysBlack:
16216         if (!WhiteOnMove(forwardMostMove)) {
16217             DisplayError(_("Wait until your turn."), 0);
16218             return;
16219         }
16220         break;
16221       default:
16222         DisplayError(_("No hint available"), 0);
16223         return;
16224     }
16225     SendToProgram("hint\n", &first);
16226     hintRequested = TRUE;
16227 }
16228
16229 int
16230 SaveSelected (FILE *g, int dummy, char *dummy2)
16231 {
16232     ListGame * lg = (ListGame *) gameList.head;
16233     int nItem, cnt=0;
16234     FILE *f;
16235
16236     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16237         DisplayError(_("Game list not loaded or empty"), 0);
16238         return 0;
16239     }
16240
16241     creatingBook = TRUE; // suppresses stuff during load game
16242
16243     /* Get list size */
16244     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16245         if(lg->position >= 0) { // selected?
16246             LoadGame(f, nItem, "", TRUE);
16247             SaveGamePGN2(g); // leaves g open
16248             cnt++; DoEvents();
16249         }
16250         lg = (ListGame *) lg->node.succ;
16251     }
16252
16253     fclose(g);
16254     creatingBook = FALSE;
16255
16256     return cnt;
16257 }
16258
16259 void
16260 CreateBookEvent ()
16261 {
16262     ListGame * lg = (ListGame *) gameList.head;
16263     FILE *f, *g;
16264     int nItem;
16265     static int secondTime = FALSE;
16266
16267     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16268         DisplayError(_("Game list not loaded or empty"), 0);
16269         return;
16270     }
16271
16272     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16273         fclose(g);
16274         secondTime++;
16275         DisplayNote(_("Book file exists! Try again for overwrite."));
16276         return;
16277     }
16278
16279     creatingBook = TRUE;
16280     secondTime = FALSE;
16281
16282     /* Get list size */
16283     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16284         if(lg->position >= 0) {
16285             LoadGame(f, nItem, "", TRUE);
16286             AddGameToBook(TRUE);
16287             DoEvents();
16288         }
16289         lg = (ListGame *) lg->node.succ;
16290     }
16291
16292     creatingBook = FALSE;
16293     FlushBook();
16294 }
16295
16296 void
16297 BookEvent ()
16298 {
16299     if (appData.noChessProgram) return;
16300     switch (gameMode) {
16301       case MachinePlaysWhite:
16302         if (WhiteOnMove(forwardMostMove)) {
16303             DisplayError(_("Wait until your turn."), 0);
16304             return;
16305         }
16306         break;
16307       case BeginningOfGame:
16308       case MachinePlaysBlack:
16309         if (!WhiteOnMove(forwardMostMove)) {
16310             DisplayError(_("Wait until your turn."), 0);
16311             return;
16312         }
16313         break;
16314       case EditPosition:
16315         EditPositionDone(TRUE);
16316         break;
16317       case TwoMachinesPlay:
16318         return;
16319       default:
16320         break;
16321     }
16322     SendToProgram("bk\n", &first);
16323     bookOutput[0] = NULLCHAR;
16324     bookRequested = TRUE;
16325 }
16326
16327 void
16328 AboutGameEvent ()
16329 {
16330     char *tags = PGNTags(&gameInfo);
16331     TagsPopUp(tags, CmailMsg());
16332     free(tags);
16333 }
16334
16335 /* end button procedures */
16336
16337 void
16338 PrintPosition (FILE *fp, int move)
16339 {
16340     int i, j;
16341
16342     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16343         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16344             char c = PieceToChar(boards[move][i][j]);
16345             fputc(c == '?' ? '.' : c, fp);
16346             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16347         }
16348     }
16349     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16350       fprintf(fp, "white to play\n");
16351     else
16352       fprintf(fp, "black to play\n");
16353 }
16354
16355 void
16356 PrintOpponents (FILE *fp)
16357 {
16358     if (gameInfo.white != NULL) {
16359         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16360     } else {
16361         fprintf(fp, "\n");
16362     }
16363 }
16364
16365 /* Find last component of program's own name, using some heuristics */
16366 void
16367 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16368 {
16369     char *p, *q, c;
16370     int local = (strcmp(host, "localhost") == 0);
16371     while (!local && (p = strchr(prog, ';')) != NULL) {
16372         p++;
16373         while (*p == ' ') p++;
16374         prog = p;
16375     }
16376     if (*prog == '"' || *prog == '\'') {
16377         q = strchr(prog + 1, *prog);
16378     } else {
16379         q = strchr(prog, ' ');
16380     }
16381     if (q == NULL) q = prog + strlen(prog);
16382     p = q;
16383     while (p >= prog && *p != '/' && *p != '\\') p--;
16384     p++;
16385     if(p == prog && *p == '"') p++;
16386     c = *q; *q = 0;
16387     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16388     memcpy(buf, p, q - p);
16389     buf[q - p] = NULLCHAR;
16390     if (!local) {
16391         strcat(buf, "@");
16392         strcat(buf, host);
16393     }
16394 }
16395
16396 char *
16397 TimeControlTagValue ()
16398 {
16399     char buf[MSG_SIZ];
16400     if (!appData.clockMode) {
16401       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16402     } else if (movesPerSession > 0) {
16403       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16404     } else if (timeIncrement == 0) {
16405       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16406     } else {
16407       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16408     }
16409     return StrSave(buf);
16410 }
16411
16412 void
16413 SetGameInfo ()
16414 {
16415     /* This routine is used only for certain modes */
16416     VariantClass v = gameInfo.variant;
16417     ChessMove r = GameUnfinished;
16418     char *p = NULL;
16419
16420     if(keepInfo) return;
16421
16422     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16423         r = gameInfo.result;
16424         p = gameInfo.resultDetails;
16425         gameInfo.resultDetails = NULL;
16426     }
16427     ClearGameInfo(&gameInfo);
16428     gameInfo.variant = v;
16429
16430     switch (gameMode) {
16431       case MachinePlaysWhite:
16432         gameInfo.event = StrSave( appData.pgnEventHeader );
16433         gameInfo.site = StrSave(HostName());
16434         gameInfo.date = PGNDate();
16435         gameInfo.round = StrSave("-");
16436         gameInfo.white = StrSave(first.tidy);
16437         gameInfo.black = StrSave(UserName());
16438         gameInfo.timeControl = TimeControlTagValue();
16439         break;
16440
16441       case MachinePlaysBlack:
16442         gameInfo.event = StrSave( appData.pgnEventHeader );
16443         gameInfo.site = StrSave(HostName());
16444         gameInfo.date = PGNDate();
16445         gameInfo.round = StrSave("-");
16446         gameInfo.white = StrSave(UserName());
16447         gameInfo.black = StrSave(first.tidy);
16448         gameInfo.timeControl = TimeControlTagValue();
16449         break;
16450
16451       case TwoMachinesPlay:
16452         gameInfo.event = StrSave( appData.pgnEventHeader );
16453         gameInfo.site = StrSave(HostName());
16454         gameInfo.date = PGNDate();
16455         if (roundNr > 0) {
16456             char buf[MSG_SIZ];
16457             snprintf(buf, MSG_SIZ, "%d", roundNr);
16458             gameInfo.round = StrSave(buf);
16459         } else {
16460             gameInfo.round = StrSave("-");
16461         }
16462         if (first.twoMachinesColor[0] == 'w') {
16463             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16464             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16465         } else {
16466             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16467             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16468         }
16469         gameInfo.timeControl = TimeControlTagValue();
16470         break;
16471
16472       case EditGame:
16473         gameInfo.event = StrSave("Edited game");
16474         gameInfo.site = StrSave(HostName());
16475         gameInfo.date = PGNDate();
16476         gameInfo.round = StrSave("-");
16477         gameInfo.white = StrSave("-");
16478         gameInfo.black = StrSave("-");
16479         gameInfo.result = r;
16480         gameInfo.resultDetails = p;
16481         break;
16482
16483       case EditPosition:
16484         gameInfo.event = StrSave("Edited position");
16485         gameInfo.site = StrSave(HostName());
16486         gameInfo.date = PGNDate();
16487         gameInfo.round = StrSave("-");
16488         gameInfo.white = StrSave("-");
16489         gameInfo.black = StrSave("-");
16490         break;
16491
16492       case IcsPlayingWhite:
16493       case IcsPlayingBlack:
16494       case IcsObserving:
16495       case IcsExamining:
16496         break;
16497
16498       case PlayFromGameFile:
16499         gameInfo.event = StrSave("Game from non-PGN file");
16500         gameInfo.site = StrSave(HostName());
16501         gameInfo.date = PGNDate();
16502         gameInfo.round = StrSave("-");
16503         gameInfo.white = StrSave("?");
16504         gameInfo.black = StrSave("?");
16505         break;
16506
16507       default:
16508         break;
16509     }
16510 }
16511
16512 void
16513 ReplaceComment (int index, char *text)
16514 {
16515     int len;
16516     char *p;
16517     float score;
16518
16519     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16520        pvInfoList[index-1].depth == len &&
16521        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16522        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16523     while (*text == '\n') text++;
16524     len = strlen(text);
16525     while (len > 0 && text[len - 1] == '\n') len--;
16526
16527     if (commentList[index] != NULL)
16528       free(commentList[index]);
16529
16530     if (len == 0) {
16531         commentList[index] = NULL;
16532         return;
16533     }
16534   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16535       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16536       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16537     commentList[index] = (char *) malloc(len + 2);
16538     strncpy(commentList[index], text, len);
16539     commentList[index][len] = '\n';
16540     commentList[index][len + 1] = NULLCHAR;
16541   } else {
16542     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16543     char *p;
16544     commentList[index] = (char *) malloc(len + 7);
16545     safeStrCpy(commentList[index], "{\n", 3);
16546     safeStrCpy(commentList[index]+2, text, len+1);
16547     commentList[index][len+2] = NULLCHAR;
16548     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16549     strcat(commentList[index], "\n}\n");
16550   }
16551 }
16552
16553 void
16554 CrushCRs (char *text)
16555 {
16556   char *p = text;
16557   char *q = text;
16558   char ch;
16559
16560   do {
16561     ch = *p++;
16562     if (ch == '\r') continue;
16563     *q++ = ch;
16564   } while (ch != '\0');
16565 }
16566
16567 void
16568 AppendComment (int index, char *text, Boolean addBraces)
16569 /* addBraces  tells if we should add {} */
16570 {
16571     int oldlen, len;
16572     char *old;
16573
16574 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16575     if(addBraces == 3) addBraces = 0; else // force appending literally
16576     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16577
16578     CrushCRs(text);
16579     while (*text == '\n') text++;
16580     len = strlen(text);
16581     while (len > 0 && text[len - 1] == '\n') len--;
16582     text[len] = NULLCHAR;
16583
16584     if (len == 0) return;
16585
16586     if (commentList[index] != NULL) {
16587       Boolean addClosingBrace = addBraces;
16588         old = commentList[index];
16589         oldlen = strlen(old);
16590         while(commentList[index][oldlen-1] ==  '\n')
16591           commentList[index][--oldlen] = NULLCHAR;
16592         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16593         safeStrCpy(commentList[index], old, oldlen + len + 6);
16594         free(old);
16595         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16596         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16597           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16598           while (*text == '\n') { text++; len--; }
16599           commentList[index][--oldlen] = NULLCHAR;
16600       }
16601         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16602         else          strcat(commentList[index], "\n");
16603         strcat(commentList[index], text);
16604         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16605         else          strcat(commentList[index], "\n");
16606     } else {
16607         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16608         if(addBraces)
16609           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16610         else commentList[index][0] = NULLCHAR;
16611         strcat(commentList[index], text);
16612         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16613         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16614     }
16615 }
16616
16617 static char *
16618 FindStr (char * text, char * sub_text)
16619 {
16620     char * result = strstr( text, sub_text );
16621
16622     if( result != NULL ) {
16623         result += strlen( sub_text );
16624     }
16625
16626     return result;
16627 }
16628
16629 /* [AS] Try to extract PV info from PGN comment */
16630 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16631 char *
16632 GetInfoFromComment (int index, char * text)
16633 {
16634     char * sep = text, *p;
16635
16636     if( text != NULL && index > 0 ) {
16637         int score = 0;
16638         int depth = 0;
16639         int time = -1, sec = 0, deci;
16640         char * s_eval = FindStr( text, "[%eval " );
16641         char * s_emt = FindStr( text, "[%emt " );
16642 #if 0
16643         if( s_eval != NULL || s_emt != NULL ) {
16644 #else
16645         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16646 #endif
16647             /* New style */
16648             char delim;
16649
16650             if( s_eval != NULL ) {
16651                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16652                     return text;
16653                 }
16654
16655                 if( delim != ']' ) {
16656                     return text;
16657                 }
16658             }
16659
16660             if( s_emt != NULL ) {
16661             }
16662                 return text;
16663         }
16664         else {
16665             /* We expect something like: [+|-]nnn.nn/dd */
16666             int score_lo = 0;
16667
16668             if(*text != '{') return text; // [HGM] braces: must be normal comment
16669
16670             sep = strchr( text, '/' );
16671             if( sep == NULL || sep < (text+4) ) {
16672                 return text;
16673             }
16674
16675             p = text;
16676             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16677             if(p[1] == '(') { // comment starts with PV
16678                p = strchr(p, ')'); // locate end of PV
16679                if(p == NULL || sep < p+5) return text;
16680                // at this point we have something like "{(.*) +0.23/6 ..."
16681                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16682                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16683                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16684             }
16685             time = -1; sec = -1; deci = -1;
16686             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16687                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16688                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16689                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16690                 return text;
16691             }
16692
16693             if( score_lo < 0 || score_lo >= 100 ) {
16694                 return text;
16695             }
16696
16697             if(sec >= 0) time = 600*time + 10*sec; else
16698             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16699
16700             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16701
16702             /* [HGM] PV time: now locate end of PV info */
16703             while( *++sep >= '0' && *sep <= '9'); // strip depth
16704             if(time >= 0)
16705             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16706             if(sec >= 0)
16707             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16708             if(deci >= 0)
16709             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16710             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16711         }
16712
16713         if( depth <= 0 ) {
16714             return text;
16715         }
16716
16717         if( time < 0 ) {
16718             time = -1;
16719         }
16720
16721         pvInfoList[index-1].depth = depth;
16722         pvInfoList[index-1].score = score;
16723         pvInfoList[index-1].time  = 10*time; // centi-sec
16724         if(*sep == '}') *sep = 0; else *--sep = '{';
16725         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16726     }
16727     return sep;
16728 }
16729
16730 void
16731 SendToProgram (char *message, ChessProgramState *cps)
16732 {
16733     int count, outCount, error;
16734     char buf[MSG_SIZ];
16735
16736     if (cps->pr == NoProc) return;
16737     Attention(cps);
16738
16739     if (appData.debugMode) {
16740         TimeMark now;
16741         GetTimeMark(&now);
16742         fprintf(debugFP, "%ld >%-6s: %s",
16743                 SubtractTimeMarks(&now, &programStartTime),
16744                 cps->which, message);
16745         if(serverFP)
16746             fprintf(serverFP, "%ld >%-6s: %s",
16747                 SubtractTimeMarks(&now, &programStartTime),
16748                 cps->which, message), fflush(serverFP);
16749     }
16750
16751     count = strlen(message);
16752     outCount = OutputToProcess(cps->pr, message, count, &error);
16753     if (outCount < count && !exiting
16754                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16755       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16756       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16757         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16758             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16759                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16760                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16761                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16762             } else {
16763                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16764                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16765                 gameInfo.result = res;
16766             }
16767             gameInfo.resultDetails = StrSave(buf);
16768         }
16769         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16770         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16771     }
16772 }
16773
16774 void
16775 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16776 {
16777     char *end_str;
16778     char buf[MSG_SIZ];
16779     ChessProgramState *cps = (ChessProgramState *)closure;
16780
16781     if (isr != cps->isr) return; /* Killed intentionally */
16782     if (count <= 0) {
16783         if (count == 0) {
16784             RemoveInputSource(cps->isr);
16785             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16786                     _(cps->which), cps->program);
16787             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16788             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16789                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16790                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16791                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16792                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16793                 } else {
16794                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16795                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16796                     gameInfo.result = res;
16797                 }
16798                 gameInfo.resultDetails = StrSave(buf);
16799             }
16800             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16801             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16802         } else {
16803             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16804                     _(cps->which), cps->program);
16805             RemoveInputSource(cps->isr);
16806
16807             /* [AS] Program is misbehaving badly... kill it */
16808             if( count == -2 ) {
16809                 DestroyChildProcess( cps->pr, 9 );
16810                 cps->pr = NoProc;
16811             }
16812
16813             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16814         }
16815         return;
16816     }
16817
16818     if ((end_str = strchr(message, '\r')) != NULL)
16819       *end_str = NULLCHAR;
16820     if ((end_str = strchr(message, '\n')) != NULL)
16821       *end_str = NULLCHAR;
16822
16823     if (appData.debugMode) {
16824         TimeMark now; int print = 1;
16825         char *quote = ""; char c; int i;
16826
16827         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16828                 char start = message[0];
16829                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16830                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16831                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16832                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16833                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16834                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16835                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16836                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16837                    sscanf(message, "hint: %c", &c)!=1 &&
16838                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16839                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16840                     print = (appData.engineComments >= 2);
16841                 }
16842                 message[0] = start; // restore original message
16843         }
16844         if(print) {
16845                 GetTimeMark(&now);
16846                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16847                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16848                         quote,
16849                         message);
16850                 if(serverFP)
16851                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16852                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16853                         quote,
16854                         message), fflush(serverFP);
16855         }
16856     }
16857
16858     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16859     if (appData.icsEngineAnalyze) {
16860         if (strstr(message, "whisper") != NULL ||
16861              strstr(message, "kibitz") != NULL ||
16862             strstr(message, "tellics") != NULL) return;
16863     }
16864
16865     HandleMachineMove(message, cps);
16866 }
16867
16868
16869 void
16870 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16871 {
16872     char buf[MSG_SIZ];
16873     int seconds;
16874
16875     if( timeControl_2 > 0 ) {
16876         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16877             tc = timeControl_2;
16878         }
16879     }
16880     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16881     inc /= cps->timeOdds;
16882     st  /= cps->timeOdds;
16883
16884     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16885
16886     if (st > 0) {
16887       /* Set exact time per move, normally using st command */
16888       if (cps->stKludge) {
16889         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16890         seconds = st % 60;
16891         if (seconds == 0) {
16892           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16893         } else {
16894           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16895         }
16896       } else {
16897         snprintf(buf, MSG_SIZ, "st %d\n", st);
16898       }
16899     } else {
16900       /* Set conventional or incremental time control, using level command */
16901       if (seconds == 0) {
16902         /* Note old gnuchess bug -- minutes:seconds used to not work.
16903            Fixed in later versions, but still avoid :seconds
16904            when seconds is 0. */
16905         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16906       } else {
16907         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16908                  seconds, inc/1000.);
16909       }
16910     }
16911     SendToProgram(buf, cps);
16912
16913     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16914     /* Orthogonally, limit search to given depth */
16915     if (sd > 0) {
16916       if (cps->sdKludge) {
16917         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16918       } else {
16919         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16920       }
16921       SendToProgram(buf, cps);
16922     }
16923
16924     if(cps->nps >= 0) { /* [HGM] nps */
16925         if(cps->supportsNPS == FALSE)
16926           cps->nps = -1; // don't use if engine explicitly says not supported!
16927         else {
16928           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16929           SendToProgram(buf, cps);
16930         }
16931     }
16932 }
16933
16934 ChessProgramState *
16935 WhitePlayer ()
16936 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16937 {
16938     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16939        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16940         return &second;
16941     return &first;
16942 }
16943
16944 void
16945 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16946 {
16947     char message[MSG_SIZ];
16948     long time, otime;
16949
16950     /* Note: this routine must be called when the clocks are stopped
16951        or when they have *just* been set or switched; otherwise
16952        it will be off by the time since the current tick started.
16953     */
16954     if (machineWhite) {
16955         time = whiteTimeRemaining / 10;
16956         otime = blackTimeRemaining / 10;
16957     } else {
16958         time = blackTimeRemaining / 10;
16959         otime = whiteTimeRemaining / 10;
16960     }
16961     /* [HGM] translate opponent's time by time-odds factor */
16962     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16963
16964     if (time <= 0) time = 1;
16965     if (otime <= 0) otime = 1;
16966
16967     snprintf(message, MSG_SIZ, "time %ld\n", time);
16968     SendToProgram(message, cps);
16969
16970     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16971     SendToProgram(message, cps);
16972 }
16973
16974 char *
16975 EngineDefinedVariant (ChessProgramState *cps, int n)
16976 {   // return name of n-th unknown variant that engine supports
16977     static char buf[MSG_SIZ];
16978     char *p, *s = cps->variants;
16979     if(!s) return NULL;
16980     do { // parse string from variants feature
16981       VariantClass v;
16982         p = strchr(s, ',');
16983         if(p) *p = NULLCHAR;
16984       v = StringToVariant(s);
16985       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16986         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16987             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16988                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16989                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16990                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16991             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16992         }
16993         if(p) *p++ = ',';
16994         if(n < 0) return buf;
16995     } while(s = p);
16996     return NULL;
16997 }
16998
16999 int
17000 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17001 {
17002   char buf[MSG_SIZ];
17003   int len = strlen(name);
17004   int val;
17005
17006   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17007     (*p) += len + 1;
17008     sscanf(*p, "%d", &val);
17009     *loc = (val != 0);
17010     while (**p && **p != ' ')
17011       (*p)++;
17012     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17013     SendToProgram(buf, cps);
17014     return TRUE;
17015   }
17016   return FALSE;
17017 }
17018
17019 int
17020 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17021 {
17022   char buf[MSG_SIZ];
17023   int len = strlen(name);
17024   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17025     (*p) += len + 1;
17026     sscanf(*p, "%d", loc);
17027     while (**p && **p != ' ') (*p)++;
17028     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17029     SendToProgram(buf, cps);
17030     return TRUE;
17031   }
17032   return FALSE;
17033 }
17034
17035 int
17036 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17037 {
17038   char buf[MSG_SIZ];
17039   int len = strlen(name);
17040   if (strncmp((*p), name, len) == 0
17041       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17042     (*p) += len + 2;
17043     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17044     sscanf(*p, "%[^\"]", *loc);
17045     while (**p && **p != '\"') (*p)++;
17046     if (**p == '\"') (*p)++;
17047     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17048     SendToProgram(buf, cps);
17049     return TRUE;
17050   }
17051   return FALSE;
17052 }
17053
17054 int
17055 ParseOption (Option *opt, ChessProgramState *cps)
17056 // [HGM] options: process the string that defines an engine option, and determine
17057 // name, type, default value, and allowed value range
17058 {
17059         char *p, *q, buf[MSG_SIZ];
17060         int n, min = (-1)<<31, max = 1<<31, def;
17061
17062         opt->target = &opt->value;   // OK for spin/slider and checkbox
17063         if(p = strstr(opt->name, " -spin ")) {
17064             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17065             if(max < min) max = min; // enforce consistency
17066             if(def < min) def = min;
17067             if(def > max) def = max;
17068             opt->value = def;
17069             opt->min = min;
17070             opt->max = max;
17071             opt->type = Spin;
17072         } else if((p = strstr(opt->name, " -slider "))) {
17073             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17074             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17075             if(max < min) max = min; // enforce consistency
17076             if(def < min) def = min;
17077             if(def > max) def = max;
17078             opt->value = def;
17079             opt->min = min;
17080             opt->max = max;
17081             opt->type = Spin; // Slider;
17082         } else if((p = strstr(opt->name, " -string "))) {
17083             opt->textValue = p+9;
17084             opt->type = TextBox;
17085             opt->target = &opt->textValue;
17086         } else if((p = strstr(opt->name, " -file "))) {
17087             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17088             opt->target = opt->textValue = p+7;
17089             opt->type = FileName; // FileName;
17090             opt->target = &opt->textValue;
17091         } else if((p = strstr(opt->name, " -path "))) {
17092             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17093             opt->target = opt->textValue = p+7;
17094             opt->type = PathName; // PathName;
17095             opt->target = &opt->textValue;
17096         } else if(p = strstr(opt->name, " -check ")) {
17097             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17098             opt->value = (def != 0);
17099             opt->type = CheckBox;
17100         } else if(p = strstr(opt->name, " -combo ")) {
17101             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17102             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17103             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17104             opt->value = n = 0;
17105             while(q = StrStr(q, " /// ")) {
17106                 n++; *q = 0;    // count choices, and null-terminate each of them
17107                 q += 5;
17108                 if(*q == '*') { // remember default, which is marked with * prefix
17109                     q++;
17110                     opt->value = n;
17111                 }
17112                 cps->comboList[cps->comboCnt++] = q;
17113             }
17114             cps->comboList[cps->comboCnt++] = NULL;
17115             opt->max = n + 1;
17116             opt->type = ComboBox;
17117         } else if(p = strstr(opt->name, " -button")) {
17118             opt->type = Button;
17119         } else if(p = strstr(opt->name, " -save")) {
17120             opt->type = SaveButton;
17121         } else return FALSE;
17122         *p = 0; // terminate option name
17123         // now look if the command-line options define a setting for this engine option.
17124         if(cps->optionSettings && cps->optionSettings[0])
17125             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17126         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17127           snprintf(buf, MSG_SIZ, "option %s", p);
17128                 if(p = strstr(buf, ",")) *p = 0;
17129                 if(q = strchr(buf, '=')) switch(opt->type) {
17130                     case ComboBox:
17131                         for(n=0; n<opt->max; n++)
17132                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17133                         break;
17134                     case TextBox:
17135                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17136                         break;
17137                     case Spin:
17138                     case CheckBox:
17139                         opt->value = atoi(q+1);
17140                     default:
17141                         break;
17142                 }
17143                 strcat(buf, "\n");
17144                 SendToProgram(buf, cps);
17145         }
17146         return TRUE;
17147 }
17148
17149 void
17150 FeatureDone (ChessProgramState *cps, int val)
17151 {
17152   DelayedEventCallback cb = GetDelayedEvent();
17153   if ((cb == InitBackEnd3 && cps == &first) ||
17154       (cb == SettingsMenuIfReady && cps == &second) ||
17155       (cb == LoadEngine) ||
17156       (cb == TwoMachinesEventIfReady)) {
17157     CancelDelayedEvent();
17158     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17159   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17160   cps->initDone = val;
17161   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17162 }
17163
17164 /* Parse feature command from engine */
17165 void
17166 ParseFeatures (char *args, ChessProgramState *cps)
17167 {
17168   char *p = args;
17169   char *q = NULL;
17170   int val;
17171   char buf[MSG_SIZ];
17172
17173   for (;;) {
17174     while (*p == ' ') p++;
17175     if (*p == NULLCHAR) return;
17176
17177     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17178     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17179     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17180     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17181     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17182     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17183     if (BoolFeature(&p, "reuse", &val, cps)) {
17184       /* Engine can disable reuse, but can't enable it if user said no */
17185       if (!val) cps->reuse = FALSE;
17186       continue;
17187     }
17188     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17189     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17190       if (gameMode == TwoMachinesPlay) {
17191         DisplayTwoMachinesTitle();
17192       } else {
17193         DisplayTitle("");
17194       }
17195       continue;
17196     }
17197     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17198     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17199     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17200     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17201     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17202     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17203     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17204     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17205     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17206     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17207     if (IntFeature(&p, "done", &val, cps)) {
17208       FeatureDone(cps, val);
17209       continue;
17210     }
17211     /* Added by Tord: */
17212     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17213     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17214     /* End of additions by Tord */
17215
17216     /* [HGM] added features: */
17217     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17218     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17219     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17220     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17221     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17222     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17223     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17224     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17225         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17226         FREE(cps->option[cps->nrOptions].name);
17227         cps->option[cps->nrOptions].name = q; q = NULL;
17228         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17229           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17230             SendToProgram(buf, cps);
17231             continue;
17232         }
17233         if(cps->nrOptions >= MAX_OPTIONS) {
17234             cps->nrOptions--;
17235             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17236             DisplayError(buf, 0);
17237         }
17238         continue;
17239     }
17240     /* End of additions by HGM */
17241
17242     /* unknown feature: complain and skip */
17243     q = p;
17244     while (*q && *q != '=') q++;
17245     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17246     SendToProgram(buf, cps);
17247     p = q;
17248     if (*p == '=') {
17249       p++;
17250       if (*p == '\"') {
17251         p++;
17252         while (*p && *p != '\"') p++;
17253         if (*p == '\"') p++;
17254       } else {
17255         while (*p && *p != ' ') p++;
17256       }
17257     }
17258   }
17259
17260 }
17261
17262 void
17263 PeriodicUpdatesEvent (int newState)
17264 {
17265     if (newState == appData.periodicUpdates)
17266       return;
17267
17268     appData.periodicUpdates=newState;
17269
17270     /* Display type changes, so update it now */
17271 //    DisplayAnalysis();
17272
17273     /* Get the ball rolling again... */
17274     if (newState) {
17275         AnalysisPeriodicEvent(1);
17276         StartAnalysisClock();
17277     }
17278 }
17279
17280 void
17281 PonderNextMoveEvent (int newState)
17282 {
17283     if (newState == appData.ponderNextMove) return;
17284     if (gameMode == EditPosition) EditPositionDone(TRUE);
17285     if (newState) {
17286         SendToProgram("hard\n", &first);
17287         if (gameMode == TwoMachinesPlay) {
17288             SendToProgram("hard\n", &second);
17289         }
17290     } else {
17291         SendToProgram("easy\n", &first);
17292         thinkOutput[0] = NULLCHAR;
17293         if (gameMode == TwoMachinesPlay) {
17294             SendToProgram("easy\n", &second);
17295         }
17296     }
17297     appData.ponderNextMove = newState;
17298 }
17299
17300 void
17301 NewSettingEvent (int option, int *feature, char *command, int value)
17302 {
17303     char buf[MSG_SIZ];
17304
17305     if (gameMode == EditPosition) EditPositionDone(TRUE);
17306     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17307     if(feature == NULL || *feature) SendToProgram(buf, &first);
17308     if (gameMode == TwoMachinesPlay) {
17309         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17310     }
17311 }
17312
17313 void
17314 ShowThinkingEvent ()
17315 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17316 {
17317     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17318     int newState = appData.showThinking
17319         // [HGM] thinking: other features now need thinking output as well
17320         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17321
17322     if (oldState == newState) return;
17323     oldState = newState;
17324     if (gameMode == EditPosition) EditPositionDone(TRUE);
17325     if (oldState) {
17326         SendToProgram("post\n", &first);
17327         if (gameMode == TwoMachinesPlay) {
17328             SendToProgram("post\n", &second);
17329         }
17330     } else {
17331         SendToProgram("nopost\n", &first);
17332         thinkOutput[0] = NULLCHAR;
17333         if (gameMode == TwoMachinesPlay) {
17334             SendToProgram("nopost\n", &second);
17335         }
17336     }
17337 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17338 }
17339
17340 void
17341 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17342 {
17343   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17344   if (pr == NoProc) return;
17345   AskQuestion(title, question, replyPrefix, pr);
17346 }
17347
17348 void
17349 TypeInEvent (char firstChar)
17350 {
17351     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17352         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17353         gameMode == AnalyzeMode || gameMode == EditGame ||
17354         gameMode == EditPosition || gameMode == IcsExamining ||
17355         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17356         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17357                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17358                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17359         gameMode == Training) PopUpMoveDialog(firstChar);
17360 }
17361
17362 void
17363 TypeInDoneEvent (char *move)
17364 {
17365         Board board;
17366         int n, fromX, fromY, toX, toY;
17367         char promoChar;
17368         ChessMove moveType;
17369
17370         // [HGM] FENedit
17371         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17372                 EditPositionPasteFEN(move);
17373                 return;
17374         }
17375         // [HGM] movenum: allow move number to be typed in any mode
17376         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17377           ToNrEvent(2*n-1);
17378           return;
17379         }
17380         // undocumented kludge: allow command-line option to be typed in!
17381         // (potentially fatal, and does not implement the effect of the option.)
17382         // should only be used for options that are values on which future decisions will be made,
17383         // and definitely not on options that would be used during initialization.
17384         if(strstr(move, "!!! -") == move) {
17385             ParseArgsFromString(move+4);
17386             return;
17387         }
17388
17389       if (gameMode != EditGame && currentMove != forwardMostMove &&
17390         gameMode != Training) {
17391         DisplayMoveError(_("Displayed move is not current"));
17392       } else {
17393         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17394           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17395         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17396         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17397           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17398           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17399         } else {
17400           DisplayMoveError(_("Could not parse move"));
17401         }
17402       }
17403 }
17404
17405 void
17406 DisplayMove (int moveNumber)
17407 {
17408     char message[MSG_SIZ];
17409     char res[MSG_SIZ];
17410     char cpThinkOutput[MSG_SIZ];
17411
17412     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17413
17414     if (moveNumber == forwardMostMove - 1 ||
17415         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17416
17417         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17418
17419         if (strchr(cpThinkOutput, '\n')) {
17420             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17421         }
17422     } else {
17423         *cpThinkOutput = NULLCHAR;
17424     }
17425
17426     /* [AS] Hide thinking from human user */
17427     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17428         *cpThinkOutput = NULLCHAR;
17429         if( thinkOutput[0] != NULLCHAR ) {
17430             int i;
17431
17432             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17433                 cpThinkOutput[i] = '.';
17434             }
17435             cpThinkOutput[i] = NULLCHAR;
17436             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17437         }
17438     }
17439
17440     if (moveNumber == forwardMostMove - 1 &&
17441         gameInfo.resultDetails != NULL) {
17442         if (gameInfo.resultDetails[0] == NULLCHAR) {
17443           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17444         } else {
17445           snprintf(res, MSG_SIZ, " {%s} %s",
17446                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17447         }
17448     } else {
17449         res[0] = NULLCHAR;
17450     }
17451
17452     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17453         DisplayMessage(res, cpThinkOutput);
17454     } else {
17455       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17456                 WhiteOnMove(moveNumber) ? " " : ".. ",
17457                 parseList[moveNumber], res);
17458         DisplayMessage(message, cpThinkOutput);
17459     }
17460 }
17461
17462 void
17463 DisplayComment (int moveNumber, char *text)
17464 {
17465     char title[MSG_SIZ];
17466
17467     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17468       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17469     } else {
17470       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17471               WhiteOnMove(moveNumber) ? " " : ".. ",
17472               parseList[moveNumber]);
17473     }
17474     if (text != NULL && (appData.autoDisplayComment || commentUp))
17475         CommentPopUp(title, text);
17476 }
17477
17478 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17479  * might be busy thinking or pondering.  It can be omitted if your
17480  * gnuchess is configured to stop thinking immediately on any user
17481  * input.  However, that gnuchess feature depends on the FIONREAD
17482  * ioctl, which does not work properly on some flavors of Unix.
17483  */
17484 void
17485 Attention (ChessProgramState *cps)
17486 {
17487 #if ATTENTION
17488     if (!cps->useSigint) return;
17489     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17490     switch (gameMode) {
17491       case MachinePlaysWhite:
17492       case MachinePlaysBlack:
17493       case TwoMachinesPlay:
17494       case IcsPlayingWhite:
17495       case IcsPlayingBlack:
17496       case AnalyzeMode:
17497       case AnalyzeFile:
17498         /* Skip if we know it isn't thinking */
17499         if (!cps->maybeThinking) return;
17500         if (appData.debugMode)
17501           fprintf(debugFP, "Interrupting %s\n", cps->which);
17502         InterruptChildProcess(cps->pr);
17503         cps->maybeThinking = FALSE;
17504         break;
17505       default:
17506         break;
17507     }
17508 #endif /*ATTENTION*/
17509 }
17510
17511 int
17512 CheckFlags ()
17513 {
17514     if (whiteTimeRemaining <= 0) {
17515         if (!whiteFlag) {
17516             whiteFlag = TRUE;
17517             if (appData.icsActive) {
17518                 if (appData.autoCallFlag &&
17519                     gameMode == IcsPlayingBlack && !blackFlag) {
17520                   SendToICS(ics_prefix);
17521                   SendToICS("flag\n");
17522                 }
17523             } else {
17524                 if (blackFlag) {
17525                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17526                 } else {
17527                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17528                     if (appData.autoCallFlag) {
17529                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17530                         return TRUE;
17531                     }
17532                 }
17533             }
17534         }
17535     }
17536     if (blackTimeRemaining <= 0) {
17537         if (!blackFlag) {
17538             blackFlag = TRUE;
17539             if (appData.icsActive) {
17540                 if (appData.autoCallFlag &&
17541                     gameMode == IcsPlayingWhite && !whiteFlag) {
17542                   SendToICS(ics_prefix);
17543                   SendToICS("flag\n");
17544                 }
17545             } else {
17546                 if (whiteFlag) {
17547                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17548                 } else {
17549                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17550                     if (appData.autoCallFlag) {
17551                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17552                         return TRUE;
17553                     }
17554                 }
17555             }
17556         }
17557     }
17558     return FALSE;
17559 }
17560
17561 void
17562 CheckTimeControl ()
17563 {
17564     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17565         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17566
17567     /*
17568      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17569      */
17570     if ( !WhiteOnMove(forwardMostMove) ) {
17571         /* White made time control */
17572         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17573         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17574         /* [HGM] time odds: correct new time quota for time odds! */
17575                                             / WhitePlayer()->timeOdds;
17576         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17577     } else {
17578         lastBlack -= blackTimeRemaining;
17579         /* Black made time control */
17580         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17581                                             / WhitePlayer()->other->timeOdds;
17582         lastWhite = whiteTimeRemaining;
17583     }
17584 }
17585
17586 void
17587 DisplayBothClocks ()
17588 {
17589     int wom = gameMode == EditPosition ?
17590       !blackPlaysFirst : WhiteOnMove(currentMove);
17591     DisplayWhiteClock(whiteTimeRemaining, wom);
17592     DisplayBlackClock(blackTimeRemaining, !wom);
17593 }
17594
17595
17596 /* Timekeeping seems to be a portability nightmare.  I think everyone
17597    has ftime(), but I'm really not sure, so I'm including some ifdefs
17598    to use other calls if you don't.  Clocks will be less accurate if
17599    you have neither ftime nor gettimeofday.
17600 */
17601
17602 /* VS 2008 requires the #include outside of the function */
17603 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17604 #include <sys/timeb.h>
17605 #endif
17606
17607 /* Get the current time as a TimeMark */
17608 void
17609 GetTimeMark (TimeMark *tm)
17610 {
17611 #if HAVE_GETTIMEOFDAY
17612
17613     struct timeval timeVal;
17614     struct timezone timeZone;
17615
17616     gettimeofday(&timeVal, &timeZone);
17617     tm->sec = (long) timeVal.tv_sec;
17618     tm->ms = (int) (timeVal.tv_usec / 1000L);
17619
17620 #else /*!HAVE_GETTIMEOFDAY*/
17621 #if HAVE_FTIME
17622
17623 // include <sys/timeb.h> / moved to just above start of function
17624     struct timeb timeB;
17625
17626     ftime(&timeB);
17627     tm->sec = (long) timeB.time;
17628     tm->ms = (int) timeB.millitm;
17629
17630 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17631     tm->sec = (long) time(NULL);
17632     tm->ms = 0;
17633 #endif
17634 #endif
17635 }
17636
17637 /* Return the difference in milliseconds between two
17638    time marks.  We assume the difference will fit in a long!
17639 */
17640 long
17641 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17642 {
17643     return 1000L*(tm2->sec - tm1->sec) +
17644            (long) (tm2->ms - tm1->ms);
17645 }
17646
17647
17648 /*
17649  * Code to manage the game clocks.
17650  *
17651  * In tournament play, black starts the clock and then white makes a move.
17652  * We give the human user a slight advantage if he is playing white---the
17653  * clocks don't run until he makes his first move, so it takes zero time.
17654  * Also, we don't account for network lag, so we could get out of sync
17655  * with GNU Chess's clock -- but then, referees are always right.
17656  */
17657
17658 static TimeMark tickStartTM;
17659 static long intendedTickLength;
17660
17661 long
17662 NextTickLength (long timeRemaining)
17663 {
17664     long nominalTickLength, nextTickLength;
17665
17666     if (timeRemaining > 0L && timeRemaining <= 10000L)
17667       nominalTickLength = 100L;
17668     else
17669       nominalTickLength = 1000L;
17670     nextTickLength = timeRemaining % nominalTickLength;
17671     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17672
17673     return nextTickLength;
17674 }
17675
17676 /* Adjust clock one minute up or down */
17677 void
17678 AdjustClock (Boolean which, int dir)
17679 {
17680     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17681     if(which) blackTimeRemaining += 60000*dir;
17682     else      whiteTimeRemaining += 60000*dir;
17683     DisplayBothClocks();
17684     adjustedClock = TRUE;
17685 }
17686
17687 /* Stop clocks and reset to a fresh time control */
17688 void
17689 ResetClocks ()
17690 {
17691     (void) StopClockTimer();
17692     if (appData.icsActive) {
17693         whiteTimeRemaining = blackTimeRemaining = 0;
17694     } else if (searchTime) {
17695         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17696         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17697     } else { /* [HGM] correct new time quote for time odds */
17698         whiteTC = blackTC = fullTimeControlString;
17699         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17700         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17701     }
17702     if (whiteFlag || blackFlag) {
17703         DisplayTitle("");
17704         whiteFlag = blackFlag = FALSE;
17705     }
17706     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17707     DisplayBothClocks();
17708     adjustedClock = FALSE;
17709 }
17710
17711 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17712
17713 /* Decrement running clock by amount of time that has passed */
17714 void
17715 DecrementClocks ()
17716 {
17717     long timeRemaining;
17718     long lastTickLength, fudge;
17719     TimeMark now;
17720
17721     if (!appData.clockMode) return;
17722     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17723
17724     GetTimeMark(&now);
17725
17726     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17727
17728     /* Fudge if we woke up a little too soon */
17729     fudge = intendedTickLength - lastTickLength;
17730     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17731
17732     if (WhiteOnMove(forwardMostMove)) {
17733         if(whiteNPS >= 0) lastTickLength = 0;
17734         timeRemaining = whiteTimeRemaining -= lastTickLength;
17735         if(timeRemaining < 0 && !appData.icsActive) {
17736             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17737             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17738                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17739                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17740             }
17741         }
17742         DisplayWhiteClock(whiteTimeRemaining - fudge,
17743                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17744     } else {
17745         if(blackNPS >= 0) lastTickLength = 0;
17746         timeRemaining = blackTimeRemaining -= lastTickLength;
17747         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17748             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17749             if(suddenDeath) {
17750                 blackStartMove = forwardMostMove;
17751                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17752             }
17753         }
17754         DisplayBlackClock(blackTimeRemaining - fudge,
17755                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17756     }
17757     if (CheckFlags()) return;
17758
17759     if(twoBoards) { // count down secondary board's clocks as well
17760         activePartnerTime -= lastTickLength;
17761         partnerUp = 1;
17762         if(activePartner == 'W')
17763             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17764         else
17765             DisplayBlackClock(activePartnerTime, TRUE);
17766         partnerUp = 0;
17767     }
17768
17769     tickStartTM = now;
17770     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17771     StartClockTimer(intendedTickLength);
17772
17773     /* if the time remaining has fallen below the alarm threshold, sound the
17774      * alarm. if the alarm has sounded and (due to a takeback or time control
17775      * with increment) the time remaining has increased to a level above the
17776      * threshold, reset the alarm so it can sound again.
17777      */
17778
17779     if (appData.icsActive && appData.icsAlarm) {
17780
17781         /* make sure we are dealing with the user's clock */
17782         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17783                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17784            )) return;
17785
17786         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17787             alarmSounded = FALSE;
17788         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17789             PlayAlarmSound();
17790             alarmSounded = TRUE;
17791         }
17792     }
17793 }
17794
17795
17796 /* A player has just moved, so stop the previously running
17797    clock and (if in clock mode) start the other one.
17798    We redisplay both clocks in case we're in ICS mode, because
17799    ICS gives us an update to both clocks after every move.
17800    Note that this routine is called *after* forwardMostMove
17801    is updated, so the last fractional tick must be subtracted
17802    from the color that is *not* on move now.
17803 */
17804 void
17805 SwitchClocks (int newMoveNr)
17806 {
17807     long lastTickLength;
17808     TimeMark now;
17809     int flagged = FALSE;
17810
17811     GetTimeMark(&now);
17812
17813     if (StopClockTimer() && appData.clockMode) {
17814         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17815         if (!WhiteOnMove(forwardMostMove)) {
17816             if(blackNPS >= 0) lastTickLength = 0;
17817             blackTimeRemaining -= lastTickLength;
17818            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17819 //         if(pvInfoList[forwardMostMove].time == -1)
17820                  pvInfoList[forwardMostMove].time =               // use GUI time
17821                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17822         } else {
17823            if(whiteNPS >= 0) lastTickLength = 0;
17824            whiteTimeRemaining -= lastTickLength;
17825            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17826 //         if(pvInfoList[forwardMostMove].time == -1)
17827                  pvInfoList[forwardMostMove].time =
17828                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17829         }
17830         flagged = CheckFlags();
17831     }
17832     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17833     CheckTimeControl();
17834
17835     if (flagged || !appData.clockMode) return;
17836
17837     switch (gameMode) {
17838       case MachinePlaysBlack:
17839       case MachinePlaysWhite:
17840       case BeginningOfGame:
17841         if (pausing) return;
17842         break;
17843
17844       case EditGame:
17845       case PlayFromGameFile:
17846       case IcsExamining:
17847         return;
17848
17849       default:
17850         break;
17851     }
17852
17853     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17854         if(WhiteOnMove(forwardMostMove))
17855              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17856         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17857     }
17858
17859     tickStartTM = now;
17860     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17861       whiteTimeRemaining : blackTimeRemaining);
17862     StartClockTimer(intendedTickLength);
17863 }
17864
17865
17866 /* Stop both clocks */
17867 void
17868 StopClocks ()
17869 {
17870     long lastTickLength;
17871     TimeMark now;
17872
17873     if (!StopClockTimer()) return;
17874     if (!appData.clockMode) return;
17875
17876     GetTimeMark(&now);
17877
17878     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17879     if (WhiteOnMove(forwardMostMove)) {
17880         if(whiteNPS >= 0) lastTickLength = 0;
17881         whiteTimeRemaining -= lastTickLength;
17882         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17883     } else {
17884         if(blackNPS >= 0) lastTickLength = 0;
17885         blackTimeRemaining -= lastTickLength;
17886         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17887     }
17888     CheckFlags();
17889 }
17890
17891 /* Start clock of player on move.  Time may have been reset, so
17892    if clock is already running, stop and restart it. */
17893 void
17894 StartClocks ()
17895 {
17896     (void) StopClockTimer(); /* in case it was running already */
17897     DisplayBothClocks();
17898     if (CheckFlags()) return;
17899
17900     if (!appData.clockMode) return;
17901     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17902
17903     GetTimeMark(&tickStartTM);
17904     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17905       whiteTimeRemaining : blackTimeRemaining);
17906
17907    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17908     whiteNPS = blackNPS = -1;
17909     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17910        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17911         whiteNPS = first.nps;
17912     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17913        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17914         blackNPS = first.nps;
17915     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17916         whiteNPS = second.nps;
17917     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17918         blackNPS = second.nps;
17919     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17920
17921     StartClockTimer(intendedTickLength);
17922 }
17923
17924 char *
17925 TimeString (long ms)
17926 {
17927     long second, minute, hour, day;
17928     char *sign = "";
17929     static char buf[32];
17930
17931     if (ms > 0 && ms <= 9900) {
17932       /* convert milliseconds to tenths, rounding up */
17933       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17934
17935       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17936       return buf;
17937     }
17938
17939     /* convert milliseconds to seconds, rounding up */
17940     /* use floating point to avoid strangeness of integer division
17941        with negative dividends on many machines */
17942     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17943
17944     if (second < 0) {
17945         sign = "-";
17946         second = -second;
17947     }
17948
17949     day = second / (60 * 60 * 24);
17950     second = second % (60 * 60 * 24);
17951     hour = second / (60 * 60);
17952     second = second % (60 * 60);
17953     minute = second / 60;
17954     second = second % 60;
17955
17956     if (day > 0)
17957       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17958               sign, day, hour, minute, second);
17959     else if (hour > 0)
17960       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17961     else
17962       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17963
17964     return buf;
17965 }
17966
17967
17968 /*
17969  * This is necessary because some C libraries aren't ANSI C compliant yet.
17970  */
17971 char *
17972 StrStr (char *string, char *match)
17973 {
17974     int i, length;
17975
17976     length = strlen(match);
17977
17978     for (i = strlen(string) - length; i >= 0; i--, string++)
17979       if (!strncmp(match, string, length))
17980         return string;
17981
17982     return NULL;
17983 }
17984
17985 char *
17986 StrCaseStr (char *string, char *match)
17987 {
17988     int i, j, length;
17989
17990     length = strlen(match);
17991
17992     for (i = strlen(string) - length; i >= 0; i--, string++) {
17993         for (j = 0; j < length; j++) {
17994             if (ToLower(match[j]) != ToLower(string[j]))
17995               break;
17996         }
17997         if (j == length) return string;
17998     }
17999
18000     return NULL;
18001 }
18002
18003 #ifndef _amigados
18004 int
18005 StrCaseCmp (char *s1, char *s2)
18006 {
18007     char c1, c2;
18008
18009     for (;;) {
18010         c1 = ToLower(*s1++);
18011         c2 = ToLower(*s2++);
18012         if (c1 > c2) return 1;
18013         if (c1 < c2) return -1;
18014         if (c1 == NULLCHAR) return 0;
18015     }
18016 }
18017
18018
18019 int
18020 ToLower (int c)
18021 {
18022     return isupper(c) ? tolower(c) : c;
18023 }
18024
18025
18026 int
18027 ToUpper (int c)
18028 {
18029     return islower(c) ? toupper(c) : c;
18030 }
18031 #endif /* !_amigados    */
18032
18033 char *
18034 StrSave (char *s)
18035 {
18036   char *ret;
18037
18038   if ((ret = (char *) malloc(strlen(s) + 1)))
18039     {
18040       safeStrCpy(ret, s, strlen(s)+1);
18041     }
18042   return ret;
18043 }
18044
18045 char *
18046 StrSavePtr (char *s, char **savePtr)
18047 {
18048     if (*savePtr) {
18049         free(*savePtr);
18050     }
18051     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18052       safeStrCpy(*savePtr, s, strlen(s)+1);
18053     }
18054     return(*savePtr);
18055 }
18056
18057 char *
18058 PGNDate ()
18059 {
18060     time_t clock;
18061     struct tm *tm;
18062     char buf[MSG_SIZ];
18063
18064     clock = time((time_t *)NULL);
18065     tm = localtime(&clock);
18066     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18067             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18068     return StrSave(buf);
18069 }
18070
18071
18072 char *
18073 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18074 {
18075     int i, j, fromX, fromY, toX, toY;
18076     int whiteToPlay, haveRights = nrCastlingRights;
18077     char buf[MSG_SIZ];
18078     char *p, *q;
18079     int emptycount;
18080     ChessSquare piece;
18081
18082     whiteToPlay = (gameMode == EditPosition) ?
18083       !blackPlaysFirst : (move % 2 == 0);
18084     p = buf;
18085
18086     /* Piece placement data */
18087     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18088         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18089         emptycount = 0;
18090         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18091             if (boards[move][i][j] == EmptySquare) {
18092                 emptycount++;
18093             } else { ChessSquare piece = boards[move][i][j];
18094                 if (emptycount > 0) {
18095                     if(emptycount<10) /* [HGM] can be >= 10 */
18096                         *p++ = '0' + emptycount;
18097                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18098                     emptycount = 0;
18099                 }
18100                 if(PieceToChar(piece) == '+') {
18101                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18102                     *p++ = '+';
18103                     piece = (ChessSquare)(CHUDEMOTED(piece));
18104                 }
18105                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18106                 if(*p = PieceSuffix(piece)) p++;
18107                 if(p[-1] == '~') {
18108                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18109                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18110                     *p++ = '~';
18111                 }
18112             }
18113         }
18114         if (emptycount > 0) {
18115             if(emptycount<10) /* [HGM] can be >= 10 */
18116                 *p++ = '0' + emptycount;
18117             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18118             emptycount = 0;
18119         }
18120         *p++ = '/';
18121     }
18122     *(p - 1) = ' ';
18123
18124     /* [HGM] print Crazyhouse or Shogi holdings */
18125     if( gameInfo.holdingsWidth ) {
18126         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18127         q = p;
18128         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18129             piece = boards[move][i][BOARD_WIDTH-1];
18130             if( piece != EmptySquare )
18131               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18132                   *p++ = PieceToChar(piece);
18133         }
18134         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18135             piece = boards[move][BOARD_HEIGHT-i-1][0];
18136             if( piece != EmptySquare )
18137               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18138                   *p++ = PieceToChar(piece);
18139         }
18140
18141         if( q == p ) *p++ = '-';
18142         *p++ = ']';
18143         *p++ = ' ';
18144     }
18145
18146     /* Active color */
18147     *p++ = whiteToPlay ? 'w' : 'b';
18148     *p++ = ' ';
18149
18150   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18151     haveRights = 0; q = p;
18152     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18153       piece = boards[move][0][i];
18154       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18155         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18156       }
18157     }
18158     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18159       piece = boards[move][BOARD_HEIGHT-1][i];
18160       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18161         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18162       }
18163     }
18164     if(p == q) *p++ = '-';
18165     *p++ = ' ';
18166   }
18167
18168   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18169     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18170   } else {
18171   if(haveRights) {
18172      int handW=0, handB=0;
18173      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18174         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18175         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18176      }
18177      q = p;
18178      if(appData.fischerCastling) {
18179         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18180            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18181                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18182         } else {
18183        /* [HGM] write directly from rights */
18184            if(boards[move][CASTLING][2] != NoRights &&
18185               boards[move][CASTLING][0] != NoRights   )
18186                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18187            if(boards[move][CASTLING][2] != NoRights &&
18188               boards[move][CASTLING][1] != NoRights   )
18189                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18190         }
18191         if(handB) {
18192            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18193                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18194         } else {
18195            if(boards[move][CASTLING][5] != NoRights &&
18196               boards[move][CASTLING][3] != NoRights   )
18197                 *p++ = boards[move][CASTLING][3] + AAA;
18198            if(boards[move][CASTLING][5] != NoRights &&
18199               boards[move][CASTLING][4] != NoRights   )
18200                 *p++ = boards[move][CASTLING][4] + AAA;
18201         }
18202      } else {
18203
18204         /* [HGM] write true castling rights */
18205         if( nrCastlingRights == 6 ) {
18206             int q, k=0;
18207             if(boards[move][CASTLING][0] != NoRights &&
18208                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18209             q = (boards[move][CASTLING][1] != NoRights &&
18210                  boards[move][CASTLING][2] != NoRights  );
18211             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18212                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18213                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18214                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18215             }
18216             if(q) *p++ = 'Q';
18217             k = 0;
18218             if(boards[move][CASTLING][3] != NoRights &&
18219                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18220             q = (boards[move][CASTLING][4] != NoRights &&
18221                  boards[move][CASTLING][5] != NoRights  );
18222             if(handB) {
18223                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18224                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18225                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18226             }
18227             if(q) *p++ = 'q';
18228         }
18229      }
18230      if (q == p) *p++ = '-'; /* No castling rights */
18231      *p++ = ' ';
18232   }
18233
18234   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18235      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18236      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18237     /* En passant target square */
18238     if (move > backwardMostMove) {
18239         fromX = moveList[move - 1][0] - AAA;
18240         fromY = moveList[move - 1][1] - ONE;
18241         toX = moveList[move - 1][2] - AAA;
18242         toY = moveList[move - 1][3] - ONE;
18243         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18244             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18245             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18246             fromX == toX) {
18247             /* 2-square pawn move just happened */
18248             *p++ = toX + AAA;
18249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18250         } else {
18251             *p++ = '-';
18252         }
18253     } else if(move == backwardMostMove) {
18254         // [HGM] perhaps we should always do it like this, and forget the above?
18255         if((signed char)boards[move][EP_STATUS] >= 0) {
18256             *p++ = boards[move][EP_STATUS] + AAA;
18257             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18258         } else {
18259             *p++ = '-';
18260         }
18261     } else {
18262         *p++ = '-';
18263     }
18264     *p++ = ' ';
18265   }
18266   }
18267
18268     if(moveCounts)
18269     {   int i = 0, j=move;
18270
18271         /* [HGM] find reversible plies */
18272         if (appData.debugMode) { int k;
18273             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18274             for(k=backwardMostMove; k<=forwardMostMove; k++)
18275                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18276
18277         }
18278
18279         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18280         if( j == backwardMostMove ) i += initialRulePlies;
18281         sprintf(p, "%d ", i);
18282         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18283
18284         /* Fullmove number */
18285         sprintf(p, "%d", (move / 2) + 1);
18286     } else *--p = NULLCHAR;
18287
18288     return StrSave(buf);
18289 }
18290
18291 Boolean
18292 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18293 {
18294     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18295     char *p, c;
18296     int emptycount, virgin[BOARD_FILES];
18297     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18298
18299     p = fen;
18300
18301     /* Piece placement data */
18302     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18303         j = 0;
18304         for (;;) {
18305             if (*p == '/' || *p == ' ' || *p == '[' ) {
18306                 if(j > w) w = j;
18307                 emptycount = gameInfo.boardWidth - j;
18308                 while (emptycount--)
18309                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18310                 if (*p == '/') p++;
18311                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18312                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18313                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18314                     }
18315                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18316                 }
18317                 break;
18318 #if(BOARD_FILES >= 10)*0
18319             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18320                 p++; emptycount=10;
18321                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18322                 while (emptycount--)
18323                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18324 #endif
18325             } else if (*p == '*') {
18326                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18327             } else if (isdigit(*p)) {
18328                 emptycount = *p++ - '0';
18329                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18330                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18331                 while (emptycount--)
18332                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18333             } else if (*p == '<') {
18334                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18335                 else if (i != 0 || !shuffle) return FALSE;
18336                 p++;
18337             } else if (shuffle && *p == '>') {
18338                 p++; // for now ignore closing shuffle range, and assume rank-end
18339             } else if (*p == '?') {
18340                 if (j >= gameInfo.boardWidth) return FALSE;
18341                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18342                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18343             } else if (*p == '+' || isalpha(*p)) {
18344                 char *q, *s = SUFFIXES;
18345                 if (j >= gameInfo.boardWidth) return FALSE;
18346                 if(*p=='+') {
18347                     char c = *++p;
18348                     if(q = strchr(s, p[1])) p++;
18349                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18350                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18351                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18352                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18353                 } else {
18354                     char c = *p++;
18355                     if(q = strchr(s, *p)) p++;
18356                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18357                 }
18358
18359                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18360                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18361                     piece = (ChessSquare) (PROMOTED(piece));
18362                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18363                     p++;
18364                 }
18365                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18366                 if(piece == king) wKingRank = i;
18367                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18368             } else {
18369                 return FALSE;
18370             }
18371         }
18372     }
18373     while (*p == '/' || *p == ' ') p++;
18374
18375     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18376
18377     /* [HGM] by default clear Crazyhouse holdings, if present */
18378     if(gameInfo.holdingsWidth) {
18379        for(i=0; i<BOARD_HEIGHT; i++) {
18380            board[i][0]             = EmptySquare; /* black holdings */
18381            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18382            board[i][1]             = (ChessSquare) 0; /* black counts */
18383            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18384        }
18385     }
18386
18387     /* [HGM] look for Crazyhouse holdings here */
18388     while(*p==' ') p++;
18389     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18390         int swap=0, wcnt=0, bcnt=0;
18391         if(*p == '[') p++;
18392         if(*p == '<') swap++, p++;
18393         if(*p == '-' ) p++; /* empty holdings */ else {
18394             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18395             /* if we would allow FEN reading to set board size, we would   */
18396             /* have to add holdings and shift the board read so far here   */
18397             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18398                 p++;
18399                 if((int) piece >= (int) BlackPawn ) {
18400                     i = (int)piece - (int)BlackPawn;
18401                     i = PieceToNumber((ChessSquare)i);
18402                     if( i >= gameInfo.holdingsSize ) return FALSE;
18403                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18404                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18405                     bcnt++;
18406                 } else {
18407                     i = (int)piece - (int)WhitePawn;
18408                     i = PieceToNumber((ChessSquare)i);
18409                     if( i >= gameInfo.holdingsSize ) return FALSE;
18410                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18411                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18412                     wcnt++;
18413                 }
18414             }
18415             if(subst) { // substitute back-rank question marks by holdings pieces
18416                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18417                     int k, m, n = bcnt + 1;
18418                     if(board[0][j] == ClearBoard) {
18419                         if(!wcnt) return FALSE;
18420                         n = rand() % wcnt;
18421                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18422                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18423                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18424                             break;
18425                         }
18426                     }
18427                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18428                         if(!bcnt) return FALSE;
18429                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18430                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18431                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18432                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18433                             break;
18434                         }
18435                     }
18436                 }
18437                 subst = 0;
18438             }
18439         }
18440         if(*p == ']') p++;
18441     }
18442
18443     if(subst) return FALSE; // substitution requested, but no holdings
18444
18445     while(*p == ' ') p++;
18446
18447     /* Active color */
18448     c = *p++;
18449     if(appData.colorNickNames) {
18450       if( c == appData.colorNickNames[0] ) c = 'w'; else
18451       if( c == appData.colorNickNames[1] ) c = 'b';
18452     }
18453     switch (c) {
18454       case 'w':
18455         *blackPlaysFirst = FALSE;
18456         break;
18457       case 'b':
18458         *blackPlaysFirst = TRUE;
18459         break;
18460       default:
18461         return FALSE;
18462     }
18463
18464     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18465     /* return the extra info in global variiables             */
18466
18467     while(*p==' ') p++;
18468
18469     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18470         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18471         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18472     }
18473
18474     /* set defaults in case FEN is incomplete */
18475     board[EP_STATUS] = EP_UNKNOWN;
18476     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18477     for(i=0; i<nrCastlingRights; i++ ) {
18478         board[CASTLING][i] =
18479             appData.fischerCastling ? NoRights : initialRights[i];
18480     }   /* assume possible unless obviously impossible */
18481     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18482     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18483     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18484                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18485     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18486     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18487     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18488                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18489     FENrulePlies = 0;
18490
18491     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18492       char *q = p;
18493       int w=0, b=0;
18494       while(isalpha(*p)) {
18495         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18496         if(islower(*p)) b |= 1 << (*p++ - 'a');
18497       }
18498       if(*p == '-') p++;
18499       if(p != q) {
18500         board[TOUCHED_W] = ~w;
18501         board[TOUCHED_B] = ~b;
18502         while(*p == ' ') p++;
18503       }
18504     } else
18505
18506     if(nrCastlingRights) {
18507       int fischer = 0;
18508       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18509       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18510           /* castling indicator present, so default becomes no castlings */
18511           for(i=0; i<nrCastlingRights; i++ ) {
18512                  board[CASTLING][i] = NoRights;
18513           }
18514       }
18515       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18516              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18517              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18518              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18519         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18520
18521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18522             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18523             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18524         }
18525         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18526             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18527         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18528                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18529         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18530                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18531         switch(c) {
18532           case'K':
18533               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18534               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18535               board[CASTLING][2] = whiteKingFile;
18536               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18537               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18538               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18539               break;
18540           case'Q':
18541               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18542               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18543               board[CASTLING][2] = whiteKingFile;
18544               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18545               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18546               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18547               break;
18548           case'k':
18549               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18550               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18551               board[CASTLING][5] = blackKingFile;
18552               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18553               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18554               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18555               break;
18556           case'q':
18557               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18558               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18559               board[CASTLING][5] = blackKingFile;
18560               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18561               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18562               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18563           case '-':
18564               break;
18565           default: /* FRC castlings */
18566               if(c >= 'a') { /* black rights */
18567                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18568                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18569                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18570                   if(i == BOARD_RGHT) break;
18571                   board[CASTLING][5] = i;
18572                   c -= AAA;
18573                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18574                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18575                   if(c > i)
18576                       board[CASTLING][3] = c;
18577                   else
18578                       board[CASTLING][4] = c;
18579               } else { /* white rights */
18580                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18581                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18582                     if(board[0][i] == WhiteKing) break;
18583                   if(i == BOARD_RGHT) break;
18584                   board[CASTLING][2] = i;
18585                   c -= AAA - 'a' + 'A';
18586                   if(board[0][c] >= WhiteKing) break;
18587                   if(c > i)
18588                       board[CASTLING][0] = c;
18589                   else
18590                       board[CASTLING][1] = c;
18591               }
18592         }
18593       }
18594       for(i=0; i<nrCastlingRights; i++)
18595         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18596       if(gameInfo.variant == VariantSChess)
18597         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18598       if(fischer && shuffle) appData.fischerCastling = TRUE;
18599     if (appData.debugMode) {
18600         fprintf(debugFP, "FEN castling rights:");
18601         for(i=0; i<nrCastlingRights; i++)
18602         fprintf(debugFP, " %d", board[CASTLING][i]);
18603         fprintf(debugFP, "\n");
18604     }
18605
18606       while(*p==' ') p++;
18607     }
18608
18609     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18610
18611     /* read e.p. field in games that know e.p. capture */
18612     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18613        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18614        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18615       if(*p=='-') {
18616         p++; board[EP_STATUS] = EP_NONE;
18617       } else {
18618          char c = *p++ - AAA;
18619
18620          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18621          if(*p >= '0' && *p <='9') p++;
18622          board[EP_STATUS] = c;
18623       }
18624     }
18625
18626
18627     if(sscanf(p, "%d", &i) == 1) {
18628         FENrulePlies = i; /* 50-move ply counter */
18629         /* (The move number is still ignored)    */
18630     }
18631
18632     return TRUE;
18633 }
18634
18635 void
18636 EditPositionPasteFEN (char *fen)
18637 {
18638   if (fen != NULL) {
18639     Board initial_position;
18640
18641     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18642       DisplayError(_("Bad FEN position in clipboard"), 0);
18643       return ;
18644     } else {
18645       int savedBlackPlaysFirst = blackPlaysFirst;
18646       EditPositionEvent();
18647       blackPlaysFirst = savedBlackPlaysFirst;
18648       CopyBoard(boards[0], initial_position);
18649       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18650       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18651       DisplayBothClocks();
18652       DrawPosition(FALSE, boards[currentMove]);
18653     }
18654   }
18655 }
18656
18657 static char cseq[12] = "\\   ";
18658
18659 Boolean
18660 set_cont_sequence (char *new_seq)
18661 {
18662     int len;
18663     Boolean ret;
18664
18665     // handle bad attempts to set the sequence
18666         if (!new_seq)
18667                 return 0; // acceptable error - no debug
18668
18669     len = strlen(new_seq);
18670     ret = (len > 0) && (len < sizeof(cseq));
18671     if (ret)
18672       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18673     else if (appData.debugMode)
18674       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18675     return ret;
18676 }
18677
18678 /*
18679     reformat a source message so words don't cross the width boundary.  internal
18680     newlines are not removed.  returns the wrapped size (no null character unless
18681     included in source message).  If dest is NULL, only calculate the size required
18682     for the dest buffer.  lp argument indicats line position upon entry, and it's
18683     passed back upon exit.
18684 */
18685 int
18686 wrap (char *dest, char *src, int count, int width, int *lp)
18687 {
18688     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18689
18690     cseq_len = strlen(cseq);
18691     old_line = line = *lp;
18692     ansi = len = clen = 0;
18693
18694     for (i=0; i < count; i++)
18695     {
18696         if (src[i] == '\033')
18697             ansi = 1;
18698
18699         // if we hit the width, back up
18700         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18701         {
18702             // store i & len in case the word is too long
18703             old_i = i, old_len = len;
18704
18705             // find the end of the last word
18706             while (i && src[i] != ' ' && src[i] != '\n')
18707             {
18708                 i--;
18709                 len--;
18710             }
18711
18712             // word too long?  restore i & len before splitting it
18713             if ((old_i-i+clen) >= width)
18714             {
18715                 i = old_i;
18716                 len = old_len;
18717             }
18718
18719             // extra space?
18720             if (i && src[i-1] == ' ')
18721                 len--;
18722
18723             if (src[i] != ' ' && src[i] != '\n')
18724             {
18725                 i--;
18726                 if (len)
18727                     len--;
18728             }
18729
18730             // now append the newline and continuation sequence
18731             if (dest)
18732                 dest[len] = '\n';
18733             len++;
18734             if (dest)
18735                 strncpy(dest+len, cseq, cseq_len);
18736             len += cseq_len;
18737             line = cseq_len;
18738             clen = cseq_len;
18739             continue;
18740         }
18741
18742         if (dest)
18743             dest[len] = src[i];
18744         len++;
18745         if (!ansi)
18746             line++;
18747         if (src[i] == '\n')
18748             line = 0;
18749         if (src[i] == 'm')
18750             ansi = 0;
18751     }
18752     if (dest && appData.debugMode)
18753     {
18754         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18755             count, width, line, len, *lp);
18756         show_bytes(debugFP, src, count);
18757         fprintf(debugFP, "\ndest: ");
18758         show_bytes(debugFP, dest, len);
18759         fprintf(debugFP, "\n");
18760     }
18761     *lp = dest ? line : old_line;
18762
18763     return len;
18764 }
18765
18766 // [HGM] vari: routines for shelving variations
18767 Boolean modeRestore = FALSE;
18768
18769 void
18770 PushInner (int firstMove, int lastMove)
18771 {
18772         int i, j, nrMoves = lastMove - firstMove;
18773
18774         // push current tail of game on stack
18775         savedResult[storedGames] = gameInfo.result;
18776         savedDetails[storedGames] = gameInfo.resultDetails;
18777         gameInfo.resultDetails = NULL;
18778         savedFirst[storedGames] = firstMove;
18779         savedLast [storedGames] = lastMove;
18780         savedFramePtr[storedGames] = framePtr;
18781         framePtr -= nrMoves; // reserve space for the boards
18782         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18783             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18784             for(j=0; j<MOVE_LEN; j++)
18785                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18786             for(j=0; j<2*MOVE_LEN; j++)
18787                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18788             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18789             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18790             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18791             pvInfoList[firstMove+i-1].depth = 0;
18792             commentList[framePtr+i] = commentList[firstMove+i];
18793             commentList[firstMove+i] = NULL;
18794         }
18795
18796         storedGames++;
18797         forwardMostMove = firstMove; // truncate game so we can start variation
18798 }
18799
18800 void
18801 PushTail (int firstMove, int lastMove)
18802 {
18803         if(appData.icsActive) { // only in local mode
18804                 forwardMostMove = currentMove; // mimic old ICS behavior
18805                 return;
18806         }
18807         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18808
18809         PushInner(firstMove, lastMove);
18810         if(storedGames == 1) GreyRevert(FALSE);
18811         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18812 }
18813
18814 void
18815 PopInner (Boolean annotate)
18816 {
18817         int i, j, nrMoves;
18818         char buf[8000], moveBuf[20];
18819
18820         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18821         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18822         nrMoves = savedLast[storedGames] - currentMove;
18823         if(annotate) {
18824                 int cnt = 10;
18825                 if(!WhiteOnMove(currentMove))
18826                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18827                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18828                 for(i=currentMove; i<forwardMostMove; i++) {
18829                         if(WhiteOnMove(i))
18830                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18831                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18832                         strcat(buf, moveBuf);
18833                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18834                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18835                 }
18836                 strcat(buf, ")");
18837         }
18838         for(i=1; i<=nrMoves; i++) { // copy last variation back
18839             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18840             for(j=0; j<MOVE_LEN; j++)
18841                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18842             for(j=0; j<2*MOVE_LEN; j++)
18843                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18844             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18845             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18846             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18847             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18848             commentList[currentMove+i] = commentList[framePtr+i];
18849             commentList[framePtr+i] = NULL;
18850         }
18851         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18852         framePtr = savedFramePtr[storedGames];
18853         gameInfo.result = savedResult[storedGames];
18854         if(gameInfo.resultDetails != NULL) {
18855             free(gameInfo.resultDetails);
18856       }
18857         gameInfo.resultDetails = savedDetails[storedGames];
18858         forwardMostMove = currentMove + nrMoves;
18859 }
18860
18861 Boolean
18862 PopTail (Boolean annotate)
18863 {
18864         if(appData.icsActive) return FALSE; // only in local mode
18865         if(!storedGames) return FALSE; // sanity
18866         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18867
18868         PopInner(annotate);
18869         if(currentMove < forwardMostMove) ForwardEvent(); else
18870         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18871
18872         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18873         return TRUE;
18874 }
18875
18876 void
18877 CleanupTail ()
18878 {       // remove all shelved variations
18879         int i;
18880         for(i=0; i<storedGames; i++) {
18881             if(savedDetails[i])
18882                 free(savedDetails[i]);
18883             savedDetails[i] = NULL;
18884         }
18885         for(i=framePtr; i<MAX_MOVES; i++) {
18886                 if(commentList[i]) free(commentList[i]);
18887                 commentList[i] = NULL;
18888         }
18889         framePtr = MAX_MOVES-1;
18890         storedGames = 0;
18891 }
18892
18893 void
18894 LoadVariation (int index, char *text)
18895 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18896         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18897         int level = 0, move;
18898
18899         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18900         // first find outermost bracketing variation
18901         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18902             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18903                 if(*p == '{') wait = '}'; else
18904                 if(*p == '[') wait = ']'; else
18905                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18906                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18907             }
18908             if(*p == wait) wait = NULLCHAR; // closing ]} found
18909             p++;
18910         }
18911         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18912         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18913         end[1] = NULLCHAR; // clip off comment beyond variation
18914         ToNrEvent(currentMove-1);
18915         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18916         // kludge: use ParsePV() to append variation to game
18917         move = currentMove;
18918         ParsePV(start, TRUE, TRUE);
18919         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18920         ClearPremoveHighlights();
18921         CommentPopDown();
18922         ToNrEvent(currentMove+1);
18923 }
18924
18925 void
18926 LoadTheme ()
18927 {
18928     char *p, *q, buf[MSG_SIZ];
18929     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18930         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18931         ParseArgsFromString(buf);
18932         ActivateTheme(TRUE); // also redo colors
18933         return;
18934     }
18935     p = nickName;
18936     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18937     {
18938         int len;
18939         q = appData.themeNames;
18940         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18941       if(appData.useBitmaps) {
18942         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18943                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18944                 appData.liteBackTextureMode,
18945                 appData.darkBackTextureMode );
18946       } else {
18947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18948                 Col2Text(2),   // lightSquareColor
18949                 Col2Text(3) ); // darkSquareColor
18950       }
18951       if(appData.useBorder) {
18952         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18953                 appData.border);
18954       } else {
18955         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18956       }
18957       if(appData.useFont) {
18958         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18959                 appData.renderPiecesWithFont,
18960                 appData.fontToPieceTable,
18961                 Col2Text(9),    // appData.fontBackColorWhite
18962                 Col2Text(10) ); // appData.fontForeColorBlack
18963       } else {
18964         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18965                 appData.pieceDirectory);
18966         if(!appData.pieceDirectory[0])
18967           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18968                 Col2Text(0),   // whitePieceColor
18969                 Col2Text(1) ); // blackPieceColor
18970       }
18971       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18972                 Col2Text(4),   // highlightSquareColor
18973                 Col2Text(5) ); // premoveHighlightColor
18974         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18975         if(insert != q) insert[-1] = NULLCHAR;
18976         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18977         if(q)   free(q);
18978     }
18979     ActivateTheme(FALSE);
18980 }