Clear highlights after moving piece in Edit Position
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <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
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #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"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         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
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 static int pieceOrder[] = {
6011   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6012  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6013  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6014  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6015  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6016  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6017 };
6018
6019 int
6020 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6021 /* [HGM] moved here from winboard.c because of its general usefulness */
6022 /*       Basically a safe strcpy that uses the last character as King */
6023 {
6024     int result = FALSE; int NrPieces;
6025     unsigned char partner[EmptySquare];
6026
6027     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6028                     && NrPieces >= 12 && !(NrPieces&1)) {
6029         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6030
6031         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6032         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             i = pieceOrder[ii];
6035             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6036             table[i] = map[j++];
6037             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6038             if(c) partner[i] = table[i], table[i] = c;
6039         }
6040         table[(int) WhiteKing]  = map[j++];
6041         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6042             char *p, c=0;
6043             i = WHITE_TO_BLACK pieceOrder[ii];
6044             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6045             table[i] = map[j++];
6046             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6047             if(c) partner[i] = table[i], table[i] = c;
6048         }
6049         table[(int) BlackKing]  = map[j++];
6050
6051
6052         if(*escapes) { // set up promotion pairing
6053             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6054             // pieceToChar entirely filled, so we can look up specified partners
6055             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6056                 int c = table[i];
6057                 if(c == '^' || c == '-') { // has specified partner
6058                     int p;
6059                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6060                     if(c == '^') table[i] = '+';
6061                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6062                 } else if(c == '*') {
6063                     table[i] = partner[i];
6064                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6065                 }
6066             }
6067         }
6068
6069         result = TRUE;
6070     }
6071
6072     return result;
6073 }
6074
6075 int
6076 SetCharTable (unsigned char *table, const char * map)
6077 {
6078     return SetCharTableEsc(table, map, "");
6079 }
6080
6081 void
6082 Prelude (Board board)
6083 {       // [HGM] superchess: random selection of exo-pieces
6084         int i, j, k; ChessSquare p;
6085         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6086
6087         GetPositionNumber(); // use FRC position number
6088
6089         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6090             SetCharTable(pieceToChar, appData.pieceToCharTable);
6091             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6092                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6093         }
6094
6095         j = seed%4;                 seed /= 4;
6096         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6097         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6098         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6099         j = seed%3 + (seed%3 >= j); seed /= 3;
6100         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6101         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6102         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6103         j = seed%3;                 seed /= 3;
6104         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6105         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6106         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6107         j = seed%2 + (seed%2 >= j); seed /= 2;
6108         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6109         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6110         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6111         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6112         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6113         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6114         put(board, exoPieces[0],    0, 0, ANY);
6115         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6116 }
6117
6118 void
6119 InitPosition (int redraw)
6120 {
6121     ChessSquare (* pieces)[BOARD_FILES];
6122     int i, j, pawnRow=1, pieceRows=1, overrule,
6123     oldx = gameInfo.boardWidth,
6124     oldy = gameInfo.boardHeight,
6125     oldh = gameInfo.holdingsWidth;
6126     static int oldv;
6127
6128     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6129
6130     /* [AS] Initialize pv info list [HGM] and game status */
6131     {
6132         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6133             pvInfoList[i].depth = 0;
6134             boards[i][EP_STATUS] = EP_NONE;
6135             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6136         }
6137
6138         initialRulePlies = 0; /* 50-move counter start */
6139
6140         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6141         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6142     }
6143
6144
6145     /* [HGM] logic here is completely changed. In stead of full positions */
6146     /* the initialized data only consist of the two backranks. The switch */
6147     /* selects which one we will use, which is than copied to the Board   */
6148     /* initialPosition, which for the rest is initialized by Pawns and    */
6149     /* empty squares. This initial position is then copied to boards[0],  */
6150     /* possibly after shuffling, so that it remains available.            */
6151
6152     gameInfo.holdingsWidth = 0; /* default board sizes */
6153     gameInfo.boardWidth    = 8;
6154     gameInfo.boardHeight   = 8;
6155     gameInfo.holdingsSize  = 0;
6156     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6157     for(i=0; i<BOARD_FILES-6; i++)
6158       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6159     initialPosition[EP_STATUS] = EP_NONE;
6160     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6161     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6162     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6163          SetCharTable(pieceNickName, appData.pieceNickNames);
6164     else SetCharTable(pieceNickName, "............");
6165     pieces = FIDEArray;
6166
6167     switch (gameInfo.variant) {
6168     case VariantFischeRandom:
6169       shuffleOpenings = TRUE;
6170       appData.fischerCastling = TRUE;
6171     default:
6172       break;
6173     case VariantShatranj:
6174       pieces = ShatranjArray;
6175       nrCastlingRights = 0;
6176       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6177       break;
6178     case VariantMakruk:
6179       pieces = makrukArray;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6182       break;
6183     case VariantASEAN:
6184       pieces = aseanArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6187       break;
6188     case VariantTwoKings:
6189       pieces = twoKingsArray;
6190       break;
6191     case VariantGrand:
6192       pieces = GrandArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6195       gameInfo.boardWidth = 10;
6196       gameInfo.boardHeight = 10;
6197       gameInfo.holdingsSize = 7;
6198       break;
6199     case VariantCapaRandom:
6200       shuffleOpenings = TRUE;
6201       appData.fischerCastling = TRUE;
6202     case VariantCapablanca:
6203       pieces = CapablancaArray;
6204       gameInfo.boardWidth = 10;
6205       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6206       break;
6207     case VariantGothic:
6208       pieces = GothicArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6211       break;
6212     case VariantSChess:
6213       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6214       gameInfo.holdingsSize = 7;
6215       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6216       break;
6217     case VariantJanus:
6218       pieces = JanusArray;
6219       gameInfo.boardWidth = 10;
6220       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6221       nrCastlingRights = 6;
6222         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6223         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6224         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6225         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6226         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6227         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6228       break;
6229     case VariantFalcon:
6230       pieces = FalconArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6233       break;
6234     case VariantXiangqi:
6235       pieces = XiangqiArray;
6236       gameInfo.boardWidth  = 9;
6237       gameInfo.boardHeight = 10;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6240       break;
6241     case VariantShogi:
6242       pieces = ShogiArray;
6243       gameInfo.boardWidth  = 9;
6244       gameInfo.boardHeight = 9;
6245       gameInfo.holdingsSize = 7;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6248       break;
6249     case VariantChu:
6250       pieces = ChuArray; pieceRows = 3;
6251       gameInfo.boardWidth  = 12;
6252       gameInfo.boardHeight = 12;
6253       nrCastlingRights = 0;
6254       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6255                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6256       break;
6257     case VariantCourier:
6258       pieces = CourierArray;
6259       gameInfo.boardWidth  = 12;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6262       break;
6263     case VariantKnightmate:
6264       pieces = KnightmateArray;
6265       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6266       break;
6267     case VariantSpartan:
6268       pieces = SpartanArray;
6269       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6270       break;
6271     case VariantLion:
6272       pieces = lionArray;
6273       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6274       break;
6275     case VariantChuChess:
6276       pieces = ChuChessArray;
6277       gameInfo.boardWidth = 10;
6278       gameInfo.boardHeight = 10;
6279       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6280       break;
6281     case VariantFairy:
6282       pieces = fairyArray;
6283       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6284       break;
6285     case VariantGreat:
6286       pieces = GreatArray;
6287       gameInfo.boardWidth = 10;
6288       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6289       gameInfo.holdingsSize = 8;
6290       break;
6291     case VariantSuper:
6292       pieces = FIDEArray;
6293       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6294       gameInfo.holdingsSize = 8;
6295       startedFromSetupPosition = TRUE;
6296       break;
6297     case VariantCrazyhouse:
6298     case VariantBughouse:
6299       pieces = FIDEArray;
6300       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6301       gameInfo.holdingsSize = 5;
6302       break;
6303     case VariantWildCastle:
6304       pieces = FIDEArray;
6305       /* !!?shuffle with kings guaranteed to be on d or e file */
6306       shuffleOpenings = 1;
6307       break;
6308     case VariantNoCastle:
6309       pieces = FIDEArray;
6310       nrCastlingRights = 0;
6311       /* !!?unconstrained back-rank shuffle */
6312       shuffleOpenings = 1;
6313       break;
6314     }
6315
6316     overrule = 0;
6317     if(appData.NrFiles >= 0) {
6318         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6319         gameInfo.boardWidth = appData.NrFiles;
6320     }
6321     if(appData.NrRanks >= 0) {
6322         gameInfo.boardHeight = appData.NrRanks;
6323     }
6324     if(appData.holdingsSize >= 0) {
6325         i = appData.holdingsSize;
6326         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6327         gameInfo.holdingsSize = i;
6328     }
6329     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6330     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6331         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6332
6333     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6334     if(pawnRow < 1) pawnRow = 1;
6335     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6336        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6337     if(gameInfo.variant == VariantChu) pawnRow = 3;
6338
6339     /* User pieceToChar list overrules defaults */
6340     if(appData.pieceToCharTable != NULL)
6341         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6342
6343     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6344
6345         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6346             s = (ChessSquare) 0; /* account holding counts in guard band */
6347         for( i=0; i<BOARD_HEIGHT; i++ )
6348             initialPosition[i][j] = s;
6349
6350         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6351         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6352         initialPosition[pawnRow][j] = WhitePawn;
6353         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6354         if(gameInfo.variant == VariantXiangqi) {
6355             if(j&1) {
6356                 initialPosition[pawnRow][j] =
6357                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6358                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6359                    initialPosition[2][j] = WhiteCannon;
6360                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6361                 }
6362             }
6363         }
6364         if(gameInfo.variant == VariantChu) {
6365              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6366                initialPosition[pawnRow+1][j] = WhiteCobra,
6367                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6368              for(i=1; i<pieceRows; i++) {
6369                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6370                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6371              }
6372         }
6373         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6374             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6375                initialPosition[0][j] = WhiteRook;
6376                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6377             }
6378         }
6379         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6380     }
6381     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6382     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6383
6384             j=BOARD_LEFT+1;
6385             initialPosition[1][j] = WhiteBishop;
6386             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6387             j=BOARD_RGHT-2;
6388             initialPosition[1][j] = WhiteRook;
6389             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6390     }
6391
6392     if( nrCastlingRights == -1) {
6393         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6394         /*       This sets default castling rights from none to normal corners   */
6395         /* Variants with other castling rights must set them themselves above    */
6396         nrCastlingRights = 6;
6397
6398         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6399         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6400         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6401         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6402         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6403         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6404      }
6405
6406      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6407      if(gameInfo.variant == VariantGreat) { // promotion commoners
6408         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6409         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6410         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6411         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6412      }
6413      if( gameInfo.variant == VariantSChess ) {
6414       initialPosition[1][0] = BlackMarshall;
6415       initialPosition[2][0] = BlackAngel;
6416       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6417       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6418       initialPosition[1][1] = initialPosition[2][1] =
6419       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6420      }
6421   if (appData.debugMode) {
6422     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6423   }
6424     if(shuffleOpenings) {
6425         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6426         startedFromSetupPosition = TRUE;
6427     }
6428     if(startedFromPositionFile) {
6429       /* [HGM] loadPos: use PositionFile for every new game */
6430       CopyBoard(initialPosition, filePosition);
6431       for(i=0; i<nrCastlingRights; i++)
6432           initialRights[i] = filePosition[CASTLING][i];
6433       startedFromSetupPosition = TRUE;
6434     }
6435
6436     CopyBoard(boards[0], initialPosition);
6437
6438     if(oldx != gameInfo.boardWidth ||
6439        oldy != gameInfo.boardHeight ||
6440        oldv != gameInfo.variant ||
6441        oldh != gameInfo.holdingsWidth
6442                                          )
6443             InitDrawingSizes(-2 ,0);
6444
6445     oldv = gameInfo.variant;
6446     if (redraw)
6447       DrawPosition(TRUE, boards[currentMove]);
6448 }
6449
6450 void
6451 SendBoard (ChessProgramState *cps, int moveNum)
6452 {
6453     char message[MSG_SIZ];
6454
6455     if (cps->useSetboard) {
6456       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6457       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6458       SendToProgram(message, cps);
6459       free(fen);
6460
6461     } else {
6462       ChessSquare *bp;
6463       int i, j, left=0, right=BOARD_WIDTH;
6464       /* Kludge to set black to move, avoiding the troublesome and now
6465        * deprecated "black" command.
6466        */
6467       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6468         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6469
6470       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6471
6472       SendToProgram("edit\n", cps);
6473       SendToProgram("#\n", cps);
6474       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6475         bp = &boards[moveNum][i][left];
6476         for (j = left; j < right; j++, bp++) {
6477           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6478           if ((int) *bp < (int) BlackPawn) {
6479             if(j == BOARD_RGHT+1)
6480                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6481             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6482             if(message[0] == '+' || message[0] == '~') {
6483               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6484                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6485                         AAA + j, ONE + i - '0');
6486             }
6487             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6488                 message[1] = BOARD_RGHT   - 1 - j + '1';
6489                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6490             }
6491             SendToProgram(message, cps);
6492           }
6493         }
6494       }
6495
6496       SendToProgram("c\n", cps);
6497       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6498         bp = &boards[moveNum][i][left];
6499         for (j = left; j < right; j++, bp++) {
6500           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6501           if (((int) *bp != (int) EmptySquare)
6502               && ((int) *bp >= (int) BlackPawn)) {
6503             if(j == BOARD_LEFT-2)
6504                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6505             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6506                     AAA + j, ONE + i - '0');
6507             if(message[0] == '+' || message[0] == '~') {
6508               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6509                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6510                         AAA + j, ONE + i - '0');
6511             }
6512             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6513                 message[1] = BOARD_RGHT   - 1 - j + '1';
6514                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6515             }
6516             SendToProgram(message, cps);
6517           }
6518         }
6519       }
6520
6521       SendToProgram(".\n", cps);
6522     }
6523     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6524 }
6525
6526 char exclusionHeader[MSG_SIZ];
6527 int exCnt, excludePtr;
6528 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6529 static Exclusion excluTab[200];
6530 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6531
6532 static void
6533 WriteMap (int s)
6534 {
6535     int j;
6536     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6537     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6538 }
6539
6540 static void
6541 ClearMap ()
6542 {
6543     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6544     excludePtr = 24; exCnt = 0;
6545     WriteMap(0);
6546 }
6547
6548 static void
6549 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6550 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6551     char buf[2*MOVE_LEN], *p;
6552     Exclusion *e = excluTab;
6553     int i;
6554     for(i=0; i<exCnt; i++)
6555         if(e[i].ff == fromX && e[i].fr == fromY &&
6556            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6557     if(i == exCnt) { // was not in exclude list; add it
6558         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6559         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6560             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6561             return; // abort
6562         }
6563         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6564         excludePtr++; e[i].mark = excludePtr++;
6565         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6566         exCnt++;
6567     }
6568     exclusionHeader[e[i].mark] = state;
6569 }
6570
6571 static int
6572 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6573 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6574     char buf[MSG_SIZ];
6575     int j, k;
6576     ChessMove moveType;
6577     if((signed char)promoChar == -1) { // kludge to indicate best move
6578         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6579             return 1; // if unparsable, abort
6580     }
6581     // update exclusion map (resolving toggle by consulting existing state)
6582     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6583     j = k%8; k >>= 3;
6584     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6585     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6586          excludeMap[k] |=   1<<j;
6587     else excludeMap[k] &= ~(1<<j);
6588     // update header
6589     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6590     // inform engine
6591     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6592     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6593     SendToBoth(buf);
6594     return (state == '+');
6595 }
6596
6597 static void
6598 ExcludeClick (int index)
6599 {
6600     int i, j;
6601     Exclusion *e = excluTab;
6602     if(index < 25) { // none, best or tail clicked
6603         if(index < 13) { // none: include all
6604             WriteMap(0); // clear map
6605             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6606             SendToBoth("include all\n"); // and inform engine
6607         } else if(index > 18) { // tail
6608             if(exclusionHeader[19] == '-') { // tail was excluded
6609                 SendToBoth("include all\n");
6610                 WriteMap(0); // clear map completely
6611                 // now re-exclude selected moves
6612                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6613                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6614             } else { // tail was included or in mixed state
6615                 SendToBoth("exclude all\n");
6616                 WriteMap(0xFF); // fill map completely
6617                 // now re-include selected moves
6618                 j = 0; // count them
6619                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6620                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6621                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6622             }
6623         } else { // best
6624             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6625         }
6626     } else {
6627         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6628             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6629             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6630             break;
6631         }
6632     }
6633 }
6634
6635 ChessSquare
6636 DefaultPromoChoice (int white)
6637 {
6638     ChessSquare result;
6639     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6640        gameInfo.variant == VariantMakruk)
6641         result = WhiteFerz; // no choice
6642     else if(gameInfo.variant == VariantASEAN)
6643         result = WhiteRook; // no choice
6644     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6645         result= WhiteKing; // in Suicide Q is the last thing we want
6646     else if(gameInfo.variant == VariantSpartan)
6647         result = white ? WhiteQueen : WhiteAngel;
6648     else result = WhiteQueen;
6649     if(!white) result = WHITE_TO_BLACK result;
6650     return result;
6651 }
6652
6653 static int autoQueen; // [HGM] oneclick
6654
6655 int
6656 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6657 {
6658     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6659     /* [HGM] add Shogi promotions */
6660     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6661     ChessSquare piece, partner;
6662     ChessMove moveType;
6663     Boolean premove;
6664
6665     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6666     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6667
6668     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6669       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6670         return FALSE;
6671
6672     piece = boards[currentMove][fromY][fromX];
6673     if(gameInfo.variant == VariantChu) {
6674         promotionZoneSize = BOARD_HEIGHT/3;
6675         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6676     } else if(gameInfo.variant == VariantShogi) {
6677         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6678         highestPromotingPiece = (int)WhiteAlfil;
6679     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6680         promotionZoneSize = 3;
6681     }
6682
6683     // Treat Lance as Pawn when it is not representing Amazon or Lance
6684     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6685         if(piece == WhiteLance) piece = WhitePawn; else
6686         if(piece == BlackLance) piece = BlackPawn;
6687     }
6688
6689     // next weed out all moves that do not touch the promotion zone at all
6690     if((int)piece >= BlackPawn) {
6691         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6692              return FALSE;
6693         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6694         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6695     } else {
6696         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6697            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6698         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6699              return FALSE;
6700     }
6701
6702     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6703
6704     // weed out mandatory Shogi promotions
6705     if(gameInfo.variant == VariantShogi) {
6706         if(piece >= BlackPawn) {
6707             if(toY == 0 && piece == BlackPawn ||
6708                toY == 0 && piece == BlackQueen ||
6709                toY <= 1 && piece == BlackKnight) {
6710                 *promoChoice = '+';
6711                 return FALSE;
6712             }
6713         } else {
6714             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6715                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6716                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6717                 *promoChoice = '+';
6718                 return FALSE;
6719             }
6720         }
6721     }
6722
6723     // weed out obviously illegal Pawn moves
6724     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6725         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6726         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6727         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6728         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6729         // note we are not allowed to test for valid (non-)capture, due to premove
6730     }
6731
6732     // we either have a choice what to promote to, or (in Shogi) whether to promote
6733     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6734        gameInfo.variant == VariantMakruk) {
6735         ChessSquare p=BlackFerz;  // no choice
6736         while(p < EmptySquare) {  //but make sure we use piece that exists
6737             *promoChoice = PieceToChar(p++);
6738             if(*promoChoice != '.') break;
6739         }
6740         return FALSE;
6741     }
6742     // no sense asking what we must promote to if it is going to explode...
6743     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6744         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6745         return FALSE;
6746     }
6747     // give caller the default choice even if we will not make it
6748     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6749     partner = piece; // pieces can promote if the pieceToCharTable says so
6750     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6751     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6752     if(        sweepSelect && gameInfo.variant != VariantGreat
6753                            && gameInfo.variant != VariantGrand
6754                            && gameInfo.variant != VariantSuper) return FALSE;
6755     if(autoQueen) return FALSE; // predetermined
6756
6757     // suppress promotion popup on illegal moves that are not premoves
6758     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6759               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6760     if(appData.testLegality && !premove) {
6761         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6762                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6763         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6764         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6765             return FALSE;
6766     }
6767
6768     return TRUE;
6769 }
6770
6771 int
6772 InPalace (int row, int column)
6773 {   /* [HGM] for Xiangqi */
6774     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6775          column < (BOARD_WIDTH + 4)/2 &&
6776          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6777     return FALSE;
6778 }
6779
6780 int
6781 PieceForSquare (int x, int y)
6782 {
6783   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6784      return -1;
6785   else
6786      return boards[currentMove][y][x];
6787 }
6788
6789 int
6790 OKToStartUserMove (int x, int y)
6791 {
6792     ChessSquare from_piece;
6793     int white_piece;
6794
6795     if (matchMode) return FALSE;
6796     if (gameMode == EditPosition) return TRUE;
6797
6798     if (x >= 0 && y >= 0)
6799       from_piece = boards[currentMove][y][x];
6800     else
6801       from_piece = EmptySquare;
6802
6803     if (from_piece == EmptySquare) return FALSE;
6804
6805     white_piece = (int)from_piece >= (int)WhitePawn &&
6806       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6807
6808     switch (gameMode) {
6809       case AnalyzeFile:
6810       case TwoMachinesPlay:
6811       case EndOfGame:
6812         return FALSE;
6813
6814       case IcsObserving:
6815       case IcsIdle:
6816         return FALSE;
6817
6818       case MachinePlaysWhite:
6819       case IcsPlayingBlack:
6820         if (appData.zippyPlay) return FALSE;
6821         if (white_piece) {
6822             DisplayMoveError(_("You are playing Black"));
6823             return FALSE;
6824         }
6825         break;
6826
6827       case MachinePlaysBlack:
6828       case IcsPlayingWhite:
6829         if (appData.zippyPlay) return FALSE;
6830         if (!white_piece) {
6831             DisplayMoveError(_("You are playing White"));
6832             return FALSE;
6833         }
6834         break;
6835
6836       case PlayFromGameFile:
6837             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6838       case EditGame:
6839         if (!white_piece && WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is White's turn"));
6841             return FALSE;
6842         }
6843         if (white_piece && !WhiteOnMove(currentMove)) {
6844             DisplayMoveError(_("It is Black's turn"));
6845             return FALSE;
6846         }
6847         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6848             /* Editing correspondence game history */
6849             /* Could disallow this or prompt for confirmation */
6850             cmailOldMove = -1;
6851         }
6852         break;
6853
6854       case BeginningOfGame:
6855         if (appData.icsActive) return FALSE;
6856         if (!appData.noChessProgram) {
6857             if (!white_piece) {
6858                 DisplayMoveError(_("You are playing White"));
6859                 return FALSE;
6860             }
6861         }
6862         break;
6863
6864       case Training:
6865         if (!white_piece && WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is White's turn"));
6867             return FALSE;
6868         }
6869         if (white_piece && !WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is Black's turn"));
6871             return FALSE;
6872         }
6873         break;
6874
6875       default:
6876       case IcsExamining:
6877         break;
6878     }
6879     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6880         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6881         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6882         && gameMode != AnalyzeFile && gameMode != Training) {
6883         DisplayMoveError(_("Displayed position is not current"));
6884         return FALSE;
6885     }
6886     return TRUE;
6887 }
6888
6889 Boolean
6890 OnlyMove (int *x, int *y, Boolean captures)
6891 {
6892     DisambiguateClosure cl;
6893     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6894     switch(gameMode) {
6895       case MachinePlaysBlack:
6896       case IcsPlayingWhite:
6897       case BeginningOfGame:
6898         if(!WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case MachinePlaysWhite:
6901       case IcsPlayingBlack:
6902         if(WhiteOnMove(currentMove)) return FALSE;
6903         break;
6904       case EditGame:
6905         break;
6906       default:
6907         return FALSE;
6908     }
6909     cl.pieceIn = EmptySquare;
6910     cl.rfIn = *y;
6911     cl.ffIn = *x;
6912     cl.rtIn = -1;
6913     cl.ftIn = -1;
6914     cl.promoCharIn = NULLCHAR;
6915     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6916     if( cl.kind == NormalMove ||
6917         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6918         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6919         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6920       fromX = cl.ff;
6921       fromY = cl.rf;
6922       *x = cl.ft;
6923       *y = cl.rt;
6924       return TRUE;
6925     }
6926     if(cl.kind != ImpossibleMove) return FALSE;
6927     cl.pieceIn = EmptySquare;
6928     cl.rfIn = -1;
6929     cl.ffIn = -1;
6930     cl.rtIn = *y;
6931     cl.ftIn = *x;
6932     cl.promoCharIn = NULLCHAR;
6933     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6934     if( cl.kind == NormalMove ||
6935         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6936         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6937         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6938       fromX = cl.ff;
6939       fromY = cl.rf;
6940       *x = cl.ft;
6941       *y = cl.rt;
6942       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6943       return TRUE;
6944     }
6945     return FALSE;
6946 }
6947
6948 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6949 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6950 int lastLoadGameUseList = FALSE;
6951 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6952 ChessMove lastLoadGameStart = EndOfFile;
6953 int doubleClick;
6954 Boolean addToBookFlag;
6955
6956 void
6957 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6958 {
6959     ChessMove moveType;
6960     ChessSquare pup;
6961     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6962
6963     /* Check if the user is playing in turn.  This is complicated because we
6964        let the user "pick up" a piece before it is his turn.  So the piece he
6965        tried to pick up may have been captured by the time he puts it down!
6966        Therefore we use the color the user is supposed to be playing in this
6967        test, not the color of the piece that is currently on the starting
6968        square---except in EditGame mode, where the user is playing both
6969        sides; fortunately there the capture race can't happen.  (It can
6970        now happen in IcsExamining mode, but that's just too bad.  The user
6971        will get a somewhat confusing message in that case.)
6972        */
6973
6974     switch (gameMode) {
6975       case AnalyzeFile:
6976       case TwoMachinesPlay:
6977       case EndOfGame:
6978       case IcsObserving:
6979       case IcsIdle:
6980         /* We switched into a game mode where moves are not accepted,
6981            perhaps while the mouse button was down. */
6982         return;
6983
6984       case MachinePlaysWhite:
6985         /* User is moving for Black */
6986         if (WhiteOnMove(currentMove)) {
6987             DisplayMoveError(_("It is White's turn"));
6988             return;
6989         }
6990         break;
6991
6992       case MachinePlaysBlack:
6993         /* User is moving for White */
6994         if (!WhiteOnMove(currentMove)) {
6995             DisplayMoveError(_("It is Black's turn"));
6996             return;
6997         }
6998         break;
6999
7000       case PlayFromGameFile:
7001             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7002       case EditGame:
7003       case IcsExamining:
7004       case BeginningOfGame:
7005       case AnalyzeMode:
7006       case Training:
7007         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7008         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7009             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7010             /* User is moving for Black */
7011             if (WhiteOnMove(currentMove)) {
7012                 DisplayMoveError(_("It is White's turn"));
7013                 return;
7014             }
7015         } else {
7016             /* User is moving for White */
7017             if (!WhiteOnMove(currentMove)) {
7018                 DisplayMoveError(_("It is Black's turn"));
7019                 return;
7020             }
7021         }
7022         break;
7023
7024       case IcsPlayingBlack:
7025         /* User is moving for Black */
7026         if (WhiteOnMove(currentMove)) {
7027             if (!appData.premove) {
7028                 DisplayMoveError(_("It is White's turn"));
7029             } else if (toX >= 0 && toY >= 0) {
7030                 premoveToX = toX;
7031                 premoveToY = toY;
7032                 premoveFromX = fromX;
7033                 premoveFromY = fromY;
7034                 premovePromoChar = promoChar;
7035                 gotPremove = 1;
7036                 if (appData.debugMode)
7037                     fprintf(debugFP, "Got premove: fromX %d,"
7038                             "fromY %d, toX %d, toY %d\n",
7039                             fromX, fromY, toX, toY);
7040             }
7041             return;
7042         }
7043         break;
7044
7045       case IcsPlayingWhite:
7046         /* User is moving for White */
7047         if (!WhiteOnMove(currentMove)) {
7048             if (!appData.premove) {
7049                 DisplayMoveError(_("It is Black's turn"));
7050             } else if (toX >= 0 && toY >= 0) {
7051                 premoveToX = toX;
7052                 premoveToY = toY;
7053                 premoveFromX = fromX;
7054                 premoveFromY = fromY;
7055                 premovePromoChar = promoChar;
7056                 gotPremove = 1;
7057                 if (appData.debugMode)
7058                     fprintf(debugFP, "Got premove: fromX %d,"
7059                             "fromY %d, toX %d, toY %d\n",
7060                             fromX, fromY, toX, toY);
7061             }
7062             return;
7063         }
7064         break;
7065
7066       default:
7067         break;
7068
7069       case EditPosition:
7070         /* EditPosition, empty square, or different color piece;
7071            click-click move is possible */
7072         if (toX == -2 || toY == -2) {
7073             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7074             DrawPosition(FALSE, boards[currentMove]);
7075             return;
7076         } else if (toX >= 0 && toY >= 0) {
7077             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7078                 ChessSquare q, p = boards[0][rf][ff];
7079                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7080                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7081                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7082                 if(PieceToChar(q) == '+') gatingPiece = p;
7083             }
7084             boards[0][toY][toX] = boards[0][fromY][fromX];
7085             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7086                 if(boards[0][fromY][0] != EmptySquare) {
7087                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7088                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7089                 }
7090             } else
7091             if(fromX == BOARD_RGHT+1) {
7092                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7093                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7094                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7095                 }
7096             } else
7097             boards[0][fromY][fromX] = gatingPiece;
7098             ClearHighlights();
7099             DrawPosition(FALSE, boards[currentMove]);
7100             return;
7101         }
7102         return;
7103     }
7104
7105     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7106     pup = boards[currentMove][toY][toX];
7107
7108     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7109     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7110          if( pup != EmptySquare ) return;
7111          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7112            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7113                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7114            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7115            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7116            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7117            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7118          fromY = DROP_RANK;
7119     }
7120
7121     /* [HGM] always test for legality, to get promotion info */
7122     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7123                                          fromY, fromX, toY, toX, promoChar);
7124
7125     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7126
7127     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7128
7129     /* [HGM] but possibly ignore an IllegalMove result */
7130     if (appData.testLegality) {
7131         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7132             DisplayMoveError(_("Illegal move"));
7133             return;
7134         }
7135     }
7136
7137     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7138         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7139              ClearPremoveHighlights(); // was included
7140         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7141         return;
7142     }
7143
7144     if(addToBookFlag) { // adding moves to book
7145         char buf[MSG_SIZ], move[MSG_SIZ];
7146         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7147         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7148         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7149         AddBookMove(buf);
7150         addToBookFlag = FALSE;
7151         ClearHighlights();
7152         return;
7153     }
7154
7155     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7156 }
7157
7158 /* Common tail of UserMoveEvent and DropMenuEvent */
7159 int
7160 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7161 {
7162     char *bookHit = 0;
7163
7164     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7165         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7166         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7167         if(WhiteOnMove(currentMove)) {
7168             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7169         } else {
7170             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7171         }
7172     }
7173
7174     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7175        move type in caller when we know the move is a legal promotion */
7176     if(moveType == NormalMove && promoChar)
7177         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7178
7179     /* [HGM] <popupFix> The following if has been moved here from
7180        UserMoveEvent(). Because it seemed to belong here (why not allow
7181        piece drops in training games?), and because it can only be
7182        performed after it is known to what we promote. */
7183     if (gameMode == Training) {
7184       /* compare the move played on the board to the next move in the
7185        * game. If they match, display the move and the opponent's response.
7186        * If they don't match, display an error message.
7187        */
7188       int saveAnimate;
7189       Board testBoard;
7190       CopyBoard(testBoard, boards[currentMove]);
7191       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7192
7193       if (CompareBoards(testBoard, boards[currentMove+1])) {
7194         ForwardInner(currentMove+1);
7195
7196         /* Autoplay the opponent's response.
7197          * if appData.animate was TRUE when Training mode was entered,
7198          * the response will be animated.
7199          */
7200         saveAnimate = appData.animate;
7201         appData.animate = animateTraining;
7202         ForwardInner(currentMove+1);
7203         appData.animate = saveAnimate;
7204
7205         /* check for the end of the game */
7206         if (currentMove >= forwardMostMove) {
7207           gameMode = PlayFromGameFile;
7208           ModeHighlight();
7209           SetTrainingModeOff();
7210           DisplayInformation(_("End of game"));
7211         }
7212       } else {
7213         DisplayError(_("Incorrect move"), 0);
7214       }
7215       return 1;
7216     }
7217
7218   /* Ok, now we know that the move is good, so we can kill
7219      the previous line in Analysis Mode */
7220   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7221                                 && currentMove < forwardMostMove) {
7222     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7223     else forwardMostMove = currentMove;
7224   }
7225
7226   ClearMap();
7227
7228   /* If we need the chess program but it's dead, restart it */
7229   ResurrectChessProgram();
7230
7231   /* A user move restarts a paused game*/
7232   if (pausing)
7233     PauseEvent();
7234
7235   thinkOutput[0] = NULLCHAR;
7236
7237   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7238
7239   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7240     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7241     return 1;
7242   }
7243
7244   if (gameMode == BeginningOfGame) {
7245     if (appData.noChessProgram) {
7246       gameMode = EditGame;
7247       SetGameInfo();
7248     } else {
7249       char buf[MSG_SIZ];
7250       gameMode = MachinePlaysBlack;
7251       StartClocks();
7252       SetGameInfo();
7253       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7254       DisplayTitle(buf);
7255       if (first.sendName) {
7256         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7257         SendToProgram(buf, &first);
7258       }
7259       StartClocks();
7260     }
7261     ModeHighlight();
7262   }
7263
7264   /* Relay move to ICS or chess engine */
7265   if (appData.icsActive) {
7266     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7267         gameMode == IcsExamining) {
7268       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7269         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7270         SendToICS("draw ");
7271         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7272       }
7273       // also send plain move, in case ICS does not understand atomic claims
7274       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7275       ics_user_moved = 1;
7276     }
7277   } else {
7278     if (first.sendTime && (gameMode == BeginningOfGame ||
7279                            gameMode == MachinePlaysWhite ||
7280                            gameMode == MachinePlaysBlack)) {
7281       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7282     }
7283     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7284          // [HGM] book: if program might be playing, let it use book
7285         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7286         first.maybeThinking = TRUE;
7287     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7288         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7289         SendBoard(&first, currentMove+1);
7290         if(second.analyzing) {
7291             if(!second.useSetboard) SendToProgram("undo\n", &second);
7292             SendBoard(&second, currentMove+1);
7293         }
7294     } else {
7295         SendMoveToProgram(forwardMostMove-1, &first);
7296         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7297     }
7298     if (currentMove == cmailOldMove + 1) {
7299       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7300     }
7301   }
7302
7303   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7304
7305   switch (gameMode) {
7306   case EditGame:
7307     if(appData.testLegality)
7308     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7309     case MT_NONE:
7310     case MT_CHECK:
7311       break;
7312     case MT_CHECKMATE:
7313     case MT_STAINMATE:
7314       if (WhiteOnMove(currentMove)) {
7315         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7316       } else {
7317         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7318       }
7319       break;
7320     case MT_STALEMATE:
7321       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7322       break;
7323     }
7324     break;
7325
7326   case MachinePlaysBlack:
7327   case MachinePlaysWhite:
7328     /* disable certain menu options while machine is thinking */
7329     SetMachineThinkingEnables();
7330     break;
7331
7332   default:
7333     break;
7334   }
7335
7336   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7337   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7338
7339   if(bookHit) { // [HGM] book: simulate book reply
7340         static char bookMove[MSG_SIZ]; // a bit generous?
7341
7342         programStats.nodes = programStats.depth = programStats.time =
7343         programStats.score = programStats.got_only_move = 0;
7344         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7345
7346         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7347         strcat(bookMove, bookHit);
7348         HandleMachineMove(bookMove, &first);
7349   }
7350   return 1;
7351 }
7352
7353 void
7354 MarkByFEN(char *fen)
7355 {
7356         int r, f;
7357         if(!appData.markers || !appData.highlightDragging) return;
7358         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7359         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7360         while(*fen) {
7361             int s = 0;
7362             marker[r][f] = 0;
7363             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7364             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7365             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7366             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7367             if(*fen == 'T') marker[r][f++] = 0; else
7368             if(*fen == 'Y') marker[r][f++] = 1; else
7369             if(*fen == 'G') marker[r][f++] = 3; else
7370             if(*fen == 'B') marker[r][f++] = 4; else
7371             if(*fen == 'C') marker[r][f++] = 5; else
7372             if(*fen == 'M') marker[r][f++] = 6; else
7373             if(*fen == 'W') marker[r][f++] = 7; else
7374             if(*fen == 'D') marker[r][f++] = 8; else
7375             if(*fen == 'R') marker[r][f++] = 2; else {
7376                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7377               f += s; fen -= s>0;
7378             }
7379             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7380             if(r < 0) break;
7381             fen++;
7382         }
7383         DrawPosition(TRUE, NULL);
7384 }
7385
7386 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7387
7388 void
7389 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7390 {
7391     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7392     Markers *m = (Markers *) closure;
7393     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7394         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7395                          || kind == WhiteCapturesEnPassant
7396                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7397     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7398 }
7399
7400 static int hoverSavedValid;
7401
7402 void
7403 MarkTargetSquares (int clear)
7404 {
7405   int x, y, sum=0;
7406   if(clear) { // no reason to ever suppress clearing
7407     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7408     hoverSavedValid = 0;
7409     if(!sum) return; // nothing was cleared,no redraw needed
7410   } else {
7411     int capt = 0;
7412     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7413        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7414     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7415     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7416       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7417       if(capt)
7418       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7419     }
7420   }
7421   DrawPosition(FALSE, NULL);
7422 }
7423
7424 int
7425 Explode (Board board, int fromX, int fromY, int toX, int toY)
7426 {
7427     if(gameInfo.variant == VariantAtomic &&
7428        (board[toY][toX] != EmptySquare ||                     // capture?
7429         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7430                          board[fromY][fromX] == BlackPawn   )
7431       )) {
7432         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7433         return TRUE;
7434     }
7435     return FALSE;
7436 }
7437
7438 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7439
7440 int
7441 CanPromote (ChessSquare piece, int y)
7442 {
7443         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7444         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7445         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7446         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7447            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7448            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7449          gameInfo.variant == VariantMakruk) return FALSE;
7450         return (piece == BlackPawn && y <= zone ||
7451                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7452                 piece == BlackLance && y <= zone ||
7453                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7454 }
7455
7456 void
7457 HoverEvent (int xPix, int yPix, int x, int y)
7458 {
7459         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7460         int r, f;
7461         if(!first.highlight) return;
7462         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7463         if(x == oldX && y == oldY) return; // only do something if we enter new square
7464         oldFromX = fromX; oldFromY = fromY;
7465         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7466           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7467             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7468           hoverSavedValid = 1;
7469         } else if(oldX != x || oldY != y) {
7470           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7471           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7472           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7473             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7474           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7475             char buf[MSG_SIZ];
7476             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7477             SendToProgram(buf, &first);
7478           }
7479           oldX = x; oldY = y;
7480 //        SetHighlights(fromX, fromY, x, y);
7481         }
7482 }
7483
7484 void ReportClick(char *action, int x, int y)
7485 {
7486         char buf[MSG_SIZ]; // Inform engine of what user does
7487         int r, f;
7488         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7489           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7490             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7491         if(!first.highlight || gameMode == EditPosition) return;
7492         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7493         SendToProgram(buf, &first);
7494 }
7495
7496 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7497
7498 void
7499 LeftClick (ClickType clickType, int xPix, int yPix)
7500 {
7501     int x, y;
7502     Boolean saveAnimate;
7503     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7504     char promoChoice = NULLCHAR;
7505     ChessSquare piece;
7506     static TimeMark lastClickTime, prevClickTime;
7507
7508     x = EventToSquare(xPix, BOARD_WIDTH);
7509     y = EventToSquare(yPix, BOARD_HEIGHT);
7510     if (!flipView && y >= 0) {
7511         y = BOARD_HEIGHT - 1 - y;
7512     }
7513     if (flipView && x >= 0) {
7514         x = BOARD_WIDTH - 1 - x;
7515     }
7516
7517     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7518         static int dummy;
7519         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7520         right = TRUE;
7521         return;
7522     }
7523
7524     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7525
7526     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7527
7528     if (clickType == Press) ErrorPopDown();
7529     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7530
7531     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7532         defaultPromoChoice = promoSweep;
7533         promoSweep = EmptySquare;   // terminate sweep
7534         promoDefaultAltered = TRUE;
7535         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7536     }
7537
7538     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7539         if(clickType == Release) return; // ignore upclick of click-click destination
7540         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7541         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7542         if(gameInfo.holdingsWidth &&
7543                 (WhiteOnMove(currentMove)
7544                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7545                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7546             // click in right holdings, for determining promotion piece
7547             ChessSquare p = boards[currentMove][y][x];
7548             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7549             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7550             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7551                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7552                 fromX = fromY = -1;
7553                 return;
7554             }
7555         }
7556         DrawPosition(FALSE, boards[currentMove]);
7557         return;
7558     }
7559
7560     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7561     if(clickType == Press
7562             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7563               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7564               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7565         return;
7566
7567     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7568         // could be static click on premove from-square: abort premove
7569         gotPremove = 0;
7570         ClearPremoveHighlights();
7571     }
7572
7573     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7574         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7575
7576     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7577         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7578                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7579         defaultPromoChoice = DefaultPromoChoice(side);
7580     }
7581
7582     autoQueen = appData.alwaysPromoteToQueen;
7583
7584     if (fromX == -1) {
7585       int originalY = y;
7586       gatingPiece = EmptySquare;
7587       if (clickType != Press) {
7588         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7589             DragPieceEnd(xPix, yPix); dragging = 0;
7590             DrawPosition(FALSE, NULL);
7591         }
7592         return;
7593       }
7594       doubleClick = FALSE;
7595       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7596         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7597       }
7598       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7599       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7600          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7601          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7602             /* First square */
7603             if (OKToStartUserMove(fromX, fromY)) {
7604                 second = 0;
7605                 ReportClick("lift", x, y);
7606                 MarkTargetSquares(0);
7607                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7608                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7609                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7610                     promoSweep = defaultPromoChoice;
7611                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7612                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7613                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7614                 }
7615                 if (appData.highlightDragging) {
7616                     SetHighlights(fromX, fromY, -1, -1);
7617                 } else {
7618                     ClearHighlights();
7619                 }
7620             } else fromX = fromY = -1;
7621             return;
7622         }
7623     }
7624
7625     /* fromX != -1 */
7626     if (clickType == Press && gameMode != EditPosition) {
7627         ChessSquare fromP;
7628         ChessSquare toP;
7629         int frc;
7630
7631         // ignore off-board to clicks
7632         if(y < 0 || x < 0) return;
7633
7634         /* Check if clicking again on the same color piece */
7635         fromP = boards[currentMove][fromY][fromX];
7636         toP = boards[currentMove][y][x];
7637         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7638         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7639             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7640            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7641              WhitePawn <= toP && toP <= WhiteKing &&
7642              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7643              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7644             (BlackPawn <= fromP && fromP <= BlackKing &&
7645              BlackPawn <= toP && toP <= BlackKing &&
7646              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7647              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7648             /* Clicked again on same color piece -- changed his mind */
7649             second = (x == fromX && y == fromY);
7650             killX = killY = -1;
7651             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7652                 second = FALSE; // first double-click rather than scond click
7653                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7654             }
7655             promoDefaultAltered = FALSE;
7656             MarkTargetSquares(1);
7657            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7658             if (appData.highlightDragging) {
7659                 SetHighlights(x, y, -1, -1);
7660             } else {
7661                 ClearHighlights();
7662             }
7663             if (OKToStartUserMove(x, y)) {
7664                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7665                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7666                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7667                  gatingPiece = boards[currentMove][fromY][fromX];
7668                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7669                 fromX = x;
7670                 fromY = y; dragging = 1;
7671                 if(!second) ReportClick("lift", x, y);
7672                 MarkTargetSquares(0);
7673                 DragPieceBegin(xPix, yPix, FALSE);
7674                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7675                     promoSweep = defaultPromoChoice;
7676                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7677                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7678                 }
7679             }
7680            }
7681            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7682            second = FALSE;
7683         }
7684         // ignore clicks on holdings
7685         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7686     }
7687
7688     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7689         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7690         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7691         return;
7692     }
7693
7694     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7695         DragPieceEnd(xPix, yPix); dragging = 0;
7696         if(clearFlag) {
7697             // a deferred attempt to click-click move an empty square on top of a piece
7698             boards[currentMove][y][x] = EmptySquare;
7699             ClearHighlights();
7700             DrawPosition(FALSE, boards[currentMove]);
7701             fromX = fromY = -1; clearFlag = 0;
7702             return;
7703         }
7704         if (appData.animateDragging) {
7705             /* Undo animation damage if any */
7706             DrawPosition(FALSE, NULL);
7707         }
7708         if (second) {
7709             /* Second up/down in same square; just abort move */
7710             second = 0;
7711             fromX = fromY = -1;
7712             gatingPiece = EmptySquare;
7713             MarkTargetSquares(1);
7714             ClearHighlights();
7715             gotPremove = 0;
7716             ClearPremoveHighlights();
7717         } else {
7718             /* First upclick in same square; start click-click mode */
7719             SetHighlights(x, y, -1, -1);
7720         }
7721         return;
7722     }
7723
7724     clearFlag = 0;
7725
7726     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7727        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7728         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7729         DisplayMessage(_("only marked squares are legal"),"");
7730         DrawPosition(TRUE, NULL);
7731         return; // ignore to-click
7732     }
7733
7734     /* we now have a different from- and (possibly off-board) to-square */
7735     /* Completed move */
7736     if(!sweepSelecting) {
7737         toX = x;
7738         toY = y;
7739     }
7740
7741     piece = boards[currentMove][fromY][fromX];
7742
7743     saveAnimate = appData.animate;
7744     if (clickType == Press) {
7745         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7746         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7747             // must be Edit Position mode with empty-square selected
7748             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7749             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7750             return;
7751         }
7752         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7753             return;
7754         }
7755         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7756             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7757         } else
7758         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7759         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7760           if(appData.sweepSelect) {
7761             promoSweep = defaultPromoChoice;
7762             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7763             selectFlag = 0; lastX = xPix; lastY = yPix;
7764             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7765             Sweep(0); // Pawn that is going to promote: preview promotion piece
7766             sweepSelecting = 1;
7767             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7768             MarkTargetSquares(1);
7769           }
7770           return; // promo popup appears on up-click
7771         }
7772         /* Finish clickclick move */
7773         if (appData.animate || appData.highlightLastMove) {
7774             SetHighlights(fromX, fromY, toX, toY);
7775         } else {
7776             ClearHighlights();
7777         }
7778     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7779         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7780         *promoRestrict = 0;
7781         if (appData.animate || appData.highlightLastMove) {
7782             SetHighlights(fromX, fromY, toX, toY);
7783         } else {
7784             ClearHighlights();
7785         }
7786     } else {
7787 #if 0
7788 // [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
7789         /* Finish drag move */
7790         if (appData.highlightLastMove) {
7791             SetHighlights(fromX, fromY, toX, toY);
7792         } else {
7793             ClearHighlights();
7794         }
7795 #endif
7796         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7797           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7798         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7799         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7800           dragging *= 2;            // flag button-less dragging if we are dragging
7801           MarkTargetSquares(1);
7802           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7803           else {
7804             kill2X = killX; kill2Y = killY;
7805             killX = x; killY = y;     //remeber this square as intermediate
7806             ReportClick("put", x, y); // and inform engine
7807             ReportClick("lift", x, y);
7808             MarkTargetSquares(0);
7809             return;
7810           }
7811         }
7812         DragPieceEnd(xPix, yPix); dragging = 0;
7813         /* Don't animate move and drag both */
7814         appData.animate = FALSE;
7815     }
7816
7817     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7818     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7819         ChessSquare piece = boards[currentMove][fromY][fromX];
7820         if(gameMode == EditPosition && piece != EmptySquare &&
7821            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7822             int n;
7823
7824             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7825                 n = PieceToNumber(piece - (int)BlackPawn);
7826                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7827                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7828                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7829             } else
7830             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7831                 n = PieceToNumber(piece);
7832                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7833                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7834                 boards[currentMove][n][BOARD_WIDTH-2]++;
7835             }
7836             boards[currentMove][fromY][fromX] = EmptySquare;
7837         }
7838         ClearHighlights();
7839         fromX = fromY = -1;
7840         MarkTargetSquares(1);
7841         DrawPosition(TRUE, boards[currentMove]);
7842         return;
7843     }
7844
7845     // off-board moves should not be highlighted
7846     if(x < 0 || y < 0) ClearHighlights();
7847     else ReportClick("put", x, y);
7848
7849     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7850
7851     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7852
7853     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7854         SetHighlights(fromX, fromY, toX, toY);
7855         MarkTargetSquares(1);
7856         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7857             // [HGM] super: promotion to captured piece selected from holdings
7858             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7859             promotionChoice = TRUE;
7860             // kludge follows to temporarily execute move on display, without promoting yet
7861             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7862             boards[currentMove][toY][toX] = p;
7863             DrawPosition(FALSE, boards[currentMove]);
7864             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7865             boards[currentMove][toY][toX] = q;
7866             DisplayMessage("Click in holdings to choose piece", "");
7867             return;
7868         }
7869         PromotionPopUp(promoChoice);
7870     } else {
7871         int oldMove = currentMove;
7872         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7873         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7874         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7875         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7876            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7877             DrawPosition(TRUE, boards[currentMove]);
7878         MarkTargetSquares(1);
7879         fromX = fromY = -1;
7880     }
7881     appData.animate = saveAnimate;
7882     if (appData.animate || appData.animateDragging) {
7883         /* Undo animation damage if needed */
7884         DrawPosition(FALSE, NULL);
7885     }
7886 }
7887
7888 int
7889 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7890 {   // front-end-free part taken out of PieceMenuPopup
7891     int whichMenu; int xSqr, ySqr;
7892
7893     if(seekGraphUp) { // [HGM] seekgraph
7894         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7895         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7896         return -2;
7897     }
7898
7899     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7900          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7901         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7902         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7903         if(action == Press)   {
7904             originalFlip = flipView;
7905             flipView = !flipView; // temporarily flip board to see game from partners perspective
7906             DrawPosition(TRUE, partnerBoard);
7907             DisplayMessage(partnerStatus, "");
7908             partnerUp = TRUE;
7909         } else if(action == Release) {
7910             flipView = originalFlip;
7911             DrawPosition(TRUE, boards[currentMove]);
7912             partnerUp = FALSE;
7913         }
7914         return -2;
7915     }
7916
7917     xSqr = EventToSquare(x, BOARD_WIDTH);
7918     ySqr = EventToSquare(y, BOARD_HEIGHT);
7919     if (action == Release) {
7920         if(pieceSweep != EmptySquare) {
7921             EditPositionMenuEvent(pieceSweep, toX, toY);
7922             pieceSweep = EmptySquare;
7923         } else UnLoadPV(); // [HGM] pv
7924     }
7925     if (action != Press) return -2; // return code to be ignored
7926     switch (gameMode) {
7927       case IcsExamining:
7928         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7929       case EditPosition:
7930         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7931         if (xSqr < 0 || ySqr < 0) return -1;
7932         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7933         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7934         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7935         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7936         NextPiece(0);
7937         return 2; // grab
7938       case IcsObserving:
7939         if(!appData.icsEngineAnalyze) return -1;
7940       case IcsPlayingWhite:
7941       case IcsPlayingBlack:
7942         if(!appData.zippyPlay) goto noZip;
7943       case AnalyzeMode:
7944       case AnalyzeFile:
7945       case MachinePlaysWhite:
7946       case MachinePlaysBlack:
7947       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7948         if (!appData.dropMenu) {
7949           LoadPV(x, y);
7950           return 2; // flag front-end to grab mouse events
7951         }
7952         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7953            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7954       case EditGame:
7955       noZip:
7956         if (xSqr < 0 || ySqr < 0) return -1;
7957         if (!appData.dropMenu || appData.testLegality &&
7958             gameInfo.variant != VariantBughouse &&
7959             gameInfo.variant != VariantCrazyhouse) return -1;
7960         whichMenu = 1; // drop menu
7961         break;
7962       default:
7963         return -1;
7964     }
7965
7966     if (((*fromX = xSqr) < 0) ||
7967         ((*fromY = ySqr) < 0)) {
7968         *fromX = *fromY = -1;
7969         return -1;
7970     }
7971     if (flipView)
7972       *fromX = BOARD_WIDTH - 1 - *fromX;
7973     else
7974       *fromY = BOARD_HEIGHT - 1 - *fromY;
7975
7976     return whichMenu;
7977 }
7978
7979 void
7980 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7981 {
7982 //    char * hint = lastHint;
7983     FrontEndProgramStats stats;
7984
7985     stats.which = cps == &first ? 0 : 1;
7986     stats.depth = cpstats->depth;
7987     stats.nodes = cpstats->nodes;
7988     stats.score = cpstats->score;
7989     stats.time = cpstats->time;
7990     stats.pv = cpstats->movelist;
7991     stats.hint = lastHint;
7992     stats.an_move_index = 0;
7993     stats.an_move_count = 0;
7994
7995     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7996         stats.hint = cpstats->move_name;
7997         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7998         stats.an_move_count = cpstats->nr_moves;
7999     }
8000
8001     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
8002
8003     SetProgramStats( &stats );
8004 }
8005
8006 void
8007 ClearEngineOutputPane (int which)
8008 {
8009     static FrontEndProgramStats dummyStats;
8010     dummyStats.which = which;
8011     dummyStats.pv = "#";
8012     SetProgramStats( &dummyStats );
8013 }
8014
8015 #define MAXPLAYERS 500
8016
8017 char *
8018 TourneyStandings (int display)
8019 {
8020     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8021     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8022     char result, *p, *names[MAXPLAYERS];
8023
8024     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8025         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8026     names[0] = p = strdup(appData.participants);
8027     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8028
8029     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8030
8031     while(result = appData.results[nr]) {
8032         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8033         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8034         wScore = bScore = 0;
8035         switch(result) {
8036           case '+': wScore = 2; break;
8037           case '-': bScore = 2; break;
8038           case '=': wScore = bScore = 1; break;
8039           case ' ':
8040           case '*': return strdup("busy"); // tourney not finished
8041         }
8042         score[w] += wScore;
8043         score[b] += bScore;
8044         games[w]++;
8045         games[b]++;
8046         nr++;
8047     }
8048     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8049     for(w=0; w<nPlayers; w++) {
8050         bScore = -1;
8051         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8052         ranking[w] = b; points[w] = bScore; score[b] = -2;
8053     }
8054     p = malloc(nPlayers*34+1);
8055     for(w=0; w<nPlayers && w<display; w++)
8056         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8057     free(names[0]);
8058     return p;
8059 }
8060
8061 void
8062 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8063 {       // count all piece types
8064         int p, f, r;
8065         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8066         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8067         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8068                 p = board[r][f];
8069                 pCnt[p]++;
8070                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8071                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8072                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8073                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8074                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8075                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8076         }
8077 }
8078
8079 int
8080 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8081 {
8082         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8083         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8084
8085         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8086         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8087         if(myPawns == 2 && nMine == 3) // KPP
8088             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8089         if(myPawns == 1 && nMine == 2) // KP
8090             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8091         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8092             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8093         if(myPawns) return FALSE;
8094         if(pCnt[WhiteRook+side])
8095             return pCnt[BlackRook-side] ||
8096                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8097                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8098                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8099         if(pCnt[WhiteCannon+side]) {
8100             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8101             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8102         }
8103         if(pCnt[WhiteKnight+side])
8104             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8105         return FALSE;
8106 }
8107
8108 int
8109 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8110 {
8111         VariantClass v = gameInfo.variant;
8112
8113         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8114         if(v == VariantShatranj) return TRUE; // always winnable through baring
8115         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8116         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8117
8118         if(v == VariantXiangqi) {
8119                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8120
8121                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8122                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8123                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8124                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8125                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8126                 if(stale) // we have at least one last-rank P plus perhaps C
8127                     return majors // KPKX
8128                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8129                 else // KCA*E*
8130                     return pCnt[WhiteFerz+side] // KCAK
8131                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8132                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8133                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8134
8135         } else if(v == VariantKnightmate) {
8136                 if(nMine == 1) return FALSE;
8137                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8138         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8139                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8140
8141                 if(nMine == 1) return FALSE; // bare King
8142                 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
8143                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8144                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8145                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8146                 if(pCnt[WhiteKnight+side])
8147                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8148                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8149                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8150                 if(nBishops)
8151                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8152                 if(pCnt[WhiteAlfil+side])
8153                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8154                 if(pCnt[WhiteWazir+side])
8155                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8156         }
8157
8158         return TRUE;
8159 }
8160
8161 int
8162 CompareWithRights (Board b1, Board b2)
8163 {
8164     int rights = 0;
8165     if(!CompareBoards(b1, b2)) return FALSE;
8166     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8167     /* compare castling rights */
8168     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8169            rights++; /* King lost rights, while rook still had them */
8170     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8171         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8172            rights++; /* but at least one rook lost them */
8173     }
8174     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8175            rights++;
8176     if( b1[CASTLING][5] != NoRights ) {
8177         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8178            rights++;
8179     }
8180     return rights == 0;
8181 }
8182
8183 int
8184 Adjudicate (ChessProgramState *cps)
8185 {       // [HGM] some adjudications useful with buggy engines
8186         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8187         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8188         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8189         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8190         int k, drop, count = 0; static int bare = 1;
8191         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8192         Boolean canAdjudicate = !appData.icsActive;
8193
8194         // most tests only when we understand the game, i.e. legality-checking on
8195             if( appData.testLegality )
8196             {   /* [HGM] Some more adjudications for obstinate engines */
8197                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8198                 static int moveCount = 6;
8199                 ChessMove result;
8200                 char *reason = NULL;
8201
8202                 /* Count what is on board. */
8203                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8204
8205                 /* Some material-based adjudications that have to be made before stalemate test */
8206                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8207                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8208                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8209                      if(canAdjudicate && appData.checkMates) {
8210                          if(engineOpponent)
8211                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8212                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8213                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8214                          return 1;
8215                      }
8216                 }
8217
8218                 /* Bare King in Shatranj (loses) or Losers (wins) */
8219                 if( nrW == 1 || nrB == 1) {
8220                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8221                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8222                      if(canAdjudicate && appData.checkMates) {
8223                          if(engineOpponent)
8224                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8225                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8226                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8227                          return 1;
8228                      }
8229                   } else
8230                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8231                   {    /* bare King */
8232                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8233                         if(canAdjudicate && appData.checkMates) {
8234                             /* but only adjudicate if adjudication enabled */
8235                             if(engineOpponent)
8236                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8237                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8238                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8239                             return 1;
8240                         }
8241                   }
8242                 } else bare = 1;
8243
8244
8245             // don't wait for engine to announce game end if we can judge ourselves
8246             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8247               case MT_CHECK:
8248                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8249                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8250                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8251                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8252                             checkCnt++;
8253                         if(checkCnt >= 2) {
8254                             reason = "Xboard adjudication: 3rd check";
8255                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8256                             break;
8257                         }
8258                     }
8259                 }
8260               case MT_NONE:
8261               default:
8262                 break;
8263               case MT_STEALMATE:
8264               case MT_STALEMATE:
8265               case MT_STAINMATE:
8266                 reason = "Xboard adjudication: Stalemate";
8267                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8268                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8269                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8270                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8271                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8272                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8273                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8274                                                                         EP_CHECKMATE : EP_WINS);
8275                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8276                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8277                 }
8278                 break;
8279               case MT_CHECKMATE:
8280                 reason = "Xboard adjudication: Checkmate";
8281                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8282                 if(gameInfo.variant == VariantShogi) {
8283                     if(forwardMostMove > backwardMostMove
8284                        && moveList[forwardMostMove-1][1] == '@'
8285                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8286                         reason = "XBoard adjudication: pawn-drop mate";
8287                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8288                     }
8289                 }
8290                 break;
8291             }
8292
8293                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8294                     case EP_STALEMATE:
8295                         result = GameIsDrawn; break;
8296                     case EP_CHECKMATE:
8297                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8298                     case EP_WINS:
8299                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8300                     default:
8301                         result = EndOfFile;
8302                 }
8303                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8304                     if(engineOpponent)
8305                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8306                     GameEnds( result, reason, GE_XBOARD );
8307                     return 1;
8308                 }
8309
8310                 /* Next absolutely insufficient mating material. */
8311                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8312                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8313                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8314
8315                      /* always flag draws, for judging claims */
8316                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8317
8318                      if(canAdjudicate && appData.materialDraws) {
8319                          /* but only adjudicate them if adjudication enabled */
8320                          if(engineOpponent) {
8321                            SendToProgram("force\n", engineOpponent); // suppress reply
8322                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8323                          }
8324                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8325                          return 1;
8326                      }
8327                 }
8328
8329                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8330                 if(gameInfo.variant == VariantXiangqi ?
8331                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8332                  : nrW + nrB == 4 &&
8333                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8334                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8335                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8336                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8337                    ) ) {
8338                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8339                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8340                           if(engineOpponent) {
8341                             SendToProgram("force\n", engineOpponent); // suppress reply
8342                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8343                           }
8344                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8345                           return 1;
8346                      }
8347                 } else moveCount = 6;
8348             }
8349
8350         // Repetition draws and 50-move rule can be applied independently of legality testing
8351
8352                 /* Check for rep-draws */
8353                 count = 0;
8354                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8355                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8356                 for(k = forwardMostMove-2;
8357                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8358                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8359                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8360                     k-=2)
8361                 {   int rights=0;
8362                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8363                         /* compare castling rights */
8364                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8365                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8366                                 rights++; /* King lost rights, while rook still had them */
8367                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8368                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8369                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8370                                    rights++; /* but at least one rook lost them */
8371                         }
8372                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8373                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8374                                 rights++;
8375                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8376                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8377                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8378                                    rights++;
8379                         }
8380                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8381                             && appData.drawRepeats > 1) {
8382                              /* adjudicate after user-specified nr of repeats */
8383                              int result = GameIsDrawn;
8384                              char *details = "XBoard adjudication: repetition draw";
8385                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8386                                 // [HGM] xiangqi: check for forbidden perpetuals
8387                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8388                                 for(m=forwardMostMove; m>k; m-=2) {
8389                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8390                                         ourPerpetual = 0; // the current mover did not always check
8391                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8392                                         hisPerpetual = 0; // the opponent did not always check
8393                                 }
8394                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8395                                                                         ourPerpetual, hisPerpetual);
8396                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8397                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8398                                     details = "Xboard adjudication: perpetual checking";
8399                                 } else
8400                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8401                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8402                                 } else
8403                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8404                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8405                                         result = BlackWins;
8406                                         details = "Xboard adjudication: repetition";
8407                                     }
8408                                 } else // it must be XQ
8409                                 // Now check for perpetual chases
8410                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8411                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8412                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8413                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8414                                         static char resdet[MSG_SIZ];
8415                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8416                                         details = resdet;
8417                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8418                                     } else
8419                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8420                                         break; // Abort repetition-checking loop.
8421                                 }
8422                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8423                              }
8424                              if(engineOpponent) {
8425                                SendToProgram("force\n", engineOpponent); // suppress reply
8426                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8427                              }
8428                              GameEnds( result, details, GE_XBOARD );
8429                              return 1;
8430                         }
8431                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8432                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8433                     }
8434                 }
8435
8436                 /* Now we test for 50-move draws. Determine ply count */
8437                 count = forwardMostMove;
8438                 /* look for last irreversble move */
8439                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8440                     count--;
8441                 /* if we hit starting position, add initial plies */
8442                 if( count == backwardMostMove )
8443                     count -= initialRulePlies;
8444                 count = forwardMostMove - count;
8445                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8446                         // adjust reversible move counter for checks in Xiangqi
8447                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8448                         if(i < backwardMostMove) i = backwardMostMove;
8449                         while(i <= forwardMostMove) {
8450                                 lastCheck = inCheck; // check evasion does not count
8451                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8452                                 if(inCheck || lastCheck) count--; // check does not count
8453                                 i++;
8454                         }
8455                 }
8456                 if( count >= 100)
8457                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8458                          /* this is used to judge if draw claims are legal */
8459                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8460                          if(engineOpponent) {
8461                            SendToProgram("force\n", engineOpponent); // suppress reply
8462                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8463                          }
8464                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8465                          return 1;
8466                 }
8467
8468                 /* if draw offer is pending, treat it as a draw claim
8469                  * when draw condition present, to allow engines a way to
8470                  * claim draws before making their move to avoid a race
8471                  * condition occurring after their move
8472                  */
8473                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8474                          char *p = NULL;
8475                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8476                              p = "Draw claim: 50-move rule";
8477                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8478                              p = "Draw claim: 3-fold repetition";
8479                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8480                              p = "Draw claim: insufficient mating material";
8481                          if( p != NULL && canAdjudicate) {
8482                              if(engineOpponent) {
8483                                SendToProgram("force\n", engineOpponent); // suppress reply
8484                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8485                              }
8486                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8487                              return 1;
8488                          }
8489                 }
8490
8491                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8492                     if(engineOpponent) {
8493                       SendToProgram("force\n", engineOpponent); // suppress reply
8494                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8495                     }
8496                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8497                     return 1;
8498                 }
8499         return 0;
8500 }
8501
8502 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8503 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8504 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8505
8506 static int
8507 BitbaseProbe ()
8508 {
8509     int pieces[10], squares[10], cnt=0, r, f, res;
8510     static int loaded;
8511     static PPROBE_EGBB probeBB;
8512     if(!appData.testLegality) return 10;
8513     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8514     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8515     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8516     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8517         ChessSquare piece = boards[forwardMostMove][r][f];
8518         int black = (piece >= BlackPawn);
8519         int type = piece - black*BlackPawn;
8520         if(piece == EmptySquare) continue;
8521         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8522         if(type == WhiteKing) type = WhiteQueen + 1;
8523         type = egbbCode[type];
8524         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8525         pieces[cnt] = type + black*6;
8526         if(++cnt > 5) return 11;
8527     }
8528     pieces[cnt] = squares[cnt] = 0;
8529     // probe EGBB
8530     if(loaded == 2) return 13; // loading failed before
8531     if(loaded == 0) {
8532         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8533         HMODULE lib;
8534         PLOAD_EGBB loadBB;
8535         loaded = 2; // prepare for failure
8536         if(!path) return 13; // no egbb installed
8537         strncpy(buf, path + 8, MSG_SIZ);
8538         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8539         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8540         lib = LoadLibrary(buf);
8541         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8542         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8543         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8544         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8545         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8546         loaded = 1; // success!
8547     }
8548     res = probeBB(forwardMostMove & 1, pieces, squares);
8549     return res > 0 ? 1 : res < 0 ? -1 : 0;
8550 }
8551
8552 char *
8553 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8554 {   // [HGM] book: this routine intercepts moves to simulate book replies
8555     char *bookHit = NULL;
8556
8557     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8558         char buf[MSG_SIZ];
8559         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8560         SendToProgram(buf, cps);
8561     }
8562     //first determine if the incoming move brings opponent into his book
8563     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8564         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8565     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8566     if(bookHit != NULL && !cps->bookSuspend) {
8567         // make sure opponent is not going to reply after receiving move to book position
8568         SendToProgram("force\n", cps);
8569         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8570     }
8571     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8572     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8573     // now arrange restart after book miss
8574     if(bookHit) {
8575         // after a book hit we never send 'go', and the code after the call to this routine
8576         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8577         char buf[MSG_SIZ], *move = bookHit;
8578         if(cps->useSAN) {
8579             int fromX, fromY, toX, toY;
8580             char promoChar;
8581             ChessMove moveType;
8582             move = buf + 30;
8583             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8584                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8585                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8586                                     PosFlags(forwardMostMove),
8587                                     fromY, fromX, toY, toX, promoChar, move);
8588             } else {
8589                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8590                 bookHit = NULL;
8591             }
8592         }
8593         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8594         SendToProgram(buf, cps);
8595         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8596     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8597         SendToProgram("go\n", cps);
8598         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8599     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8600         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8601             SendToProgram("go\n", cps);
8602         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8603     }
8604     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8605 }
8606
8607 int
8608 LoadError (char *errmess, ChessProgramState *cps)
8609 {   // unloads engine and switches back to -ncp mode if it was first
8610     if(cps->initDone) return FALSE;
8611     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8612     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8613     cps->pr = NoProc;
8614     if(cps == &first) {
8615         appData.noChessProgram = TRUE;
8616         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8617         gameMode = BeginningOfGame; ModeHighlight();
8618         SetNCPMode();
8619     }
8620     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8621     DisplayMessage("", ""); // erase waiting message
8622     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8623     return TRUE;
8624 }
8625
8626 char *savedMessage;
8627 ChessProgramState *savedState;
8628 void
8629 DeferredBookMove (void)
8630 {
8631         if(savedState->lastPing != savedState->lastPong)
8632                     ScheduleDelayedEvent(DeferredBookMove, 10);
8633         else
8634         HandleMachineMove(savedMessage, savedState);
8635 }
8636
8637 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8638 static ChessProgramState *stalledEngine;
8639 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8640
8641 void
8642 HandleMachineMove (char *message, ChessProgramState *cps)
8643 {
8644     static char firstLeg[20];
8645     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8646     char realname[MSG_SIZ];
8647     int fromX, fromY, toX, toY;
8648     ChessMove moveType;
8649     char promoChar, roar;
8650     char *p, *pv=buf1;
8651     int oldError;
8652     char *bookHit;
8653
8654     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8655         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8656         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8657             DisplayError(_("Invalid pairing from pairing engine"), 0);
8658             return;
8659         }
8660         pairingReceived = 1;
8661         NextMatchGame();
8662         return; // Skim the pairing messages here.
8663     }
8664
8665     oldError = cps->userError; cps->userError = 0;
8666
8667 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8668     /*
8669      * Kludge to ignore BEL characters
8670      */
8671     while (*message == '\007') message++;
8672
8673     /*
8674      * [HGM] engine debug message: ignore lines starting with '#' character
8675      */
8676     if(cps->debug && *message == '#') return;
8677
8678     /*
8679      * Look for book output
8680      */
8681     if (cps == &first && bookRequested) {
8682         if (message[0] == '\t' || message[0] == ' ') {
8683             /* Part of the book output is here; append it */
8684             strcat(bookOutput, message);
8685             strcat(bookOutput, "  \n");
8686             return;
8687         } else if (bookOutput[0] != NULLCHAR) {
8688             /* All of book output has arrived; display it */
8689             char *p = bookOutput;
8690             while (*p != NULLCHAR) {
8691                 if (*p == '\t') *p = ' ';
8692                 p++;
8693             }
8694             DisplayInformation(bookOutput);
8695             bookRequested = FALSE;
8696             /* Fall through to parse the current output */
8697         }
8698     }
8699
8700     /*
8701      * Look for machine move.
8702      */
8703     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8704         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8705     {
8706         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8707             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8708             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8709             stalledEngine = cps;
8710             if(appData.ponderNextMove) { // bring opponent out of ponder
8711                 if(gameMode == TwoMachinesPlay) {
8712                     if(cps->other->pause)
8713                         PauseEngine(cps->other);
8714                     else
8715                         SendToProgram("easy\n", cps->other);
8716                 }
8717             }
8718             StopClocks();
8719             return;
8720         }
8721
8722       if(cps->usePing) {
8723
8724         /* This method is only useful on engines that support ping */
8725         if(abortEngineThink) {
8726             if (appData.debugMode) {
8727                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8728             }
8729             SendToProgram("undo\n", cps);
8730             return;
8731         }
8732
8733         if (cps->lastPing != cps->lastPong) {
8734             /* Extra move from before last new; ignore */
8735             if (appData.debugMode) {
8736                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8737             }
8738           return;
8739         }
8740
8741       } else {
8742
8743         int machineWhite = FALSE;
8744
8745         switch (gameMode) {
8746           case BeginningOfGame:
8747             /* Extra move from before last reset; ignore */
8748             if (appData.debugMode) {
8749                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8750             }
8751             return;
8752
8753           case EndOfGame:
8754           case IcsIdle:
8755           default:
8756             /* Extra move after we tried to stop.  The mode test is
8757                not a reliable way of detecting this problem, but it's
8758                the best we can do on engines that don't support ping.
8759             */
8760             if (appData.debugMode) {
8761                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8762                         cps->which, gameMode);
8763             }
8764             SendToProgram("undo\n", cps);
8765             return;
8766
8767           case MachinePlaysWhite:
8768           case IcsPlayingWhite:
8769             machineWhite = TRUE;
8770             break;
8771
8772           case MachinePlaysBlack:
8773           case IcsPlayingBlack:
8774             machineWhite = FALSE;
8775             break;
8776
8777           case TwoMachinesPlay:
8778             machineWhite = (cps->twoMachinesColor[0] == 'w');
8779             break;
8780         }
8781         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8782             if (appData.debugMode) {
8783                 fprintf(debugFP,
8784                         "Ignoring move out of turn by %s, gameMode %d"
8785                         ", forwardMost %d\n",
8786                         cps->which, gameMode, forwardMostMove);
8787             }
8788             return;
8789         }
8790       }
8791
8792         if(cps->alphaRank) AlphaRank(machineMove, 4);
8793
8794         // [HGM] lion: (some very limited) support for Alien protocol
8795         killX = killY = kill2X = kill2Y = -1;
8796         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8797             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8798             return;
8799         }
8800         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8801             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8802             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8803         }
8804         if(firstLeg[0]) { // there was a previous leg;
8805             // only support case where same piece makes two step
8806             char buf[20], *p = machineMove+1, *q = buf+1, f;
8807             safeStrCpy(buf, machineMove, 20);
8808             while(isdigit(*q)) q++; // find start of to-square
8809             safeStrCpy(machineMove, firstLeg, 20);
8810             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8811             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8812             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8813             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8814             firstLeg[0] = NULLCHAR;
8815         }
8816
8817         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8818                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8819             /* Machine move could not be parsed; ignore it. */
8820           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8821                     machineMove, _(cps->which));
8822             DisplayMoveError(buf1);
8823             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8824                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8825             if (gameMode == TwoMachinesPlay) {
8826               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8827                        buf1, GE_XBOARD);
8828             }
8829             return;
8830         }
8831
8832         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8833         /* So we have to redo legality test with true e.p. status here,  */
8834         /* to make sure an illegal e.p. capture does not slip through,   */
8835         /* to cause a forfeit on a justified illegal-move complaint      */
8836         /* of the opponent.                                              */
8837         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8838            ChessMove moveType;
8839            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8840                              fromY, fromX, toY, toX, promoChar);
8841             if(moveType == IllegalMove) {
8842               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8843                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8844                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8845                            buf1, GE_XBOARD);
8846                 return;
8847            } else if(!appData.fischerCastling)
8848            /* [HGM] Kludge to handle engines that send FRC-style castling
8849               when they shouldn't (like TSCP-Gothic) */
8850            switch(moveType) {
8851              case WhiteASideCastleFR:
8852              case BlackASideCastleFR:
8853                toX+=2;
8854                currentMoveString[2]++;
8855                break;
8856              case WhiteHSideCastleFR:
8857              case BlackHSideCastleFR:
8858                toX--;
8859                currentMoveString[2]--;
8860                break;
8861              default: ; // nothing to do, but suppresses warning of pedantic compilers
8862            }
8863         }
8864         hintRequested = FALSE;
8865         lastHint[0] = NULLCHAR;
8866         bookRequested = FALSE;
8867         /* Program may be pondering now */
8868         cps->maybeThinking = TRUE;
8869         if (cps->sendTime == 2) cps->sendTime = 1;
8870         if (cps->offeredDraw) cps->offeredDraw--;
8871
8872         /* [AS] Save move info*/
8873         pvInfoList[ forwardMostMove ].score = programStats.score;
8874         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8875         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8876
8877         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8878
8879         /* Test suites abort the 'game' after one move */
8880         if(*appData.finger) {
8881            static FILE *f;
8882            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8883            if(!f) f = fopen(appData.finger, "w");
8884            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8885            else { DisplayFatalError("Bad output file", errno, 0); return; }
8886            free(fen);
8887            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8888         }
8889         if(appData.epd) {
8890            if(solvingTime >= 0) {
8891               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8892               totalTime += solvingTime; first.matchWins++;
8893            } else {
8894               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8895               second.matchWins++;
8896            }
8897            OutputKibitz(2, buf1);
8898            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8899         }
8900
8901         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8902         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8903             int count = 0;
8904
8905             while( count < adjudicateLossPlies ) {
8906                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8907
8908                 if( count & 1 ) {
8909                     score = -score; /* Flip score for winning side */
8910                 }
8911
8912                 if( score > appData.adjudicateLossThreshold ) {
8913                     break;
8914                 }
8915
8916                 count++;
8917             }
8918
8919             if( count >= adjudicateLossPlies ) {
8920                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8921
8922                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8923                     "Xboard adjudication",
8924                     GE_XBOARD );
8925
8926                 return;
8927             }
8928         }
8929
8930         if(Adjudicate(cps)) {
8931             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8932             return; // [HGM] adjudicate: for all automatic game ends
8933         }
8934
8935 #if ZIPPY
8936         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8937             first.initDone) {
8938           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8939                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8940                 SendToICS("draw ");
8941                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8942           }
8943           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8944           ics_user_moved = 1;
8945           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8946                 char buf[3*MSG_SIZ];
8947
8948                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8949                         programStats.score / 100.,
8950                         programStats.depth,
8951                         programStats.time / 100.,
8952                         (unsigned int)programStats.nodes,
8953                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8954                         programStats.movelist);
8955                 SendToICS(buf);
8956           }
8957         }
8958 #endif
8959
8960         /* [AS] Clear stats for next move */
8961         ClearProgramStats();
8962         thinkOutput[0] = NULLCHAR;
8963         hiddenThinkOutputState = 0;
8964
8965         bookHit = NULL;
8966         if (gameMode == TwoMachinesPlay) {
8967             /* [HGM] relaying draw offers moved to after reception of move */
8968             /* and interpreting offer as claim if it brings draw condition */
8969             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8970                 SendToProgram("draw\n", cps->other);
8971             }
8972             if (cps->other->sendTime) {
8973                 SendTimeRemaining(cps->other,
8974                                   cps->other->twoMachinesColor[0] == 'w');
8975             }
8976             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8977             if (firstMove && !bookHit) {
8978                 firstMove = FALSE;
8979                 if (cps->other->useColors) {
8980                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8981                 }
8982                 SendToProgram("go\n", cps->other);
8983             }
8984             cps->other->maybeThinking = TRUE;
8985         }
8986
8987         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8988
8989         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8990
8991         if (!pausing && appData.ringBellAfterMoves) {
8992             if(!roar) RingBell();
8993         }
8994
8995         /*
8996          * Reenable menu items that were disabled while
8997          * machine was thinking
8998          */
8999         if (gameMode != TwoMachinesPlay)
9000             SetUserThinkingEnables();
9001
9002         // [HGM] book: after book hit opponent has received move and is now in force mode
9003         // force the book reply into it, and then fake that it outputted this move by jumping
9004         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9005         if(bookHit) {
9006                 static char bookMove[MSG_SIZ]; // a bit generous?
9007
9008                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9009                 strcat(bookMove, bookHit);
9010                 message = bookMove;
9011                 cps = cps->other;
9012                 programStats.nodes = programStats.depth = programStats.time =
9013                 programStats.score = programStats.got_only_move = 0;
9014                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9015
9016                 if(cps->lastPing != cps->lastPong) {
9017                     savedMessage = message; // args for deferred call
9018                     savedState = cps;
9019                     ScheduleDelayedEvent(DeferredBookMove, 10);
9020                     return;
9021                 }
9022                 goto FakeBookMove;
9023         }
9024
9025         return;
9026     }
9027
9028     /* Set special modes for chess engines.  Later something general
9029      *  could be added here; for now there is just one kludge feature,
9030      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9031      *  when "xboard" is given as an interactive command.
9032      */
9033     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9034         cps->useSigint = FALSE;
9035         cps->useSigterm = FALSE;
9036     }
9037     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9038       ParseFeatures(message+8, cps);
9039       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9040     }
9041
9042     if (!strncmp(message, "setup ", 6) && 
9043         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9044           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9045                                         ) { // [HGM] allow first engine to define opening position
9046       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9047       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9048       *buf = NULLCHAR;
9049       if(sscanf(message, "setup (%s", buf) == 1) {
9050         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9051         ASSIGN(appData.pieceToCharTable, buf);
9052       }
9053       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9054       if(dummy >= 3) {
9055         while(message[s] && message[s++] != ' ');
9056         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9057            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9058             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9059             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9060           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9061           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9062           startedFromSetupPosition = FALSE;
9063         }
9064       }
9065       if(startedFromSetupPosition) return;
9066       ParseFEN(boards[0], &dummy, message+s, FALSE);
9067       DrawPosition(TRUE, boards[0]);
9068       CopyBoard(initialPosition, boards[0]);
9069       startedFromSetupPosition = TRUE;
9070       return;
9071     }
9072     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9073       ChessSquare piece = WhitePawn;
9074       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9075       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9076       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9077       piece += CharToPiece(ID & 255) - WhitePawn;
9078       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9079       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9080       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9081       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9082       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9083       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9084                                                && gameInfo.variant != VariantGreat
9085                                                && gameInfo.variant != VariantFairy    ) return;
9086       if(piece < EmptySquare) {
9087         pieceDefs = TRUE;
9088         ASSIGN(pieceDesc[piece], buf1);
9089         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9090       }
9091       return;
9092     }
9093     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9094       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9095       Sweep(0);
9096       return;
9097     }
9098     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9099      * want this, I was asked to put it in, and obliged.
9100      */
9101     if (!strncmp(message, "setboard ", 9)) {
9102         Board initial_position;
9103
9104         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9105
9106         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9107             DisplayError(_("Bad FEN received from engine"), 0);
9108             return ;
9109         } else {
9110            Reset(TRUE, FALSE);
9111            CopyBoard(boards[0], initial_position);
9112            initialRulePlies = FENrulePlies;
9113            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9114            else gameMode = MachinePlaysBlack;
9115            DrawPosition(FALSE, boards[currentMove]);
9116         }
9117         return;
9118     }
9119
9120     /*
9121      * Look for communication commands
9122      */
9123     if (!strncmp(message, "telluser ", 9)) {
9124         if(message[9] == '\\' && message[10] == '\\')
9125             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9126         PlayTellSound();
9127         DisplayNote(message + 9);
9128         return;
9129     }
9130     if (!strncmp(message, "tellusererror ", 14)) {
9131         cps->userError = 1;
9132         if(message[14] == '\\' && message[15] == '\\')
9133             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9134         PlayTellSound();
9135         DisplayError(message + 14, 0);
9136         return;
9137     }
9138     if (!strncmp(message, "tellopponent ", 13)) {
9139       if (appData.icsActive) {
9140         if (loggedOn) {
9141           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9142           SendToICS(buf1);
9143         }
9144       } else {
9145         DisplayNote(message + 13);
9146       }
9147       return;
9148     }
9149     if (!strncmp(message, "tellothers ", 11)) {
9150       if (appData.icsActive) {
9151         if (loggedOn) {
9152           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9153           SendToICS(buf1);
9154         }
9155       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9156       return;
9157     }
9158     if (!strncmp(message, "tellall ", 8)) {
9159       if (appData.icsActive) {
9160         if (loggedOn) {
9161           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9162           SendToICS(buf1);
9163         }
9164       } else {
9165         DisplayNote(message + 8);
9166       }
9167       return;
9168     }
9169     if (strncmp(message, "warning", 7) == 0) {
9170         /* Undocumented feature, use tellusererror in new code */
9171         DisplayError(message, 0);
9172         return;
9173     }
9174     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9175         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9176         strcat(realname, " query");
9177         AskQuestion(realname, buf2, buf1, cps->pr);
9178         return;
9179     }
9180     /* Commands from the engine directly to ICS.  We don't allow these to be
9181      *  sent until we are logged on. Crafty kibitzes have been known to
9182      *  interfere with the login process.
9183      */
9184     if (loggedOn) {
9185         if (!strncmp(message, "tellics ", 8)) {
9186             SendToICS(message + 8);
9187             SendToICS("\n");
9188             return;
9189         }
9190         if (!strncmp(message, "tellicsnoalias ", 15)) {
9191             SendToICS(ics_prefix);
9192             SendToICS(message + 15);
9193             SendToICS("\n");
9194             return;
9195         }
9196         /* The following are for backward compatibility only */
9197         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9198             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9199             SendToICS(ics_prefix);
9200             SendToICS(message);
9201             SendToICS("\n");
9202             return;
9203         }
9204     }
9205     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9206         if(initPing == cps->lastPong) {
9207             if(gameInfo.variant == VariantUnknown) {
9208                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9209                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9210                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9211             }
9212             initPing = -1;
9213         }
9214         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9215             abortEngineThink = FALSE;
9216             DisplayMessage("", "");
9217             ThawUI();
9218         }
9219         return;
9220     }
9221     if(!strncmp(message, "highlight ", 10)) {
9222         if(appData.testLegality && !*engineVariant && appData.markers) return;
9223         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9224         return;
9225     }
9226     if(!strncmp(message, "click ", 6)) {
9227         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9228         if(appData.testLegality || !appData.oneClick) return;
9229         sscanf(message+6, "%c%d%c", &f, &y, &c);
9230         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9231         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9232         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9233         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9234         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9235         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9236             LeftClick(Release, lastLeftX, lastLeftY);
9237         controlKey  = (c == ',');
9238         LeftClick(Press, x, y);
9239         LeftClick(Release, x, y);
9240         first.highlight = f;
9241         return;
9242     }
9243     /*
9244      * If the move is illegal, cancel it and redraw the board.
9245      * Also deal with other error cases.  Matching is rather loose
9246      * here to accommodate engines written before the spec.
9247      */
9248     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9249         strncmp(message, "Error", 5) == 0) {
9250         if (StrStr(message, "name") ||
9251             StrStr(message, "rating") || StrStr(message, "?") ||
9252             StrStr(message, "result") || StrStr(message, "board") ||
9253             StrStr(message, "bk") || StrStr(message, "computer") ||
9254             StrStr(message, "variant") || StrStr(message, "hint") ||
9255             StrStr(message, "random") || StrStr(message, "depth") ||
9256             StrStr(message, "accepted")) {
9257             return;
9258         }
9259         if (StrStr(message, "protover")) {
9260           /* Program is responding to input, so it's apparently done
9261              initializing, and this error message indicates it is
9262              protocol version 1.  So we don't need to wait any longer
9263              for it to initialize and send feature commands. */
9264           FeatureDone(cps, 1);
9265           cps->protocolVersion = 1;
9266           return;
9267         }
9268         cps->maybeThinking = FALSE;
9269
9270         if (StrStr(message, "draw")) {
9271             /* Program doesn't have "draw" command */
9272             cps->sendDrawOffers = 0;
9273             return;
9274         }
9275         if (cps->sendTime != 1 &&
9276             (StrStr(message, "time") || StrStr(message, "otim"))) {
9277           /* Program apparently doesn't have "time" or "otim" command */
9278           cps->sendTime = 0;
9279           return;
9280         }
9281         if (StrStr(message, "analyze")) {
9282             cps->analysisSupport = FALSE;
9283             cps->analyzing = FALSE;
9284 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9285             EditGameEvent(); // [HGM] try to preserve loaded game
9286             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9287             DisplayError(buf2, 0);
9288             return;
9289         }
9290         if (StrStr(message, "(no matching move)st")) {
9291           /* Special kludge for GNU Chess 4 only */
9292           cps->stKludge = TRUE;
9293           SendTimeControl(cps, movesPerSession, timeControl,
9294                           timeIncrement, appData.searchDepth,
9295                           searchTime);
9296           return;
9297         }
9298         if (StrStr(message, "(no matching move)sd")) {
9299           /* Special kludge for GNU Chess 4 only */
9300           cps->sdKludge = TRUE;
9301           SendTimeControl(cps, movesPerSession, timeControl,
9302                           timeIncrement, appData.searchDepth,
9303                           searchTime);
9304           return;
9305         }
9306         if (!StrStr(message, "llegal")) {
9307             return;
9308         }
9309         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9310             gameMode == IcsIdle) return;
9311         if (forwardMostMove <= backwardMostMove) return;
9312         if (pausing) PauseEvent();
9313       if(appData.forceIllegal) {
9314             // [HGM] illegal: machine refused move; force position after move into it
9315           SendToProgram("force\n", cps);
9316           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9317                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9318                 // when black is to move, while there might be nothing on a2 or black
9319                 // might already have the move. So send the board as if white has the move.
9320                 // But first we must change the stm of the engine, as it refused the last move
9321                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9322                 if(WhiteOnMove(forwardMostMove)) {
9323                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9324                     SendBoard(cps, forwardMostMove); // kludgeless board
9325                 } else {
9326                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9327                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9328                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9329                 }
9330           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9331             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9332                  gameMode == TwoMachinesPlay)
9333               SendToProgram("go\n", cps);
9334             return;
9335       } else
9336         if (gameMode == PlayFromGameFile) {
9337             /* Stop reading this game file */
9338             gameMode = EditGame;
9339             ModeHighlight();
9340         }
9341         /* [HGM] illegal-move claim should forfeit game when Xboard */
9342         /* only passes fully legal moves                            */
9343         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9344             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9345                                 "False illegal-move claim", GE_XBOARD );
9346             return; // do not take back move we tested as valid
9347         }
9348         currentMove = forwardMostMove-1;
9349         DisplayMove(currentMove-1); /* before DisplayMoveError */
9350         SwitchClocks(forwardMostMove-1); // [HGM] race
9351         DisplayBothClocks();
9352         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9353                 parseList[currentMove], _(cps->which));
9354         DisplayMoveError(buf1);
9355         DrawPosition(FALSE, boards[currentMove]);
9356
9357         SetUserThinkingEnables();
9358         return;
9359     }
9360     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9361         /* Program has a broken "time" command that
9362            outputs a string not ending in newline.
9363            Don't use it. */
9364         cps->sendTime = 0;
9365     }
9366     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9367         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9368             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9369     }
9370
9371     /*
9372      * If chess program startup fails, exit with an error message.
9373      * Attempts to recover here are futile. [HGM] Well, we try anyway
9374      */
9375     if ((StrStr(message, "unknown host") != NULL)
9376         || (StrStr(message, "No remote directory") != NULL)
9377         || (StrStr(message, "not found") != NULL)
9378         || (StrStr(message, "No such file") != NULL)
9379         || (StrStr(message, "can't alloc") != NULL)
9380         || (StrStr(message, "Permission denied") != NULL)) {
9381
9382         cps->maybeThinking = FALSE;
9383         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9384                 _(cps->which), cps->program, cps->host, message);
9385         RemoveInputSource(cps->isr);
9386         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9387             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9388             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9389         }
9390         return;
9391     }
9392
9393     /*
9394      * Look for hint output
9395      */
9396     if (sscanf(message, "Hint: %s", buf1) == 1) {
9397         if (cps == &first && hintRequested) {
9398             hintRequested = FALSE;
9399             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9400                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9401                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9402                                     PosFlags(forwardMostMove),
9403                                     fromY, fromX, toY, toX, promoChar, buf1);
9404                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9405                 DisplayInformation(buf2);
9406             } else {
9407                 /* Hint move could not be parsed!? */
9408               snprintf(buf2, sizeof(buf2),
9409                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9410                         buf1, _(cps->which));
9411                 DisplayError(buf2, 0);
9412             }
9413         } else {
9414           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9415         }
9416         return;
9417     }
9418
9419     /*
9420      * Ignore other messages if game is not in progress
9421      */
9422     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9423         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9424
9425     /*
9426      * look for win, lose, draw, or draw offer
9427      */
9428     if (strncmp(message, "1-0", 3) == 0) {
9429         char *p, *q, *r = "";
9430         p = strchr(message, '{');
9431         if (p) {
9432             q = strchr(p, '}');
9433             if (q) {
9434                 *q = NULLCHAR;
9435                 r = p + 1;
9436             }
9437         }
9438         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9439         return;
9440     } else if (strncmp(message, "0-1", 3) == 0) {
9441         char *p, *q, *r = "";
9442         p = strchr(message, '{');
9443         if (p) {
9444             q = strchr(p, '}');
9445             if (q) {
9446                 *q = NULLCHAR;
9447                 r = p + 1;
9448             }
9449         }
9450         /* Kludge for Arasan 4.1 bug */
9451         if (strcmp(r, "Black resigns") == 0) {
9452             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9453             return;
9454         }
9455         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9456         return;
9457     } else if (strncmp(message, "1/2", 3) == 0) {
9458         char *p, *q, *r = "";
9459         p = strchr(message, '{');
9460         if (p) {
9461             q = strchr(p, '}');
9462             if (q) {
9463                 *q = NULLCHAR;
9464                 r = p + 1;
9465             }
9466         }
9467
9468         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9469         return;
9470
9471     } else if (strncmp(message, "White resign", 12) == 0) {
9472         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9473         return;
9474     } else if (strncmp(message, "Black resign", 12) == 0) {
9475         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9476         return;
9477     } else if (strncmp(message, "White matches", 13) == 0 ||
9478                strncmp(message, "Black matches", 13) == 0   ) {
9479         /* [HGM] ignore GNUShogi noises */
9480         return;
9481     } else if (strncmp(message, "White", 5) == 0 &&
9482                message[5] != '(' &&
9483                StrStr(message, "Black") == NULL) {
9484         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9485         return;
9486     } else if (strncmp(message, "Black", 5) == 0 &&
9487                message[5] != '(') {
9488         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9489         return;
9490     } else if (strcmp(message, "resign") == 0 ||
9491                strcmp(message, "computer resigns") == 0) {
9492         switch (gameMode) {
9493           case MachinePlaysBlack:
9494           case IcsPlayingBlack:
9495             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9496             break;
9497           case MachinePlaysWhite:
9498           case IcsPlayingWhite:
9499             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9500             break;
9501           case TwoMachinesPlay:
9502             if (cps->twoMachinesColor[0] == 'w')
9503               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9504             else
9505               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9506             break;
9507           default:
9508             /* can't happen */
9509             break;
9510         }
9511         return;
9512     } else if (strncmp(message, "opponent mates", 14) == 0) {
9513         switch (gameMode) {
9514           case MachinePlaysBlack:
9515           case IcsPlayingBlack:
9516             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9517             break;
9518           case MachinePlaysWhite:
9519           case IcsPlayingWhite:
9520             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9521             break;
9522           case TwoMachinesPlay:
9523             if (cps->twoMachinesColor[0] == 'w')
9524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9525             else
9526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9527             break;
9528           default:
9529             /* can't happen */
9530             break;
9531         }
9532         return;
9533     } else if (strncmp(message, "computer mates", 14) == 0) {
9534         switch (gameMode) {
9535           case MachinePlaysBlack:
9536           case IcsPlayingBlack:
9537             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9538             break;
9539           case MachinePlaysWhite:
9540           case IcsPlayingWhite:
9541             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9542             break;
9543           case TwoMachinesPlay:
9544             if (cps->twoMachinesColor[0] == 'w')
9545               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9546             else
9547               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9548             break;
9549           default:
9550             /* can't happen */
9551             break;
9552         }
9553         return;
9554     } else if (strncmp(message, "checkmate", 9) == 0) {
9555         if (WhiteOnMove(forwardMostMove)) {
9556             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9557         } else {
9558             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9559         }
9560         return;
9561     } else if (strstr(message, "Draw") != NULL ||
9562                strstr(message, "game is a draw") != NULL) {
9563         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9564         return;
9565     } else if (strstr(message, "offer") != NULL &&
9566                strstr(message, "draw") != NULL) {
9567 #if ZIPPY
9568         if (appData.zippyPlay && first.initDone) {
9569             /* Relay offer to ICS */
9570             SendToICS(ics_prefix);
9571             SendToICS("draw\n");
9572         }
9573 #endif
9574         cps->offeredDraw = 2; /* valid until this engine moves twice */
9575         if (gameMode == TwoMachinesPlay) {
9576             if (cps->other->offeredDraw) {
9577                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9578             /* [HGM] in two-machine mode we delay relaying draw offer      */
9579             /* until after we also have move, to see if it is really claim */
9580             }
9581         } else if (gameMode == MachinePlaysWhite ||
9582                    gameMode == MachinePlaysBlack) {
9583           if (userOfferedDraw) {
9584             DisplayInformation(_("Machine accepts your draw offer"));
9585             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9586           } else {
9587             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9588           }
9589         }
9590     }
9591
9592
9593     /*
9594      * Look for thinking output
9595      */
9596     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9597           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9598                                 ) {
9599         int plylev, mvleft, mvtot, curscore, time;
9600         char mvname[MOVE_LEN];
9601         u64 nodes; // [DM]
9602         char plyext;
9603         int ignore = FALSE;
9604         int prefixHint = FALSE;
9605         mvname[0] = NULLCHAR;
9606
9607         switch (gameMode) {
9608           case MachinePlaysBlack:
9609           case IcsPlayingBlack:
9610             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9611             break;
9612           case MachinePlaysWhite:
9613           case IcsPlayingWhite:
9614             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9615             break;
9616           case AnalyzeMode:
9617           case AnalyzeFile:
9618             break;
9619           case IcsObserving: /* [DM] icsEngineAnalyze */
9620             if (!appData.icsEngineAnalyze) ignore = TRUE;
9621             break;
9622           case TwoMachinesPlay:
9623             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9624                 ignore = TRUE;
9625             }
9626             break;
9627           default:
9628             ignore = TRUE;
9629             break;
9630         }
9631
9632         if (!ignore) {
9633             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9634             buf1[0] = NULLCHAR;
9635             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9636                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9637                 char score_buf[MSG_SIZ];
9638
9639                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9640                     nodes += u64Const(0x100000000);
9641
9642                 if (plyext != ' ' && plyext != '\t') {
9643                     time *= 100;
9644                 }
9645
9646                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9647                 if( cps->scoreIsAbsolute &&
9648                     ( gameMode == MachinePlaysBlack ||
9649                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9650                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9651                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9652                      !WhiteOnMove(currentMove)
9653                     ) )
9654                 {
9655                     curscore = -curscore;
9656                 }
9657
9658                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9659
9660                 if(*bestMove) { // rememer time best EPD move was first found
9661                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9662                     ChessMove mt;
9663                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9664                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9665                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9666                 }
9667
9668                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9669                         char buf[MSG_SIZ];
9670                         FILE *f;
9671                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9672                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9673                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9674                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9675                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9676                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9677                                 fclose(f);
9678                         }
9679                         else
9680                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9681                           DisplayError(_("failed writing PV"), 0);
9682                 }
9683
9684                 tempStats.depth = plylev;
9685                 tempStats.nodes = nodes;
9686                 tempStats.time = time;
9687                 tempStats.score = curscore;
9688                 tempStats.got_only_move = 0;
9689
9690                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9691                         int ticklen;
9692
9693                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9694                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9695                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9696                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9697                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9698                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9699                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9700                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9701                 }
9702
9703                 /* Buffer overflow protection */
9704                 if (pv[0] != NULLCHAR) {
9705                     if (strlen(pv) >= sizeof(tempStats.movelist)
9706                         && appData.debugMode) {
9707                         fprintf(debugFP,
9708                                 "PV is too long; using the first %u bytes.\n",
9709                                 (unsigned) sizeof(tempStats.movelist) - 1);
9710                     }
9711
9712                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9713                 } else {
9714                     sprintf(tempStats.movelist, " no PV\n");
9715                 }
9716
9717                 if (tempStats.seen_stat) {
9718                     tempStats.ok_to_send = 1;
9719                 }
9720
9721                 if (strchr(tempStats.movelist, '(') != NULL) {
9722                     tempStats.line_is_book = 1;
9723                     tempStats.nr_moves = 0;
9724                     tempStats.moves_left = 0;
9725                 } else {
9726                     tempStats.line_is_book = 0;
9727                 }
9728
9729                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9730                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9731
9732                 SendProgramStatsToFrontend( cps, &tempStats );
9733
9734                 /*
9735                     [AS] Protect the thinkOutput buffer from overflow... this
9736                     is only useful if buf1 hasn't overflowed first!
9737                 */
9738                 if(curscore >= MATE_SCORE) 
9739                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9740                 else if(curscore <= -MATE_SCORE) 
9741                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9742                 else
9743                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9744                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9745                          plylev,
9746                          (gameMode == TwoMachinesPlay ?
9747                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9748                          score_buf,
9749                          prefixHint ? lastHint : "",
9750                          prefixHint ? " " : "" );
9751
9752                 if( buf1[0] != NULLCHAR ) {
9753                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9754
9755                     if( strlen(pv) > max_len ) {
9756                         if( appData.debugMode) {
9757                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9758                         }
9759                         pv[max_len+1] = '\0';
9760                     }
9761
9762                     strcat( thinkOutput, pv);
9763                 }
9764
9765                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9766                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9767                     DisplayMove(currentMove - 1);
9768                 }
9769                 return;
9770
9771             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9772                 /* crafty (9.25+) says "(only move) <move>"
9773                  * if there is only 1 legal move
9774                  */
9775                 sscanf(p, "(only move) %s", buf1);
9776                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9777                 sprintf(programStats.movelist, "%s (only move)", buf1);
9778                 programStats.depth = 1;
9779                 programStats.nr_moves = 1;
9780                 programStats.moves_left = 1;
9781                 programStats.nodes = 1;
9782                 programStats.time = 1;
9783                 programStats.got_only_move = 1;
9784
9785                 /* Not really, but we also use this member to
9786                    mean "line isn't going to change" (Crafty
9787                    isn't searching, so stats won't change) */
9788                 programStats.line_is_book = 1;
9789
9790                 SendProgramStatsToFrontend( cps, &programStats );
9791
9792                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9793                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9794                     DisplayMove(currentMove - 1);
9795                 }
9796                 return;
9797             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9798                               &time, &nodes, &plylev, &mvleft,
9799                               &mvtot, mvname) >= 5) {
9800                 /* The stat01: line is from Crafty (9.29+) in response
9801                    to the "." command */
9802                 programStats.seen_stat = 1;
9803                 cps->maybeThinking = TRUE;
9804
9805                 if (programStats.got_only_move || !appData.periodicUpdates)
9806                   return;
9807
9808                 programStats.depth = plylev;
9809                 programStats.time = time;
9810                 programStats.nodes = nodes;
9811                 programStats.moves_left = mvleft;
9812                 programStats.nr_moves = mvtot;
9813                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9814                 programStats.ok_to_send = 1;
9815                 programStats.movelist[0] = '\0';
9816
9817                 SendProgramStatsToFrontend( cps, &programStats );
9818
9819                 return;
9820
9821             } else if (strncmp(message,"++",2) == 0) {
9822                 /* Crafty 9.29+ outputs this */
9823                 programStats.got_fail = 2;
9824                 return;
9825
9826             } else if (strncmp(message,"--",2) == 0) {
9827                 /* Crafty 9.29+ outputs this */
9828                 programStats.got_fail = 1;
9829                 return;
9830
9831             } else if (thinkOutput[0] != NULLCHAR &&
9832                        strncmp(message, "    ", 4) == 0) {
9833                 unsigned message_len;
9834
9835                 p = message;
9836                 while (*p && *p == ' ') p++;
9837
9838                 message_len = strlen( p );
9839
9840                 /* [AS] Avoid buffer overflow */
9841                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9842                     strcat(thinkOutput, " ");
9843                     strcat(thinkOutput, p);
9844                 }
9845
9846                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9847                     strcat(programStats.movelist, " ");
9848                     strcat(programStats.movelist, p);
9849                 }
9850
9851                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9852                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9853                     DisplayMove(currentMove - 1);
9854                 }
9855                 return;
9856             }
9857         }
9858         else {
9859             buf1[0] = NULLCHAR;
9860
9861             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9862                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9863             {
9864                 ChessProgramStats cpstats;
9865
9866                 if (plyext != ' ' && plyext != '\t') {
9867                     time *= 100;
9868                 }
9869
9870                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9871                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9872                     curscore = -curscore;
9873                 }
9874
9875                 cpstats.depth = plylev;
9876                 cpstats.nodes = nodes;
9877                 cpstats.time = time;
9878                 cpstats.score = curscore;
9879                 cpstats.got_only_move = 0;
9880                 cpstats.movelist[0] = '\0';
9881
9882                 if (buf1[0] != NULLCHAR) {
9883                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9884                 }
9885
9886                 cpstats.ok_to_send = 0;
9887                 cpstats.line_is_book = 0;
9888                 cpstats.nr_moves = 0;
9889                 cpstats.moves_left = 0;
9890
9891                 SendProgramStatsToFrontend( cps, &cpstats );
9892             }
9893         }
9894     }
9895 }
9896
9897
9898 /* Parse a game score from the character string "game", and
9899    record it as the history of the current game.  The game
9900    score is NOT assumed to start from the standard position.
9901    The display is not updated in any way.
9902    */
9903 void
9904 ParseGameHistory (char *game)
9905 {
9906     ChessMove moveType;
9907     int fromX, fromY, toX, toY, boardIndex;
9908     char promoChar;
9909     char *p, *q;
9910     char buf[MSG_SIZ];
9911
9912     if (appData.debugMode)
9913       fprintf(debugFP, "Parsing game history: %s\n", game);
9914
9915     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9916     gameInfo.site = StrSave(appData.icsHost);
9917     gameInfo.date = PGNDate();
9918     gameInfo.round = StrSave("-");
9919
9920     /* Parse out names of players */
9921     while (*game == ' ') game++;
9922     p = buf;
9923     while (*game != ' ') *p++ = *game++;
9924     *p = NULLCHAR;
9925     gameInfo.white = StrSave(buf);
9926     while (*game == ' ') game++;
9927     p = buf;
9928     while (*game != ' ' && *game != '\n') *p++ = *game++;
9929     *p = NULLCHAR;
9930     gameInfo.black = StrSave(buf);
9931
9932     /* Parse moves */
9933     boardIndex = blackPlaysFirst ? 1 : 0;
9934     yynewstr(game);
9935     for (;;) {
9936         yyboardindex = boardIndex;
9937         moveType = (ChessMove) Myylex();
9938         switch (moveType) {
9939           case IllegalMove:             /* maybe suicide chess, etc. */
9940   if (appData.debugMode) {
9941     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9942     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9943     setbuf(debugFP, NULL);
9944   }
9945           case WhitePromotion:
9946           case BlackPromotion:
9947           case WhiteNonPromotion:
9948           case BlackNonPromotion:
9949           case NormalMove:
9950           case FirstLeg:
9951           case WhiteCapturesEnPassant:
9952           case BlackCapturesEnPassant:
9953           case WhiteKingSideCastle:
9954           case WhiteQueenSideCastle:
9955           case BlackKingSideCastle:
9956           case BlackQueenSideCastle:
9957           case WhiteKingSideCastleWild:
9958           case WhiteQueenSideCastleWild:
9959           case BlackKingSideCastleWild:
9960           case BlackQueenSideCastleWild:
9961           /* PUSH Fabien */
9962           case WhiteHSideCastleFR:
9963           case WhiteASideCastleFR:
9964           case BlackHSideCastleFR:
9965           case BlackASideCastleFR:
9966           /* POP Fabien */
9967             fromX = currentMoveString[0] - AAA;
9968             fromY = currentMoveString[1] - ONE;
9969             toX = currentMoveString[2] - AAA;
9970             toY = currentMoveString[3] - ONE;
9971             promoChar = currentMoveString[4];
9972             break;
9973           case WhiteDrop:
9974           case BlackDrop:
9975             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9976             fromX = moveType == WhiteDrop ?
9977               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9978             (int) CharToPiece(ToLower(currentMoveString[0]));
9979             fromY = DROP_RANK;
9980             toX = currentMoveString[2] - AAA;
9981             toY = currentMoveString[3] - ONE;
9982             promoChar = NULLCHAR;
9983             break;
9984           case AmbiguousMove:
9985             /* bug? */
9986             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9987   if (appData.debugMode) {
9988     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9989     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9990     setbuf(debugFP, NULL);
9991   }
9992             DisplayError(buf, 0);
9993             return;
9994           case ImpossibleMove:
9995             /* bug? */
9996             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9997   if (appData.debugMode) {
9998     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9999     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10000     setbuf(debugFP, NULL);
10001   }
10002             DisplayError(buf, 0);
10003             return;
10004           case EndOfFile:
10005             if (boardIndex < backwardMostMove) {
10006                 /* Oops, gap.  How did that happen? */
10007                 DisplayError(_("Gap in move list"), 0);
10008                 return;
10009             }
10010             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10011             if (boardIndex > forwardMostMove) {
10012                 forwardMostMove = boardIndex;
10013             }
10014             return;
10015           case ElapsedTime:
10016             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10017                 strcat(parseList[boardIndex-1], " ");
10018                 strcat(parseList[boardIndex-1], yy_text);
10019             }
10020             continue;
10021           case Comment:
10022           case PGNTag:
10023           case NAG:
10024           default:
10025             /* ignore */
10026             continue;
10027           case WhiteWins:
10028           case BlackWins:
10029           case GameIsDrawn:
10030           case GameUnfinished:
10031             if (gameMode == IcsExamining) {
10032                 if (boardIndex < backwardMostMove) {
10033                     /* Oops, gap.  How did that happen? */
10034                     return;
10035                 }
10036                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10037                 return;
10038             }
10039             gameInfo.result = moveType;
10040             p = strchr(yy_text, '{');
10041             if (p == NULL) p = strchr(yy_text, '(');
10042             if (p == NULL) {
10043                 p = yy_text;
10044                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10045             } else {
10046                 q = strchr(p, *p == '{' ? '}' : ')');
10047                 if (q != NULL) *q = NULLCHAR;
10048                 p++;
10049             }
10050             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10051             gameInfo.resultDetails = StrSave(p);
10052             continue;
10053         }
10054         if (boardIndex >= forwardMostMove &&
10055             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10056             backwardMostMove = blackPlaysFirst ? 1 : 0;
10057             return;
10058         }
10059         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10060                                  fromY, fromX, toY, toX, promoChar,
10061                                  parseList[boardIndex]);
10062         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10063         /* currentMoveString is set as a side-effect of yylex */
10064         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10065         strcat(moveList[boardIndex], "\n");
10066         boardIndex++;
10067         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10068         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10069           case MT_NONE:
10070           case MT_STALEMATE:
10071           default:
10072             break;
10073           case MT_CHECK:
10074             if(!IS_SHOGI(gameInfo.variant))
10075                 strcat(parseList[boardIndex - 1], "+");
10076             break;
10077           case MT_CHECKMATE:
10078           case MT_STAINMATE:
10079             strcat(parseList[boardIndex - 1], "#");
10080             break;
10081         }
10082     }
10083 }
10084
10085
10086 /* Apply a move to the given board  */
10087 void
10088 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10089 {
10090   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10091   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10092
10093     /* [HGM] compute & store e.p. status and castling rights for new position */
10094     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10095
10096       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10097       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10098       board[EP_STATUS] = EP_NONE;
10099       board[EP_FILE] = board[EP_RANK] = 100;
10100
10101   if (fromY == DROP_RANK) {
10102         /* must be first */
10103         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10104             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10105             return;
10106         }
10107         piece = board[toY][toX] = (ChessSquare) fromX;
10108   } else {
10109 //      ChessSquare victim;
10110       int i;
10111
10112       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10113 //           victim = board[killY][killX],
10114            killed = board[killY][killX],
10115            board[killY][killX] = EmptySquare,
10116            board[EP_STATUS] = EP_CAPTURE;
10117            if( kill2X >= 0 && kill2Y >= 0)
10118              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10119       }
10120
10121       if( board[toY][toX] != EmptySquare ) {
10122            board[EP_STATUS] = EP_CAPTURE;
10123            if( (fromX != toX || fromY != toY) && // not igui!
10124                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10125                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10126                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10127            }
10128       }
10129
10130       pawn = board[fromY][fromX];
10131       if( pawn == WhiteLance || pawn == BlackLance ) {
10132            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10133                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10134                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10135            }
10136       }
10137       if( pawn == WhitePawn ) {
10138            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10139                board[EP_STATUS] = EP_PAWN_MOVE;
10140            if( toY-fromY>=2) {
10141                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10142                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10143                         gameInfo.variant != VariantBerolina || toX < fromX)
10144                       board[EP_STATUS] = toX | berolina;
10145                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10146                         gameInfo.variant != VariantBerolina || toX > fromX)
10147                       board[EP_STATUS] = toX;
10148            }
10149       } else
10150       if( pawn == BlackPawn ) {
10151            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10152                board[EP_STATUS] = EP_PAWN_MOVE;
10153            if( toY-fromY<= -2) {
10154                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10155                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10156                         gameInfo.variant != VariantBerolina || toX < fromX)
10157                       board[EP_STATUS] = toX | berolina;
10158                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10159                         gameInfo.variant != VariantBerolina || toX > fromX)
10160                       board[EP_STATUS] = toX;
10161            }
10162        }
10163
10164        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10165        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10166        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10167        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10168
10169        for(i=0; i<nrCastlingRights; i++) {
10170            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10171               board[CASTLING][i] == toX   && castlingRank[i] == toY
10172              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10173        }
10174
10175        if(gameInfo.variant == VariantSChess) { // update virginity
10176            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10177            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10178            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10179            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10180        }
10181
10182      if (fromX == toX && fromY == toY) return;
10183
10184      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10185      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10186      if(gameInfo.variant == VariantKnightmate)
10187          king += (int) WhiteUnicorn - (int) WhiteKing;
10188
10189     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10190        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10191         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10192         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10193         board[EP_STATUS] = EP_NONE; // capture was fake!
10194     } else
10195     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10196         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10197         board[toY][toX] = piece;
10198         board[EP_STATUS] = EP_NONE; // capture was fake!
10199     } else
10200     /* Code added by Tord: */
10201     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10202     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10203         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10204       board[EP_STATUS] = EP_NONE; // capture was fake!
10205       board[fromY][fromX] = EmptySquare;
10206       board[toY][toX] = EmptySquare;
10207       if((toX > fromX) != (piece == WhiteRook)) {
10208         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10209       } else {
10210         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10211       }
10212     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10213                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10214       board[EP_STATUS] = EP_NONE;
10215       board[fromY][fromX] = EmptySquare;
10216       board[toY][toX] = EmptySquare;
10217       if((toX > fromX) != (piece == BlackRook)) {
10218         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10219       } else {
10220         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10221       }
10222     /* End of code added by Tord */
10223
10224     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10225         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10226         board[toY][toX] = piece;
10227     } else if (board[fromY][fromX] == king
10228         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10229         && toY == fromY && toX > fromX+1) {
10230         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10231         board[fromY][toX-1] = board[fromY][rookX];
10232         board[fromY][rookX] = EmptySquare;
10233         board[fromY][fromX] = EmptySquare;
10234         board[toY][toX] = king;
10235     } else if (board[fromY][fromX] == king
10236         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10237                && toY == fromY && toX < fromX-1) {
10238         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10239         board[fromY][toX+1] = board[fromY][rookX];
10240         board[fromY][rookX] = EmptySquare;
10241         board[fromY][fromX] = EmptySquare;
10242         board[toY][toX] = king;
10243     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10244                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10245                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10246                ) {
10247         /* white pawn promotion */
10248         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10249         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10250             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10251         board[fromY][fromX] = EmptySquare;
10252     } else if ((fromY >= BOARD_HEIGHT>>1)
10253                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10254                && (toX != fromX)
10255                && gameInfo.variant != VariantXiangqi
10256                && gameInfo.variant != VariantBerolina
10257                && (pawn == WhitePawn)
10258                && (board[toY][toX] == EmptySquare)) {
10259         board[fromY][fromX] = EmptySquare;
10260         board[toY][toX] = piece;
10261         if(toY == epRank - 128 + 1)
10262             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10263         else
10264             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10265     } else if ((fromY == BOARD_HEIGHT-4)
10266                && (toX == fromX)
10267                && gameInfo.variant == VariantBerolina
10268                && (board[fromY][fromX] == WhitePawn)
10269                && (board[toY][toX] == EmptySquare)) {
10270         board[fromY][fromX] = EmptySquare;
10271         board[toY][toX] = WhitePawn;
10272         if(oldEP & EP_BEROLIN_A) {
10273                 captured = board[fromY][fromX-1];
10274                 board[fromY][fromX-1] = EmptySquare;
10275         }else{  captured = board[fromY][fromX+1];
10276                 board[fromY][fromX+1] = EmptySquare;
10277         }
10278     } else if (board[fromY][fromX] == king
10279         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10280                && toY == fromY && toX > fromX+1) {
10281         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10282         board[fromY][toX-1] = board[fromY][rookX];
10283         board[fromY][rookX] = EmptySquare;
10284         board[fromY][fromX] = EmptySquare;
10285         board[toY][toX] = king;
10286     } else if (board[fromY][fromX] == king
10287         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10288                && toY == fromY && toX < fromX-1) {
10289         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10290         board[fromY][toX+1] = board[fromY][rookX];
10291         board[fromY][rookX] = EmptySquare;
10292         board[fromY][fromX] = EmptySquare;
10293         board[toY][toX] = king;
10294     } else if (fromY == 7 && fromX == 3
10295                && board[fromY][fromX] == BlackKing
10296                && toY == 7 && toX == 5) {
10297         board[fromY][fromX] = EmptySquare;
10298         board[toY][toX] = BlackKing;
10299         board[fromY][7] = EmptySquare;
10300         board[toY][4] = BlackRook;
10301     } else if (fromY == 7 && fromX == 3
10302                && board[fromY][fromX] == BlackKing
10303                && toY == 7 && toX == 1) {
10304         board[fromY][fromX] = EmptySquare;
10305         board[toY][toX] = BlackKing;
10306         board[fromY][0] = EmptySquare;
10307         board[toY][2] = BlackRook;
10308     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10309                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10310                && toY < promoRank && promoChar
10311                ) {
10312         /* black pawn promotion */
10313         board[toY][toX] = CharToPiece(ToLower(promoChar));
10314         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10315             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10316         board[fromY][fromX] = EmptySquare;
10317     } else if ((fromY < BOARD_HEIGHT>>1)
10318                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10319                && (toX != fromX)
10320                && gameInfo.variant != VariantXiangqi
10321                && gameInfo.variant != VariantBerolina
10322                && (pawn == BlackPawn)
10323                && (board[toY][toX] == EmptySquare)) {
10324         board[fromY][fromX] = EmptySquare;
10325         board[toY][toX] = piece;
10326         if(toY == epRank - 128 - 1)
10327             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10328         else
10329             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10330     } else if ((fromY == 3)
10331                && (toX == fromX)
10332                && gameInfo.variant == VariantBerolina
10333                && (board[fromY][fromX] == BlackPawn)
10334                && (board[toY][toX] == EmptySquare)) {
10335         board[fromY][fromX] = EmptySquare;
10336         board[toY][toX] = BlackPawn;
10337         if(oldEP & EP_BEROLIN_A) {
10338                 captured = board[fromY][fromX-1];
10339                 board[fromY][fromX-1] = EmptySquare;
10340         }else{  captured = board[fromY][fromX+1];
10341                 board[fromY][fromX+1] = EmptySquare;
10342         }
10343     } else {
10344         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10345         board[fromY][fromX] = EmptySquare;
10346         board[toY][toX] = piece;
10347     }
10348   }
10349
10350     if (gameInfo.holdingsWidth != 0) {
10351
10352       /* !!A lot more code needs to be written to support holdings  */
10353       /* [HGM] OK, so I have written it. Holdings are stored in the */
10354       /* penultimate board files, so they are automaticlly stored   */
10355       /* in the game history.                                       */
10356       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10357                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10358         /* Delete from holdings, by decreasing count */
10359         /* and erasing image if necessary            */
10360         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10361         if(p < (int) BlackPawn) { /* white drop */
10362              p -= (int)WhitePawn;
10363                  p = PieceToNumber((ChessSquare)p);
10364              if(p >= gameInfo.holdingsSize) p = 0;
10365              if(--board[p][BOARD_WIDTH-2] <= 0)
10366                   board[p][BOARD_WIDTH-1] = EmptySquare;
10367              if((int)board[p][BOARD_WIDTH-2] < 0)
10368                         board[p][BOARD_WIDTH-2] = 0;
10369         } else {                  /* black drop */
10370              p -= (int)BlackPawn;
10371                  p = PieceToNumber((ChessSquare)p);
10372              if(p >= gameInfo.holdingsSize) p = 0;
10373              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10374                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10375              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10376                         board[BOARD_HEIGHT-1-p][1] = 0;
10377         }
10378       }
10379       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10380           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10381         /* [HGM] holdings: Add to holdings, if holdings exist */
10382         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10383                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10384                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10385         }
10386         p = (int) captured;
10387         if (p >= (int) BlackPawn) {
10388           p -= (int)BlackPawn;
10389           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10390                   /* Restore shogi-promoted piece to its original  first */
10391                   captured = (ChessSquare) (DEMOTED(captured));
10392                   p = DEMOTED(p);
10393           }
10394           p = PieceToNumber((ChessSquare)p);
10395           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10396           board[p][BOARD_WIDTH-2]++;
10397           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10398         } else {
10399           p -= (int)WhitePawn;
10400           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10401                   captured = (ChessSquare) (DEMOTED(captured));
10402                   p = DEMOTED(p);
10403           }
10404           p = PieceToNumber((ChessSquare)p);
10405           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10406           board[BOARD_HEIGHT-1-p][1]++;
10407           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10408         }
10409       }
10410     } else if (gameInfo.variant == VariantAtomic) {
10411       if (captured != EmptySquare) {
10412         int y, x;
10413         for (y = toY-1; y <= toY+1; y++) {
10414           for (x = toX-1; x <= toX+1; x++) {
10415             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10416                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10417               board[y][x] = EmptySquare;
10418             }
10419           }
10420         }
10421         board[toY][toX] = EmptySquare;
10422       }
10423     }
10424
10425     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10426         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10427     } else
10428     if(promoChar == '+') {
10429         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10430         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10431         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10432           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10433     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10434         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10435         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10436            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10437         board[toY][toX] = newPiece;
10438     }
10439     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10440                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10441         // [HGM] superchess: take promotion piece out of holdings
10442         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10443         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10444             if(!--board[k][BOARD_WIDTH-2])
10445                 board[k][BOARD_WIDTH-1] = EmptySquare;
10446         } else {
10447             if(!--board[BOARD_HEIGHT-1-k][1])
10448                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10449         }
10450     }
10451 }
10452
10453 /* Updates forwardMostMove */
10454 void
10455 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10456 {
10457     int x = toX, y = toY;
10458     char *s = parseList[forwardMostMove];
10459     ChessSquare p = boards[forwardMostMove][toY][toX];
10460 //    forwardMostMove++; // [HGM] bare: moved downstream
10461
10462     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10463     (void) CoordsToAlgebraic(boards[forwardMostMove],
10464                              PosFlags(forwardMostMove),
10465                              fromY, fromX, y, x, promoChar,
10466                              s);
10467     if(killX >= 0 && killY >= 0)
10468         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10469
10470     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10471         int timeLeft; static int lastLoadFlag=0; int king, piece;
10472         piece = boards[forwardMostMove][fromY][fromX];
10473         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10474         if(gameInfo.variant == VariantKnightmate)
10475             king += (int) WhiteUnicorn - (int) WhiteKing;
10476         if(forwardMostMove == 0) {
10477             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10478                 fprintf(serverMoves, "%s;", UserName());
10479             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10480                 fprintf(serverMoves, "%s;", second.tidy);
10481             fprintf(serverMoves, "%s;", first.tidy);
10482             if(gameMode == MachinePlaysWhite)
10483                 fprintf(serverMoves, "%s;", UserName());
10484             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10485                 fprintf(serverMoves, "%s;", second.tidy);
10486         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10487         lastLoadFlag = loadFlag;
10488         // print base move
10489         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10490         // print castling suffix
10491         if( toY == fromY && piece == king ) {
10492             if(toX-fromX > 1)
10493                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10494             if(fromX-toX >1)
10495                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10496         }
10497         // e.p. suffix
10498         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10499              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10500              boards[forwardMostMove][toY][toX] == EmptySquare
10501              && fromX != toX && fromY != toY)
10502                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10503         // promotion suffix
10504         if(promoChar != NULLCHAR) {
10505             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10506                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10507                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10508             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10509         }
10510         if(!loadFlag) {
10511                 char buf[MOVE_LEN*2], *p; int len;
10512             fprintf(serverMoves, "/%d/%d",
10513                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10514             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10515             else                      timeLeft = blackTimeRemaining/1000;
10516             fprintf(serverMoves, "/%d", timeLeft);
10517                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10518                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10519                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10520                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10521             fprintf(serverMoves, "/%s", buf);
10522         }
10523         fflush(serverMoves);
10524     }
10525
10526     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10527         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10528       return;
10529     }
10530     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10531     if (commentList[forwardMostMove+1] != NULL) {
10532         free(commentList[forwardMostMove+1]);
10533         commentList[forwardMostMove+1] = NULL;
10534     }
10535     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10536     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10537     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10538     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10539     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10540     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10541     adjustedClock = FALSE;
10542     gameInfo.result = GameUnfinished;
10543     if (gameInfo.resultDetails != NULL) {
10544         free(gameInfo.resultDetails);
10545         gameInfo.resultDetails = NULL;
10546     }
10547     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10548                               moveList[forwardMostMove - 1]);
10549     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10550       case MT_NONE:
10551       case MT_STALEMATE:
10552       default:
10553         break;
10554       case MT_CHECK:
10555         if(!IS_SHOGI(gameInfo.variant))
10556             strcat(parseList[forwardMostMove - 1], "+");
10557         break;
10558       case MT_CHECKMATE:
10559       case MT_STAINMATE:
10560         strcat(parseList[forwardMostMove - 1], "#");
10561         break;
10562     }
10563 }
10564
10565 /* Updates currentMove if not pausing */
10566 void
10567 ShowMove (int fromX, int fromY, int toX, int toY)
10568 {
10569     int instant = (gameMode == PlayFromGameFile) ?
10570         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10571     if(appData.noGUI) return;
10572     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10573         if (!instant) {
10574             if (forwardMostMove == currentMove + 1) {
10575                 AnimateMove(boards[forwardMostMove - 1],
10576                             fromX, fromY, toX, toY);
10577             }
10578         }
10579         currentMove = forwardMostMove;
10580     }
10581
10582     killX = killY = -1; // [HGM] lion: used up
10583
10584     if (instant) return;
10585
10586     DisplayMove(currentMove - 1);
10587     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10588             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10589                 SetHighlights(fromX, fromY, toX, toY);
10590             }
10591     }
10592     DrawPosition(FALSE, boards[currentMove]);
10593     DisplayBothClocks();
10594     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10595 }
10596
10597 void
10598 SendEgtPath (ChessProgramState *cps)
10599 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10600         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10601
10602         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10603
10604         while(*p) {
10605             char c, *q = name+1, *r, *s;
10606
10607             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10608             while(*p && *p != ',') *q++ = *p++;
10609             *q++ = ':'; *q = 0;
10610             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10611                 strcmp(name, ",nalimov:") == 0 ) {
10612                 // take nalimov path from the menu-changeable option first, if it is defined
10613               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10614                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10615             } else
10616             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10617                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10618                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10619                 s = r = StrStr(s, ":") + 1; // beginning of path info
10620                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10621                 c = *r; *r = 0;             // temporarily null-terminate path info
10622                     *--q = 0;               // strip of trailig ':' from name
10623                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10624                 *r = c;
10625                 SendToProgram(buf,cps);     // send egtbpath command for this format
10626             }
10627             if(*p == ',') p++; // read away comma to position for next format name
10628         }
10629 }
10630
10631 static int
10632 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10633 {
10634       int width = 8, height = 8, holdings = 0;             // most common sizes
10635       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10636       // correct the deviations default for each variant
10637       if( v == VariantXiangqi ) width = 9,  height = 10;
10638       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10639       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10640       if( v == VariantCapablanca || v == VariantCapaRandom ||
10641           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10642                                 width = 10;
10643       if( v == VariantCourier ) width = 12;
10644       if( v == VariantSuper )                            holdings = 8;
10645       if( v == VariantGreat )   width = 10,              holdings = 8;
10646       if( v == VariantSChess )                           holdings = 7;
10647       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10648       if( v == VariantChuChess) width = 10, height = 10;
10649       if( v == VariantChu )     width = 12, height = 12;
10650       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10651              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10652              holdingsSize >= 0 && holdingsSize != holdings;
10653 }
10654
10655 char variantError[MSG_SIZ];
10656
10657 char *
10658 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10659 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10660       char *p, *variant = VariantName(v);
10661       static char b[MSG_SIZ];
10662       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10663            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10664                                                holdingsSize, variant); // cook up sized variant name
10665            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10666            if(StrStr(list, b) == NULL) {
10667                // specific sized variant not known, check if general sizing allowed
10668                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10669                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10670                             boardWidth, boardHeight, holdingsSize, engine);
10671                    return NULL;
10672                }
10673                /* [HGM] here we really should compare with the maximum supported board size */
10674            }
10675       } else snprintf(b, MSG_SIZ,"%s", variant);
10676       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10677       p = StrStr(list, b);
10678       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10679       if(p == NULL) {
10680           // occurs not at all in list, or only as sub-string
10681           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10682           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10683               int l = strlen(variantError);
10684               char *q;
10685               while(p != list && p[-1] != ',') p--;
10686               q = strchr(p, ',');
10687               if(q) *q = NULLCHAR;
10688               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10689               if(q) *q= ',';
10690           }
10691           return NULL;
10692       }
10693       return b;
10694 }
10695
10696 void
10697 InitChessProgram (ChessProgramState *cps, int setup)
10698 /* setup needed to setup FRC opening position */
10699 {
10700     char buf[MSG_SIZ], *b;
10701     if (appData.noChessProgram) return;
10702     hintRequested = FALSE;
10703     bookRequested = FALSE;
10704
10705     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10706     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10707     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10708     if(cps->memSize) { /* [HGM] memory */
10709       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10710         SendToProgram(buf, cps);
10711     }
10712     SendEgtPath(cps); /* [HGM] EGT */
10713     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10714       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10715         SendToProgram(buf, cps);
10716     }
10717
10718     setboardSpoiledMachineBlack = FALSE;
10719     SendToProgram(cps->initString, cps);
10720     if (gameInfo.variant != VariantNormal &&
10721         gameInfo.variant != VariantLoadable
10722         /* [HGM] also send variant if board size non-standard */
10723         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10724
10725       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10726                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10727       if (b == NULL) {
10728         VariantClass v;
10729         char c, *q = cps->variants, *p = strchr(q, ',');
10730         if(p) *p = NULLCHAR;
10731         v = StringToVariant(q);
10732         DisplayError(variantError, 0);
10733         if(v != VariantUnknown && cps == &first) {
10734             int w, h, s;
10735             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10736                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10737             ASSIGN(appData.variant, q);
10738             Reset(TRUE, FALSE);
10739         }
10740         if(p) *p = ',';
10741         return;
10742       }
10743
10744       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10745       SendToProgram(buf, cps);
10746     }
10747     currentlyInitializedVariant = gameInfo.variant;
10748
10749     /* [HGM] send opening position in FRC to first engine */
10750     if(setup) {
10751           SendToProgram("force\n", cps);
10752           SendBoard(cps, 0);
10753           /* engine is now in force mode! Set flag to wake it up after first move. */
10754           setboardSpoiledMachineBlack = 1;
10755     }
10756
10757     if (cps->sendICS) {
10758       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10759       SendToProgram(buf, cps);
10760     }
10761     cps->maybeThinking = FALSE;
10762     cps->offeredDraw = 0;
10763     if (!appData.icsActive) {
10764         SendTimeControl(cps, movesPerSession, timeControl,
10765                         timeIncrement, appData.searchDepth,
10766                         searchTime);
10767     }
10768     if (appData.showThinking
10769         // [HGM] thinking: four options require thinking output to be sent
10770         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10771                                 ) {
10772         SendToProgram("post\n", cps);
10773     }
10774     SendToProgram("hard\n", cps);
10775     if (!appData.ponderNextMove) {
10776         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10777            it without being sure what state we are in first.  "hard"
10778            is not a toggle, so that one is OK.
10779          */
10780         SendToProgram("easy\n", cps);
10781     }
10782     if (cps->usePing) {
10783       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10784       SendToProgram(buf, cps);
10785     }
10786     cps->initDone = TRUE;
10787     ClearEngineOutputPane(cps == &second);
10788 }
10789
10790
10791 void
10792 ResendOptions (ChessProgramState *cps)
10793 { // send the stored value of the options
10794   int i;
10795   char buf[MSG_SIZ];
10796   Option *opt = cps->option;
10797   for(i=0; i<cps->nrOptions; i++, opt++) {
10798       switch(opt->type) {
10799         case Spin:
10800         case Slider:
10801         case CheckBox:
10802             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10803           break;
10804         case ComboBox:
10805           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10806           break;
10807         default:
10808             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10809           break;
10810         case Button:
10811         case SaveButton:
10812           continue;
10813       }
10814       SendToProgram(buf, cps);
10815   }
10816 }
10817
10818 void
10819 StartChessProgram (ChessProgramState *cps)
10820 {
10821     char buf[MSG_SIZ];
10822     int err;
10823
10824     if (appData.noChessProgram) return;
10825     cps->initDone = FALSE;
10826
10827     if (strcmp(cps->host, "localhost") == 0) {
10828         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10829     } else if (*appData.remoteShell == NULLCHAR) {
10830         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10831     } else {
10832         if (*appData.remoteUser == NULLCHAR) {
10833           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10834                     cps->program);
10835         } else {
10836           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10837                     cps->host, appData.remoteUser, cps->program);
10838         }
10839         err = StartChildProcess(buf, "", &cps->pr);
10840     }
10841
10842     if (err != 0) {
10843       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10844         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10845         if(cps != &first) return;
10846         appData.noChessProgram = TRUE;
10847         ThawUI();
10848         SetNCPMode();
10849 //      DisplayFatalError(buf, err, 1);
10850 //      cps->pr = NoProc;
10851 //      cps->isr = NULL;
10852         return;
10853     }
10854
10855     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10856     if (cps->protocolVersion > 1) {
10857       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10858       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10859         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10860         cps->comboCnt = 0;  //                and values of combo boxes
10861       }
10862       SendToProgram(buf, cps);
10863       if(cps->reload) ResendOptions(cps);
10864     } else {
10865       SendToProgram("xboard\n", cps);
10866     }
10867 }
10868
10869 void
10870 TwoMachinesEventIfReady P((void))
10871 {
10872   static int curMess = 0;
10873   if (first.lastPing != first.lastPong) {
10874     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10875     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10876     return;
10877   }
10878   if (second.lastPing != second.lastPong) {
10879     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10880     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10881     return;
10882   }
10883   DisplayMessage("", ""); curMess = 0;
10884   TwoMachinesEvent();
10885 }
10886
10887 char *
10888 MakeName (char *template)
10889 {
10890     time_t clock;
10891     struct tm *tm;
10892     static char buf[MSG_SIZ];
10893     char *p = buf;
10894     int i;
10895
10896     clock = time((time_t *)NULL);
10897     tm = localtime(&clock);
10898
10899     while(*p++ = *template++) if(p[-1] == '%') {
10900         switch(*template++) {
10901           case 0:   *p = 0; return buf;
10902           case 'Y': i = tm->tm_year+1900; break;
10903           case 'y': i = tm->tm_year-100; break;
10904           case 'M': i = tm->tm_mon+1; break;
10905           case 'd': i = tm->tm_mday; break;
10906           case 'h': i = tm->tm_hour; break;
10907           case 'm': i = tm->tm_min; break;
10908           case 's': i = tm->tm_sec; break;
10909           default:  i = 0;
10910         }
10911         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10912     }
10913     return buf;
10914 }
10915
10916 int
10917 CountPlayers (char *p)
10918 {
10919     int n = 0;
10920     while(p = strchr(p, '\n')) p++, n++; // count participants
10921     return n;
10922 }
10923
10924 FILE *
10925 WriteTourneyFile (char *results, FILE *f)
10926 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10927     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10928     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10929         // create a file with tournament description
10930         fprintf(f, "-participants {%s}\n", appData.participants);
10931         fprintf(f, "-seedBase %d\n", appData.seedBase);
10932         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10933         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10934         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10935         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10936         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10937         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10938         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10939         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10940         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10941         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10942         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10943         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10944         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10945         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10946         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10947         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10948         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10949         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10950         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10951         fprintf(f, "-smpCores %d\n", appData.smpCores);
10952         if(searchTime > 0)
10953                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10954         else {
10955                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10956                 fprintf(f, "-tc %s\n", appData.timeControl);
10957                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10958         }
10959         fprintf(f, "-results \"%s\"\n", results);
10960     }
10961     return f;
10962 }
10963
10964 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10965
10966 void
10967 Substitute (char *participants, int expunge)
10968 {
10969     int i, changed, changes=0, nPlayers=0;
10970     char *p, *q, *r, buf[MSG_SIZ];
10971     if(participants == NULL) return;
10972     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10973     r = p = participants; q = appData.participants;
10974     while(*p && *p == *q) {
10975         if(*p == '\n') r = p+1, nPlayers++;
10976         p++; q++;
10977     }
10978     if(*p) { // difference
10979         while(*p && *p++ != '\n');
10980         while(*q && *q++ != '\n');
10981       changed = nPlayers;
10982         changes = 1 + (strcmp(p, q) != 0);
10983     }
10984     if(changes == 1) { // a single engine mnemonic was changed
10985         q = r; while(*q) nPlayers += (*q++ == '\n');
10986         p = buf; while(*r && (*p = *r++) != '\n') p++;
10987         *p = NULLCHAR;
10988         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10989         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10990         if(mnemonic[i]) { // The substitute is valid
10991             FILE *f;
10992             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10993                 flock(fileno(f), LOCK_EX);
10994                 ParseArgsFromFile(f);
10995                 fseek(f, 0, SEEK_SET);
10996                 FREE(appData.participants); appData.participants = participants;
10997                 if(expunge) { // erase results of replaced engine
10998                     int len = strlen(appData.results), w, b, dummy;
10999                     for(i=0; i<len; i++) {
11000                         Pairing(i, nPlayers, &w, &b, &dummy);
11001                         if((w == changed || b == changed) && appData.results[i] == '*') {
11002                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11003                             fclose(f);
11004                             return;
11005                         }
11006                     }
11007                     for(i=0; i<len; i++) {
11008                         Pairing(i, nPlayers, &w, &b, &dummy);
11009                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11010                     }
11011                 }
11012                 WriteTourneyFile(appData.results, f);
11013                 fclose(f); // release lock
11014                 return;
11015             }
11016         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11017     }
11018     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11019     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11020     free(participants);
11021     return;
11022 }
11023
11024 int
11025 CheckPlayers (char *participants)
11026 {
11027         int i;
11028         char buf[MSG_SIZ], *p;
11029         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11030         while(p = strchr(participants, '\n')) {
11031             *p = NULLCHAR;
11032             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11033             if(!mnemonic[i]) {
11034                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11035                 *p = '\n';
11036                 DisplayError(buf, 0);
11037                 return 1;
11038             }
11039             *p = '\n';
11040             participants = p + 1;
11041         }
11042         return 0;
11043 }
11044
11045 int
11046 CreateTourney (char *name)
11047 {
11048         FILE *f;
11049         if(matchMode && strcmp(name, appData.tourneyFile)) {
11050              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11051         }
11052         if(name[0] == NULLCHAR) {
11053             if(appData.participants[0])
11054                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11055             return 0;
11056         }
11057         f = fopen(name, "r");
11058         if(f) { // file exists
11059             ASSIGN(appData.tourneyFile, name);
11060             ParseArgsFromFile(f); // parse it
11061         } else {
11062             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11063             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11064                 DisplayError(_("Not enough participants"), 0);
11065                 return 0;
11066             }
11067             if(CheckPlayers(appData.participants)) return 0;
11068             ASSIGN(appData.tourneyFile, name);
11069             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11070             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11071         }
11072         fclose(f);
11073         appData.noChessProgram = FALSE;
11074         appData.clockMode = TRUE;
11075         SetGNUMode();
11076         return 1;
11077 }
11078
11079 int
11080 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11081 {
11082     char buf[MSG_SIZ], *p, *q;
11083     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11084     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11085     skip = !all && group[0]; // if group requested, we start in skip mode
11086     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11087         p = names; q = buf; header = 0;
11088         while(*p && *p != '\n') *q++ = *p++;
11089         *q = 0;
11090         if(*p == '\n') p++;
11091         if(buf[0] == '#') {
11092             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11093             depth++; // we must be entering a new group
11094             if(all) continue; // suppress printing group headers when complete list requested
11095             header = 1;
11096             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11097         }
11098         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11099         if(engineList[i]) free(engineList[i]);
11100         engineList[i] = strdup(buf);
11101         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11102         if(engineMnemonic[i]) free(engineMnemonic[i]);
11103         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11104             strcat(buf, " (");
11105             sscanf(q + 8, "%s", buf + strlen(buf));
11106             strcat(buf, ")");
11107         }
11108         engineMnemonic[i] = strdup(buf);
11109         i++;
11110     }
11111     engineList[i] = engineMnemonic[i] = NULL;
11112     return i;
11113 }
11114
11115 // following implemented as macro to avoid type limitations
11116 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11117
11118 void
11119 SwapEngines (int n)
11120 {   // swap settings for first engine and other engine (so far only some selected options)
11121     int h;
11122     char *p;
11123     if(n == 0) return;
11124     SWAP(directory, p)
11125     SWAP(chessProgram, p)
11126     SWAP(isUCI, h)
11127     SWAP(hasOwnBookUCI, h)
11128     SWAP(protocolVersion, h)
11129     SWAP(reuse, h)
11130     SWAP(scoreIsAbsolute, h)
11131     SWAP(timeOdds, h)
11132     SWAP(logo, p)
11133     SWAP(pgnName, p)
11134     SWAP(pvSAN, h)
11135     SWAP(engOptions, p)
11136     SWAP(engInitString, p)
11137     SWAP(computerString, p)
11138     SWAP(features, p)
11139     SWAP(fenOverride, p)
11140     SWAP(NPS, h)
11141     SWAP(accumulateTC, h)
11142     SWAP(drawDepth, h)
11143     SWAP(host, p)
11144     SWAP(pseudo, h)
11145 }
11146
11147 int
11148 GetEngineLine (char *s, int n)
11149 {
11150     int i;
11151     char buf[MSG_SIZ];
11152     extern char *icsNames;
11153     if(!s || !*s) return 0;
11154     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11155     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11156     if(!mnemonic[i]) return 0;
11157     if(n == 11) return 1; // just testing if there was a match
11158     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11159     if(n == 1) SwapEngines(n);
11160     ParseArgsFromString(buf);
11161     if(n == 1) SwapEngines(n);
11162     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11163         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11164         ParseArgsFromString(buf);
11165     }
11166     return 1;
11167 }
11168
11169 int
11170 SetPlayer (int player, char *p)
11171 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11172     int i;
11173     char buf[MSG_SIZ], *engineName;
11174     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11175     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11176     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11177     if(mnemonic[i]) {
11178         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11179         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11180         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11181         ParseArgsFromString(buf);
11182     } else { // no engine with this nickname is installed!
11183         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11184         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11185         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11186         ModeHighlight();
11187         DisplayError(buf, 0);
11188         return 0;
11189     }
11190     free(engineName);
11191     return i;
11192 }
11193
11194 char *recentEngines;
11195
11196 void
11197 RecentEngineEvent (int nr)
11198 {
11199     int n;
11200 //    SwapEngines(1); // bump first to second
11201 //    ReplaceEngine(&second, 1); // and load it there
11202     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11203     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11204     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11205         ReplaceEngine(&first, 0);
11206         FloatToFront(&appData.recentEngineList, command[n]);
11207     }
11208 }
11209
11210 int
11211 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11212 {   // determine players from game number
11213     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11214
11215     if(appData.tourneyType == 0) {
11216         roundsPerCycle = (nPlayers - 1) | 1;
11217         pairingsPerRound = nPlayers / 2;
11218     } else if(appData.tourneyType > 0) {
11219         roundsPerCycle = nPlayers - appData.tourneyType;
11220         pairingsPerRound = appData.tourneyType;
11221     }
11222     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11223     gamesPerCycle = gamesPerRound * roundsPerCycle;
11224     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11225     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11226     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11227     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11228     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11229     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11230
11231     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11232     if(appData.roundSync) *syncInterval = gamesPerRound;
11233
11234     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11235
11236     if(appData.tourneyType == 0) {
11237         if(curPairing == (nPlayers-1)/2 ) {
11238             *whitePlayer = curRound;
11239             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11240         } else {
11241             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11242             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11243             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11244             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11245         }
11246     } else if(appData.tourneyType > 1) {
11247         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11248         *whitePlayer = curRound + appData.tourneyType;
11249     } else if(appData.tourneyType > 0) {
11250         *whitePlayer = curPairing;
11251         *blackPlayer = curRound + appData.tourneyType;
11252     }
11253
11254     // take care of white/black alternation per round.
11255     // For cycles and games this is already taken care of by default, derived from matchGame!
11256     return curRound & 1;
11257 }
11258
11259 int
11260 NextTourneyGame (int nr, int *swapColors)
11261 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11262     char *p, *q;
11263     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11264     FILE *tf;
11265     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11266     tf = fopen(appData.tourneyFile, "r");
11267     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11268     ParseArgsFromFile(tf); fclose(tf);
11269     InitTimeControls(); // TC might be altered from tourney file
11270
11271     nPlayers = CountPlayers(appData.participants); // count participants
11272     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11273     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11274
11275     if(syncInterval) {
11276         p = q = appData.results;
11277         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11278         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11279             DisplayMessage(_("Waiting for other game(s)"),"");
11280             waitingForGame = TRUE;
11281             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11282             return 0;
11283         }
11284         waitingForGame = FALSE;
11285     }
11286
11287     if(appData.tourneyType < 0) {
11288         if(nr>=0 && !pairingReceived) {
11289             char buf[1<<16];
11290             if(pairing.pr == NoProc) {
11291                 if(!appData.pairingEngine[0]) {
11292                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11293                     return 0;
11294                 }
11295                 StartChessProgram(&pairing); // starts the pairing engine
11296             }
11297             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11298             SendToProgram(buf, &pairing);
11299             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11300             SendToProgram(buf, &pairing);
11301             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11302         }
11303         pairingReceived = 0;                              // ... so we continue here
11304         *swapColors = 0;
11305         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11306         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11307         matchGame = 1; roundNr = nr / syncInterval + 1;
11308     }
11309
11310     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11311
11312     // redefine engines, engine dir, etc.
11313     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11314     if(first.pr == NoProc) {
11315       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11316       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11317     }
11318     if(second.pr == NoProc) {
11319       SwapEngines(1);
11320       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11321       SwapEngines(1);         // and make that valid for second engine by swapping
11322       InitEngine(&second, 1);
11323     }
11324     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11325     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11326     return OK;
11327 }
11328
11329 void
11330 NextMatchGame ()
11331 {   // performs game initialization that does not invoke engines, and then tries to start the game
11332     int res, firstWhite, swapColors = 0;
11333     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11334     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
11335         char buf[MSG_SIZ];
11336         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11337         if(strcmp(buf, currentDebugFile)) { // name has changed
11338             FILE *f = fopen(buf, "w");
11339             if(f) { // if opening the new file failed, just keep using the old one
11340                 ASSIGN(currentDebugFile, buf);
11341                 fclose(debugFP);
11342                 debugFP = f;
11343             }
11344             if(appData.serverFileName) {
11345                 if(serverFP) fclose(serverFP);
11346                 serverFP = fopen(appData.serverFileName, "w");
11347                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11348                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11349             }
11350         }
11351     }
11352     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11353     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11354     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11355     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11356     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11357     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11358     Reset(FALSE, first.pr != NoProc);
11359     res = LoadGameOrPosition(matchGame); // setup game
11360     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11361     if(!res) return; // abort when bad game/pos file
11362     TwoMachinesEvent();
11363 }
11364
11365 void
11366 UserAdjudicationEvent (int result)
11367 {
11368     ChessMove gameResult = GameIsDrawn;
11369
11370     if( result > 0 ) {
11371         gameResult = WhiteWins;
11372     }
11373     else if( result < 0 ) {
11374         gameResult = BlackWins;
11375     }
11376
11377     if( gameMode == TwoMachinesPlay ) {
11378         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11379     }
11380 }
11381
11382
11383 // [HGM] save: calculate checksum of game to make games easily identifiable
11384 int
11385 StringCheckSum (char *s)
11386 {
11387         int i = 0;
11388         if(s==NULL) return 0;
11389         while(*s) i = i*259 + *s++;
11390         return i;
11391 }
11392
11393 int
11394 GameCheckSum ()
11395 {
11396         int i, sum=0;
11397         for(i=backwardMostMove; i<forwardMostMove; i++) {
11398                 sum += pvInfoList[i].depth;
11399                 sum += StringCheckSum(parseList[i]);
11400                 sum += StringCheckSum(commentList[i]);
11401                 sum *= 261;
11402         }
11403         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11404         return sum + StringCheckSum(commentList[i]);
11405 } // end of save patch
11406
11407 void
11408 GameEnds (ChessMove result, char *resultDetails, int whosays)
11409 {
11410     GameMode nextGameMode;
11411     int isIcsGame;
11412     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11413
11414     if(endingGame) return; /* [HGM] crash: forbid recursion */
11415     endingGame = 1;
11416     if(twoBoards) { // [HGM] dual: switch back to one board
11417         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11418         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11419     }
11420     if (appData.debugMode) {
11421       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11422               result, resultDetails ? resultDetails : "(null)", whosays);
11423     }
11424
11425     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11426
11427     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11428
11429     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11430         /* If we are playing on ICS, the server decides when the
11431            game is over, but the engine can offer to draw, claim
11432            a draw, or resign.
11433          */
11434 #if ZIPPY
11435         if (appData.zippyPlay && first.initDone) {
11436             if (result == GameIsDrawn) {
11437                 /* In case draw still needs to be claimed */
11438                 SendToICS(ics_prefix);
11439                 SendToICS("draw\n");
11440             } else if (StrCaseStr(resultDetails, "resign")) {
11441                 SendToICS(ics_prefix);
11442                 SendToICS("resign\n");
11443             }
11444         }
11445 #endif
11446         endingGame = 0; /* [HGM] crash */
11447         return;
11448     }
11449
11450     /* If we're loading the game from a file, stop */
11451     if (whosays == GE_FILE) {
11452       (void) StopLoadGameTimer();
11453       gameFileFP = NULL;
11454     }
11455
11456     /* Cancel draw offers */
11457     first.offeredDraw = second.offeredDraw = 0;
11458
11459     /* If this is an ICS game, only ICS can really say it's done;
11460        if not, anyone can. */
11461     isIcsGame = (gameMode == IcsPlayingWhite ||
11462                  gameMode == IcsPlayingBlack ||
11463                  gameMode == IcsObserving    ||
11464                  gameMode == IcsExamining);
11465
11466     if (!isIcsGame || whosays == GE_ICS) {
11467         /* OK -- not an ICS game, or ICS said it was done */
11468         StopClocks();
11469         if (!isIcsGame && !appData.noChessProgram)
11470           SetUserThinkingEnables();
11471
11472         /* [HGM] if a machine claims the game end we verify this claim */
11473         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11474             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11475                 char claimer;
11476                 ChessMove trueResult = (ChessMove) -1;
11477
11478                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11479                                             first.twoMachinesColor[0] :
11480                                             second.twoMachinesColor[0] ;
11481
11482                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11483                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11484                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11485                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11486                 } else
11487                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11488                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11489                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11490                 } else
11491                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11492                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11493                 }
11494
11495                 // now verify win claims, but not in drop games, as we don't understand those yet
11496                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11497                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11498                     (result == WhiteWins && claimer == 'w' ||
11499                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11500                       if (appData.debugMode) {
11501                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11502                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11503                       }
11504                       if(result != trueResult) {
11505                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11506                               result = claimer == 'w' ? BlackWins : WhiteWins;
11507                               resultDetails = buf;
11508                       }
11509                 } else
11510                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11511                     && (forwardMostMove <= backwardMostMove ||
11512                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11513                         (claimer=='b')==(forwardMostMove&1))
11514                                                                                   ) {
11515                       /* [HGM] verify: draws that were not flagged are false claims */
11516                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11517                       result = claimer == 'w' ? BlackWins : WhiteWins;
11518                       resultDetails = buf;
11519                 }
11520                 /* (Claiming a loss is accepted no questions asked!) */
11521             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11522                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11523                 result = GameUnfinished;
11524                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11525             }
11526             /* [HGM] bare: don't allow bare King to win */
11527             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11528                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11529                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11530                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11531                && result != GameIsDrawn)
11532             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11533                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11534                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11535                         if(p >= 0 && p <= (int)WhiteKing) k++;
11536                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11537                 }
11538                 if (appData.debugMode) {
11539                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11540                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11541                 }
11542                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11543                         result = GameIsDrawn;
11544                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11545                         resultDetails = buf;
11546                 }
11547             }
11548         }
11549
11550
11551         if(serverMoves != NULL && !loadFlag) { char c = '=';
11552             if(result==WhiteWins) c = '+';
11553             if(result==BlackWins) c = '-';
11554             if(resultDetails != NULL)
11555                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11556         }
11557         if (resultDetails != NULL) {
11558             gameInfo.result = result;
11559             gameInfo.resultDetails = StrSave(resultDetails);
11560
11561             /* display last move only if game was not loaded from file */
11562             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11563                 DisplayMove(currentMove - 1);
11564
11565             if (forwardMostMove != 0) {
11566                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11567                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11568                                                                 ) {
11569                     if (*appData.saveGameFile != NULLCHAR) {
11570                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11571                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11572                         else
11573                         SaveGameToFile(appData.saveGameFile, TRUE);
11574                     } else if (appData.autoSaveGames) {
11575                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11576                     }
11577                     if (*appData.savePositionFile != NULLCHAR) {
11578                         SavePositionToFile(appData.savePositionFile);
11579                     }
11580                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11581                 }
11582             }
11583
11584             /* Tell program how game ended in case it is learning */
11585             /* [HGM] Moved this to after saving the PGN, just in case */
11586             /* engine died and we got here through time loss. In that */
11587             /* case we will get a fatal error writing the pipe, which */
11588             /* would otherwise lose us the PGN.                       */
11589             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11590             /* output during GameEnds should never be fatal anymore   */
11591             if (gameMode == MachinePlaysWhite ||
11592                 gameMode == MachinePlaysBlack ||
11593                 gameMode == TwoMachinesPlay ||
11594                 gameMode == IcsPlayingWhite ||
11595                 gameMode == IcsPlayingBlack ||
11596                 gameMode == BeginningOfGame) {
11597                 char buf[MSG_SIZ];
11598                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11599                         resultDetails);
11600                 if (first.pr != NoProc) {
11601                     SendToProgram(buf, &first);
11602                 }
11603                 if (second.pr != NoProc &&
11604                     gameMode == TwoMachinesPlay) {
11605                     SendToProgram(buf, &second);
11606                 }
11607             }
11608         }
11609
11610         if (appData.icsActive) {
11611             if (appData.quietPlay &&
11612                 (gameMode == IcsPlayingWhite ||
11613                  gameMode == IcsPlayingBlack)) {
11614                 SendToICS(ics_prefix);
11615                 SendToICS("set shout 1\n");
11616             }
11617             nextGameMode = IcsIdle;
11618             ics_user_moved = FALSE;
11619             /* clean up premove.  It's ugly when the game has ended and the
11620              * premove highlights are still on the board.
11621              */
11622             if (gotPremove) {
11623               gotPremove = FALSE;
11624               ClearPremoveHighlights();
11625               DrawPosition(FALSE, boards[currentMove]);
11626             }
11627             if (whosays == GE_ICS) {
11628                 switch (result) {
11629                 case WhiteWins:
11630                     if (gameMode == IcsPlayingWhite)
11631                         PlayIcsWinSound();
11632                     else if(gameMode == IcsPlayingBlack)
11633                         PlayIcsLossSound();
11634                     break;
11635                 case BlackWins:
11636                     if (gameMode == IcsPlayingBlack)
11637                         PlayIcsWinSound();
11638                     else if(gameMode == IcsPlayingWhite)
11639                         PlayIcsLossSound();
11640                     break;
11641                 case GameIsDrawn:
11642                     PlayIcsDrawSound();
11643                     break;
11644                 default:
11645                     PlayIcsUnfinishedSound();
11646                 }
11647             }
11648             if(appData.quitNext) { ExitEvent(0); return; }
11649         } else if (gameMode == EditGame ||
11650                    gameMode == PlayFromGameFile ||
11651                    gameMode == AnalyzeMode ||
11652                    gameMode == AnalyzeFile) {
11653             nextGameMode = gameMode;
11654         } else {
11655             nextGameMode = EndOfGame;
11656         }
11657         pausing = FALSE;
11658         ModeHighlight();
11659     } else {
11660         nextGameMode = gameMode;
11661     }
11662
11663     if (appData.noChessProgram) {
11664         gameMode = nextGameMode;
11665         ModeHighlight();
11666         endingGame = 0; /* [HGM] crash */
11667         return;
11668     }
11669
11670     if (first.reuse) {
11671         /* Put first chess program into idle state */
11672         if (first.pr != NoProc &&
11673             (gameMode == MachinePlaysWhite ||
11674              gameMode == MachinePlaysBlack ||
11675              gameMode == TwoMachinesPlay ||
11676              gameMode == IcsPlayingWhite ||
11677              gameMode == IcsPlayingBlack ||
11678              gameMode == BeginningOfGame)) {
11679             SendToProgram("force\n", &first);
11680             if (first.usePing) {
11681               char buf[MSG_SIZ];
11682               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11683               SendToProgram(buf, &first);
11684             }
11685         }
11686     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11687         /* Kill off first chess program */
11688         if (first.isr != NULL)
11689           RemoveInputSource(first.isr);
11690         first.isr = NULL;
11691
11692         if (first.pr != NoProc) {
11693             ExitAnalyzeMode();
11694             DoSleep( appData.delayBeforeQuit );
11695             SendToProgram("quit\n", &first);
11696             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11697             first.reload = TRUE;
11698         }
11699         first.pr = NoProc;
11700     }
11701     if (second.reuse) {
11702         /* Put second chess program into idle state */
11703         if (second.pr != NoProc &&
11704             gameMode == TwoMachinesPlay) {
11705             SendToProgram("force\n", &second);
11706             if (second.usePing) {
11707               char buf[MSG_SIZ];
11708               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11709               SendToProgram(buf, &second);
11710             }
11711         }
11712     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11713         /* Kill off second chess program */
11714         if (second.isr != NULL)
11715           RemoveInputSource(second.isr);
11716         second.isr = NULL;
11717
11718         if (second.pr != NoProc) {
11719             DoSleep( appData.delayBeforeQuit );
11720             SendToProgram("quit\n", &second);
11721             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11722             second.reload = TRUE;
11723         }
11724         second.pr = NoProc;
11725     }
11726
11727     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11728         char resChar = '=';
11729         switch (result) {
11730         case WhiteWins:
11731           resChar = '+';
11732           if (first.twoMachinesColor[0] == 'w') {
11733             first.matchWins++;
11734           } else {
11735             second.matchWins++;
11736           }
11737           break;
11738         case BlackWins:
11739           resChar = '-';
11740           if (first.twoMachinesColor[0] == 'b') {
11741             first.matchWins++;
11742           } else {
11743             second.matchWins++;
11744           }
11745           break;
11746         case GameUnfinished:
11747           resChar = ' ';
11748         default:
11749           break;
11750         }
11751
11752         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11753         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11754             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11755             ReserveGame(nextGame, resChar); // sets nextGame
11756             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11757             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11758         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11759
11760         if (nextGame <= appData.matchGames && !abortMatch) {
11761             gameMode = nextGameMode;
11762             matchGame = nextGame; // this will be overruled in tourney mode!
11763             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11764             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11765             endingGame = 0; /* [HGM] crash */
11766             return;
11767         } else {
11768             gameMode = nextGameMode;
11769             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11770                      first.tidy, second.tidy,
11771                      first.matchWins, second.matchWins,
11772                      appData.matchGames - (first.matchWins + second.matchWins));
11773             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11774             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11775             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11776             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11777                 first.twoMachinesColor = "black\n";
11778                 second.twoMachinesColor = "white\n";
11779             } else {
11780                 first.twoMachinesColor = "white\n";
11781                 second.twoMachinesColor = "black\n";
11782             }
11783         }
11784     }
11785     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11786         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11787       ExitAnalyzeMode();
11788     gameMode = nextGameMode;
11789     ModeHighlight();
11790     endingGame = 0;  /* [HGM] crash */
11791     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11792         if(matchMode == TRUE) { // match through command line: exit with or without popup
11793             if(ranking) {
11794                 ToNrEvent(forwardMostMove);
11795                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11796                 else ExitEvent(0);
11797             } else DisplayFatalError(buf, 0, 0);
11798         } else { // match through menu; just stop, with or without popup
11799             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11800             ModeHighlight();
11801             if(ranking){
11802                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11803             } else DisplayNote(buf);
11804       }
11805       if(ranking) free(ranking);
11806     }
11807 }
11808
11809 /* Assumes program was just initialized (initString sent).
11810    Leaves program in force mode. */
11811 void
11812 FeedMovesToProgram (ChessProgramState *cps, int upto)
11813 {
11814     int i;
11815
11816     if (appData.debugMode)
11817       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11818               startedFromSetupPosition ? "position and " : "",
11819               backwardMostMove, upto, cps->which);
11820     if(currentlyInitializedVariant != gameInfo.variant) {
11821       char buf[MSG_SIZ];
11822         // [HGM] variantswitch: make engine aware of new variant
11823         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11824                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11825                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11826         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11827         SendToProgram(buf, cps);
11828         currentlyInitializedVariant = gameInfo.variant;
11829     }
11830     SendToProgram("force\n", cps);
11831     if (startedFromSetupPosition) {
11832         SendBoard(cps, backwardMostMove);
11833     if (appData.debugMode) {
11834         fprintf(debugFP, "feedMoves\n");
11835     }
11836     }
11837     for (i = backwardMostMove; i < upto; i++) {
11838         SendMoveToProgram(i, cps);
11839     }
11840 }
11841
11842
11843 int
11844 ResurrectChessProgram ()
11845 {
11846      /* The chess program may have exited.
11847         If so, restart it and feed it all the moves made so far. */
11848     static int doInit = 0;
11849
11850     if (appData.noChessProgram) return 1;
11851
11852     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11853         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11854         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11855         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11856     } else {
11857         if (first.pr != NoProc) return 1;
11858         StartChessProgram(&first);
11859     }
11860     InitChessProgram(&first, FALSE);
11861     FeedMovesToProgram(&first, currentMove);
11862
11863     if (!first.sendTime) {
11864         /* can't tell gnuchess what its clock should read,
11865            so we bow to its notion. */
11866         ResetClocks();
11867         timeRemaining[0][currentMove] = whiteTimeRemaining;
11868         timeRemaining[1][currentMove] = blackTimeRemaining;
11869     }
11870
11871     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11872                 appData.icsEngineAnalyze) && first.analysisSupport) {
11873       SendToProgram("analyze\n", &first);
11874       first.analyzing = TRUE;
11875     }
11876     return 1;
11877 }
11878
11879 /*
11880  * Button procedures
11881  */
11882 void
11883 Reset (int redraw, int init)
11884 {
11885     int i;
11886
11887     if (appData.debugMode) {
11888         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11889                 redraw, init, gameMode);
11890     }
11891     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11892     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11893     CleanupTail(); // [HGM] vari: delete any stored variations
11894     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11895     pausing = pauseExamInvalid = FALSE;
11896     startedFromSetupPosition = blackPlaysFirst = FALSE;
11897     firstMove = TRUE;
11898     whiteFlag = blackFlag = FALSE;
11899     userOfferedDraw = FALSE;
11900     hintRequested = bookRequested = FALSE;
11901     first.maybeThinking = FALSE;
11902     second.maybeThinking = FALSE;
11903     first.bookSuspend = FALSE; // [HGM] book
11904     second.bookSuspend = FALSE;
11905     thinkOutput[0] = NULLCHAR;
11906     lastHint[0] = NULLCHAR;
11907     ClearGameInfo(&gameInfo);
11908     gameInfo.variant = StringToVariant(appData.variant);
11909     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11910     ics_user_moved = ics_clock_paused = FALSE;
11911     ics_getting_history = H_FALSE;
11912     ics_gamenum = -1;
11913     white_holding[0] = black_holding[0] = NULLCHAR;
11914     ClearProgramStats();
11915     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11916
11917     ResetFrontEnd();
11918     ClearHighlights();
11919     flipView = appData.flipView;
11920     ClearPremoveHighlights();
11921     gotPremove = FALSE;
11922     alarmSounded = FALSE;
11923     killX = killY = -1; // [HGM] lion
11924
11925     GameEnds(EndOfFile, NULL, GE_PLAYER);
11926     if(appData.serverMovesName != NULL) {
11927         /* [HGM] prepare to make moves file for broadcasting */
11928         clock_t t = clock();
11929         if(serverMoves != NULL) fclose(serverMoves);
11930         serverMoves = fopen(appData.serverMovesName, "r");
11931         if(serverMoves != NULL) {
11932             fclose(serverMoves);
11933             /* delay 15 sec before overwriting, so all clients can see end */
11934             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11935         }
11936         serverMoves = fopen(appData.serverMovesName, "w");
11937     }
11938
11939     ExitAnalyzeMode();
11940     gameMode = BeginningOfGame;
11941     ModeHighlight();
11942     if(appData.icsActive) gameInfo.variant = VariantNormal;
11943     currentMove = forwardMostMove = backwardMostMove = 0;
11944     MarkTargetSquares(1);
11945     InitPosition(redraw);
11946     for (i = 0; i < MAX_MOVES; i++) {
11947         if (commentList[i] != NULL) {
11948             free(commentList[i]);
11949             commentList[i] = NULL;
11950         }
11951     }
11952     ResetClocks();
11953     timeRemaining[0][0] = whiteTimeRemaining;
11954     timeRemaining[1][0] = blackTimeRemaining;
11955
11956     if (first.pr == NoProc) {
11957         StartChessProgram(&first);
11958     }
11959     if (init) {
11960             InitChessProgram(&first, startedFromSetupPosition);
11961     }
11962     DisplayTitle("");
11963     DisplayMessage("", "");
11964     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11965     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11966     ClearMap();        // [HGM] exclude: invalidate map
11967 }
11968
11969 void
11970 AutoPlayGameLoop ()
11971 {
11972     for (;;) {
11973         if (!AutoPlayOneMove())
11974           return;
11975         if (matchMode || appData.timeDelay == 0)
11976           continue;
11977         if (appData.timeDelay < 0)
11978           return;
11979         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11980         break;
11981     }
11982 }
11983
11984 void
11985 AnalyzeNextGame()
11986 {
11987     ReloadGame(1); // next game
11988 }
11989
11990 int
11991 AutoPlayOneMove ()
11992 {
11993     int fromX, fromY, toX, toY;
11994
11995     if (appData.debugMode) {
11996       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11997     }
11998
11999     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12000       return FALSE;
12001
12002     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12003       pvInfoList[currentMove].depth = programStats.depth;
12004       pvInfoList[currentMove].score = programStats.score;
12005       pvInfoList[currentMove].time  = 0;
12006       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12007       else { // append analysis of final position as comment
12008         char buf[MSG_SIZ];
12009         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12010         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12011       }
12012       programStats.depth = 0;
12013     }
12014
12015     if (currentMove >= forwardMostMove) {
12016       if(gameMode == AnalyzeFile) {
12017           if(appData.loadGameIndex == -1) {
12018             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12019           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12020           } else {
12021           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12022         }
12023       }
12024 //      gameMode = EndOfGame;
12025 //      ModeHighlight();
12026
12027       /* [AS] Clear current move marker at the end of a game */
12028       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12029
12030       return FALSE;
12031     }
12032
12033     toX = moveList[currentMove][2] - AAA;
12034     toY = moveList[currentMove][3] - ONE;
12035
12036     if (moveList[currentMove][1] == '@') {
12037         if (appData.highlightLastMove) {
12038             SetHighlights(-1, -1, toX, toY);
12039         }
12040     } else {
12041         int viaX = moveList[currentMove][5] - AAA;
12042         int viaY = moveList[currentMove][6] - ONE;
12043         fromX = moveList[currentMove][0] - AAA;
12044         fromY = moveList[currentMove][1] - ONE;
12045
12046         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12047
12048         if(moveList[currentMove][4] == ';') { // multi-leg
12049             ChessSquare piece = boards[currentMove][viaY][viaX];
12050             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12051             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12052             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12053             boards[currentMove][viaY][viaX] = piece;
12054         } else
12055         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12056
12057         if (appData.highlightLastMove) {
12058             SetHighlights(fromX, fromY, toX, toY);
12059         }
12060     }
12061     DisplayMove(currentMove);
12062     SendMoveToProgram(currentMove++, &first);
12063     DisplayBothClocks();
12064     DrawPosition(FALSE, boards[currentMove]);
12065     // [HGM] PV info: always display, routine tests if empty
12066     DisplayComment(currentMove - 1, commentList[currentMove]);
12067     return TRUE;
12068 }
12069
12070
12071 int
12072 LoadGameOneMove (ChessMove readAhead)
12073 {
12074     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12075     char promoChar = NULLCHAR;
12076     ChessMove moveType;
12077     char move[MSG_SIZ];
12078     char *p, *q;
12079
12080     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12081         gameMode != AnalyzeMode && gameMode != Training) {
12082         gameFileFP = NULL;
12083         return FALSE;
12084     }
12085
12086     yyboardindex = forwardMostMove;
12087     if (readAhead != EndOfFile) {
12088       moveType = readAhead;
12089     } else {
12090       if (gameFileFP == NULL)
12091           return FALSE;
12092       moveType = (ChessMove) Myylex();
12093     }
12094
12095     done = FALSE;
12096     switch (moveType) {
12097       case Comment:
12098         if (appData.debugMode)
12099           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12100         p = yy_text;
12101
12102         /* append the comment but don't display it */
12103         AppendComment(currentMove, p, FALSE);
12104         return TRUE;
12105
12106       case WhiteCapturesEnPassant:
12107       case BlackCapturesEnPassant:
12108       case WhitePromotion:
12109       case BlackPromotion:
12110       case WhiteNonPromotion:
12111       case BlackNonPromotion:
12112       case NormalMove:
12113       case FirstLeg:
12114       case WhiteKingSideCastle:
12115       case WhiteQueenSideCastle:
12116       case BlackKingSideCastle:
12117       case BlackQueenSideCastle:
12118       case WhiteKingSideCastleWild:
12119       case WhiteQueenSideCastleWild:
12120       case BlackKingSideCastleWild:
12121       case BlackQueenSideCastleWild:
12122       /* PUSH Fabien */
12123       case WhiteHSideCastleFR:
12124       case WhiteASideCastleFR:
12125       case BlackHSideCastleFR:
12126       case BlackASideCastleFR:
12127       /* POP Fabien */
12128         if (appData.debugMode)
12129           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12130         fromX = currentMoveString[0] - AAA;
12131         fromY = currentMoveString[1] - ONE;
12132         toX = currentMoveString[2] - AAA;
12133         toY = currentMoveString[3] - ONE;
12134         promoChar = currentMoveString[4];
12135         if(promoChar == ';') promoChar = NULLCHAR;
12136         break;
12137
12138       case WhiteDrop:
12139       case BlackDrop:
12140         if (appData.debugMode)
12141           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12142         fromX = moveType == WhiteDrop ?
12143           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12144         (int) CharToPiece(ToLower(currentMoveString[0]));
12145         fromY = DROP_RANK;
12146         toX = currentMoveString[2] - AAA;
12147         toY = currentMoveString[3] - ONE;
12148         break;
12149
12150       case WhiteWins:
12151       case BlackWins:
12152       case GameIsDrawn:
12153       case GameUnfinished:
12154         if (appData.debugMode)
12155           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12156         p = strchr(yy_text, '{');
12157         if (p == NULL) p = strchr(yy_text, '(');
12158         if (p == NULL) {
12159             p = yy_text;
12160             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12161         } else {
12162             q = strchr(p, *p == '{' ? '}' : ')');
12163             if (q != NULL) *q = NULLCHAR;
12164             p++;
12165         }
12166         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12167         GameEnds(moveType, p, GE_FILE);
12168         done = TRUE;
12169         if (cmailMsgLoaded) {
12170             ClearHighlights();
12171             flipView = WhiteOnMove(currentMove);
12172             if (moveType == GameUnfinished) flipView = !flipView;
12173             if (appData.debugMode)
12174               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12175         }
12176         break;
12177
12178       case EndOfFile:
12179         if (appData.debugMode)
12180           fprintf(debugFP, "Parser hit end of file\n");
12181         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12182           case MT_NONE:
12183           case MT_CHECK:
12184             break;
12185           case MT_CHECKMATE:
12186           case MT_STAINMATE:
12187             if (WhiteOnMove(currentMove)) {
12188                 GameEnds(BlackWins, "Black mates", GE_FILE);
12189             } else {
12190                 GameEnds(WhiteWins, "White mates", GE_FILE);
12191             }
12192             break;
12193           case MT_STALEMATE:
12194             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12195             break;
12196         }
12197         done = TRUE;
12198         break;
12199
12200       case MoveNumberOne:
12201         if (lastLoadGameStart == GNUChessGame) {
12202             /* GNUChessGames have numbers, but they aren't move numbers */
12203             if (appData.debugMode)
12204               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12205                       yy_text, (int) moveType);
12206             return LoadGameOneMove(EndOfFile); /* tail recursion */
12207         }
12208         /* else fall thru */
12209
12210       case XBoardGame:
12211       case GNUChessGame:
12212       case PGNTag:
12213         /* Reached start of next game in file */
12214         if (appData.debugMode)
12215           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12216         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12217           case MT_NONE:
12218           case MT_CHECK:
12219             break;
12220           case MT_CHECKMATE:
12221           case MT_STAINMATE:
12222             if (WhiteOnMove(currentMove)) {
12223                 GameEnds(BlackWins, "Black mates", GE_FILE);
12224             } else {
12225                 GameEnds(WhiteWins, "White mates", GE_FILE);
12226             }
12227             break;
12228           case MT_STALEMATE:
12229             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12230             break;
12231         }
12232         done = TRUE;
12233         break;
12234
12235       case PositionDiagram:     /* should not happen; ignore */
12236       case ElapsedTime:         /* ignore */
12237       case NAG:                 /* ignore */
12238         if (appData.debugMode)
12239           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12240                   yy_text, (int) moveType);
12241         return LoadGameOneMove(EndOfFile); /* tail recursion */
12242
12243       case IllegalMove:
12244         if (appData.testLegality) {
12245             if (appData.debugMode)
12246               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12247             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12248                     (forwardMostMove / 2) + 1,
12249                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12250             DisplayError(move, 0);
12251             done = TRUE;
12252         } else {
12253             if (appData.debugMode)
12254               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12255                       yy_text, currentMoveString);
12256             if(currentMoveString[1] == '@') {
12257                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12258                 fromY = DROP_RANK;
12259             } else {
12260                 fromX = currentMoveString[0] - AAA;
12261                 fromY = currentMoveString[1] - ONE;
12262             }
12263             toX = currentMoveString[2] - AAA;
12264             toY = currentMoveString[3] - ONE;
12265             promoChar = currentMoveString[4];
12266         }
12267         break;
12268
12269       case AmbiguousMove:
12270         if (appData.debugMode)
12271           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12272         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12273                 (forwardMostMove / 2) + 1,
12274                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12275         DisplayError(move, 0);
12276         done = TRUE;
12277         break;
12278
12279       default:
12280       case ImpossibleMove:
12281         if (appData.debugMode)
12282           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12283         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12284                 (forwardMostMove / 2) + 1,
12285                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12286         DisplayError(move, 0);
12287         done = TRUE;
12288         break;
12289     }
12290
12291     if (done) {
12292         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12293             DrawPosition(FALSE, boards[currentMove]);
12294             DisplayBothClocks();
12295             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12296               DisplayComment(currentMove - 1, commentList[currentMove]);
12297         }
12298         (void) StopLoadGameTimer();
12299         gameFileFP = NULL;
12300         cmailOldMove = forwardMostMove;
12301         return FALSE;
12302     } else {
12303         /* currentMoveString is set as a side-effect of yylex */
12304
12305         thinkOutput[0] = NULLCHAR;
12306         MakeMove(fromX, fromY, toX, toY, promoChar);
12307         killX = killY = -1; // [HGM] lion: used up
12308         currentMove = forwardMostMove;
12309         return TRUE;
12310     }
12311 }
12312
12313 /* Load the nth game from the given file */
12314 int
12315 LoadGameFromFile (char *filename, int n, char *title, int useList)
12316 {
12317     FILE *f;
12318     char buf[MSG_SIZ];
12319
12320     if (strcmp(filename, "-") == 0) {
12321         f = stdin;
12322         title = "stdin";
12323     } else {
12324         f = fopen(filename, "rb");
12325         if (f == NULL) {
12326           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12327             DisplayError(buf, errno);
12328             return FALSE;
12329         }
12330     }
12331     if (fseek(f, 0, 0) == -1) {
12332         /* f is not seekable; probably a pipe */
12333         useList = FALSE;
12334     }
12335     if (useList && n == 0) {
12336         int error = GameListBuild(f);
12337         if (error) {
12338             DisplayError(_("Cannot build game list"), error);
12339         } else if (!ListEmpty(&gameList) &&
12340                    ((ListGame *) gameList.tailPred)->number > 1) {
12341             GameListPopUp(f, title);
12342             return TRUE;
12343         }
12344         GameListDestroy();
12345         n = 1;
12346     }
12347     if (n == 0) n = 1;
12348     return LoadGame(f, n, title, FALSE);
12349 }
12350
12351
12352 void
12353 MakeRegisteredMove ()
12354 {
12355     int fromX, fromY, toX, toY;
12356     char promoChar;
12357     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12358         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12359           case CMAIL_MOVE:
12360           case CMAIL_DRAW:
12361             if (appData.debugMode)
12362               fprintf(debugFP, "Restoring %s for game %d\n",
12363                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12364
12365             thinkOutput[0] = NULLCHAR;
12366             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12367             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12368             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12369             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12370             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12371             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12372             MakeMove(fromX, fromY, toX, toY, promoChar);
12373             ShowMove(fromX, fromY, toX, toY);
12374
12375             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12376               case MT_NONE:
12377               case MT_CHECK:
12378                 break;
12379
12380               case MT_CHECKMATE:
12381               case MT_STAINMATE:
12382                 if (WhiteOnMove(currentMove)) {
12383                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12384                 } else {
12385                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12386                 }
12387                 break;
12388
12389               case MT_STALEMATE:
12390                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12391                 break;
12392             }
12393
12394             break;
12395
12396           case CMAIL_RESIGN:
12397             if (WhiteOnMove(currentMove)) {
12398                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12399             } else {
12400                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12401             }
12402             break;
12403
12404           case CMAIL_ACCEPT:
12405             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12406             break;
12407
12408           default:
12409             break;
12410         }
12411     }
12412
12413     return;
12414 }
12415
12416 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12417 int
12418 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12419 {
12420     int retVal;
12421
12422     if (gameNumber > nCmailGames) {
12423         DisplayError(_("No more games in this message"), 0);
12424         return FALSE;
12425     }
12426     if (f == lastLoadGameFP) {
12427         int offset = gameNumber - lastLoadGameNumber;
12428         if (offset == 0) {
12429             cmailMsg[0] = NULLCHAR;
12430             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12431                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12432                 nCmailMovesRegistered--;
12433             }
12434             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12435             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12436                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12437             }
12438         } else {
12439             if (! RegisterMove()) return FALSE;
12440         }
12441     }
12442
12443     retVal = LoadGame(f, gameNumber, title, useList);
12444
12445     /* Make move registered during previous look at this game, if any */
12446     MakeRegisteredMove();
12447
12448     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12449         commentList[currentMove]
12450           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12451         DisplayComment(currentMove - 1, commentList[currentMove]);
12452     }
12453
12454     return retVal;
12455 }
12456
12457 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12458 int
12459 ReloadGame (int offset)
12460 {
12461     int gameNumber = lastLoadGameNumber + offset;
12462     if (lastLoadGameFP == NULL) {
12463         DisplayError(_("No game has been loaded yet"), 0);
12464         return FALSE;
12465     }
12466     if (gameNumber <= 0) {
12467         DisplayError(_("Can't back up any further"), 0);
12468         return FALSE;
12469     }
12470     if (cmailMsgLoaded) {
12471         return CmailLoadGame(lastLoadGameFP, gameNumber,
12472                              lastLoadGameTitle, lastLoadGameUseList);
12473     } else {
12474         return LoadGame(lastLoadGameFP, gameNumber,
12475                         lastLoadGameTitle, lastLoadGameUseList);
12476     }
12477 }
12478
12479 int keys[EmptySquare+1];
12480
12481 int
12482 PositionMatches (Board b1, Board b2)
12483 {
12484     int r, f, sum=0;
12485     switch(appData.searchMode) {
12486         case 1: return CompareWithRights(b1, b2);
12487         case 2:
12488             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12489                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12490             }
12491             return TRUE;
12492         case 3:
12493             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12494               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12495                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12496             }
12497             return sum==0;
12498         case 4:
12499             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12500                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12501             }
12502             return sum==0;
12503     }
12504     return TRUE;
12505 }
12506
12507 #define Q_PROMO  4
12508 #define Q_EP     3
12509 #define Q_BCASTL 2
12510 #define Q_WCASTL 1
12511
12512 int pieceList[256], quickBoard[256];
12513 ChessSquare pieceType[256] = { EmptySquare };
12514 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12515 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12516 int soughtTotal, turn;
12517 Boolean epOK, flipSearch;
12518
12519 typedef struct {
12520     unsigned char piece, to;
12521 } Move;
12522
12523 #define DSIZE (250000)
12524
12525 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12526 Move *moveDatabase = initialSpace;
12527 unsigned int movePtr, dataSize = DSIZE;
12528
12529 int
12530 MakePieceList (Board board, int *counts)
12531 {
12532     int r, f, n=Q_PROMO, total=0;
12533     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12534     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12535         int sq = f + (r<<4);
12536         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12537             quickBoard[sq] = ++n;
12538             pieceList[n] = sq;
12539             pieceType[n] = board[r][f];
12540             counts[board[r][f]]++;
12541             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12542             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12543             total++;
12544         }
12545     }
12546     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12547     return total;
12548 }
12549
12550 void
12551 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12552 {
12553     int sq = fromX + (fromY<<4);
12554     int piece = quickBoard[sq], rook;
12555     quickBoard[sq] = 0;
12556     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12557     if(piece == pieceList[1] && fromY == toY) {
12558       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12559         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12560         moveDatabase[movePtr++].piece = Q_WCASTL;
12561         quickBoard[sq] = piece;
12562         piece = quickBoard[from]; quickBoard[from] = 0;
12563         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12564       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12565         quickBoard[sq] = 0; // remove Rook
12566         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12567         moveDatabase[movePtr++].piece = Q_WCASTL;
12568         quickBoard[sq] = pieceList[1]; // put King
12569         piece = rook;
12570         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12571       }
12572     } else
12573     if(piece == pieceList[2] && fromY == toY) {
12574       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12575         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12576         moveDatabase[movePtr++].piece = Q_BCASTL;
12577         quickBoard[sq] = piece;
12578         piece = quickBoard[from]; quickBoard[from] = 0;
12579         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12580       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12581         quickBoard[sq] = 0; // remove Rook
12582         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12583         moveDatabase[movePtr++].piece = Q_BCASTL;
12584         quickBoard[sq] = pieceList[2]; // put King
12585         piece = rook;
12586         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12587       }
12588     } else
12589     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12590         quickBoard[(fromY<<4)+toX] = 0;
12591         moveDatabase[movePtr].piece = Q_EP;
12592         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12593         moveDatabase[movePtr].to = sq;
12594     } else
12595     if(promoPiece != pieceType[piece]) {
12596         moveDatabase[movePtr++].piece = Q_PROMO;
12597         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12598     }
12599     moveDatabase[movePtr].piece = piece;
12600     quickBoard[sq] = piece;
12601     movePtr++;
12602 }
12603
12604 int
12605 PackGame (Board board)
12606 {
12607     Move *newSpace = NULL;
12608     moveDatabase[movePtr].piece = 0; // terminate previous game
12609     if(movePtr > dataSize) {
12610         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12611         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12612         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12613         if(newSpace) {
12614             int i;
12615             Move *p = moveDatabase, *q = newSpace;
12616             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12617             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12618             moveDatabase = newSpace;
12619         } else { // calloc failed, we must be out of memory. Too bad...
12620             dataSize = 0; // prevent calloc events for all subsequent games
12621             return 0;     // and signal this one isn't cached
12622         }
12623     }
12624     movePtr++;
12625     MakePieceList(board, counts);
12626     return movePtr;
12627 }
12628
12629 int
12630 QuickCompare (Board board, int *minCounts, int *maxCounts)
12631 {   // compare according to search mode
12632     int r, f;
12633     switch(appData.searchMode)
12634     {
12635       case 1: // exact position match
12636         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12637         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12638             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12639         }
12640         break;
12641       case 2: // can have extra material on empty squares
12642         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12643             if(board[r][f] == EmptySquare) continue;
12644             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12645         }
12646         break;
12647       case 3: // material with exact Pawn structure
12648         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12649             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12650             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12651         } // fall through to material comparison
12652       case 4: // exact material
12653         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12654         break;
12655       case 6: // material range with given imbalance
12656         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12657         // fall through to range comparison
12658       case 5: // material range
12659         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12660     }
12661     return TRUE;
12662 }
12663
12664 int
12665 QuickScan (Board board, Move *move)
12666 {   // reconstruct game,and compare all positions in it
12667     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12668     do {
12669         int piece = move->piece;
12670         int to = move->to, from = pieceList[piece];
12671         if(found < 0) { // if already found just scan to game end for final piece count
12672           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12673            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12674            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12675                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12676             ) {
12677             static int lastCounts[EmptySquare+1];
12678             int i;
12679             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12680             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12681           } else stretch = 0;
12682           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12683           if(found >= 0 && !appData.minPieces) return found;
12684         }
12685         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12686           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12687           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12688             piece = (++move)->piece;
12689             from = pieceList[piece];
12690             counts[pieceType[piece]]--;
12691             pieceType[piece] = (ChessSquare) move->to;
12692             counts[move->to]++;
12693           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12694             counts[pieceType[quickBoard[to]]]--;
12695             quickBoard[to] = 0; total--;
12696             move++;
12697             continue;
12698           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12699             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12700             from  = pieceList[piece]; // so this must be King
12701             quickBoard[from] = 0;
12702             pieceList[piece] = to;
12703             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12704             quickBoard[from] = 0; // rook
12705             quickBoard[to] = piece;
12706             to = move->to; piece = move->piece;
12707             goto aftercastle;
12708           }
12709         }
12710         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12711         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12712         quickBoard[from] = 0;
12713       aftercastle:
12714         quickBoard[to] = piece;
12715         pieceList[piece] = to;
12716         cnt++; turn ^= 3;
12717         move++;
12718     } while(1);
12719 }
12720
12721 void
12722 InitSearch ()
12723 {
12724     int r, f;
12725     flipSearch = FALSE;
12726     CopyBoard(soughtBoard, boards[currentMove]);
12727     soughtTotal = MakePieceList(soughtBoard, maxSought);
12728     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12729     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12730     CopyBoard(reverseBoard, boards[currentMove]);
12731     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12732         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12733         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12734         reverseBoard[r][f] = piece;
12735     }
12736     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12737     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12738     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12739                  || (boards[currentMove][CASTLING][2] == NoRights ||
12740                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12741                  && (boards[currentMove][CASTLING][5] == NoRights ||
12742                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12743       ) {
12744         flipSearch = TRUE;
12745         CopyBoard(flipBoard, soughtBoard);
12746         CopyBoard(rotateBoard, reverseBoard);
12747         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12748             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12749             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12750         }
12751     }
12752     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12753     if(appData.searchMode >= 5) {
12754         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12755         MakePieceList(soughtBoard, minSought);
12756         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12757     }
12758     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12759         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12760 }
12761
12762 GameInfo dummyInfo;
12763 static int creatingBook;
12764
12765 int
12766 GameContainsPosition (FILE *f, ListGame *lg)
12767 {
12768     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12769     int fromX, fromY, toX, toY;
12770     char promoChar;
12771     static int initDone=FALSE;
12772
12773     // weed out games based on numerical tag comparison
12774     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12775     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12776     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12777     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12778     if(!initDone) {
12779         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12780         initDone = TRUE;
12781     }
12782     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12783     else CopyBoard(boards[scratch], initialPosition); // default start position
12784     if(lg->moves) {
12785         turn = btm + 1;
12786         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12787         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12788     }
12789     if(btm) plyNr++;
12790     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12791     fseek(f, lg->offset, 0);
12792     yynewfile(f);
12793     while(1) {
12794         yyboardindex = scratch;
12795         quickFlag = plyNr+1;
12796         next = Myylex();
12797         quickFlag = 0;
12798         switch(next) {
12799             case PGNTag:
12800                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12801             default:
12802                 continue;
12803
12804             case XBoardGame:
12805             case GNUChessGame:
12806                 if(plyNr) return -1; // after we have seen moves, this is for new game
12807               continue;
12808
12809             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12810             case ImpossibleMove:
12811             case WhiteWins: // game ends here with these four
12812             case BlackWins:
12813             case GameIsDrawn:
12814             case GameUnfinished:
12815                 return -1;
12816
12817             case IllegalMove:
12818                 if(appData.testLegality) return -1;
12819             case WhiteCapturesEnPassant:
12820             case BlackCapturesEnPassant:
12821             case WhitePromotion:
12822             case BlackPromotion:
12823             case WhiteNonPromotion:
12824             case BlackNonPromotion:
12825             case NormalMove:
12826             case FirstLeg:
12827             case WhiteKingSideCastle:
12828             case WhiteQueenSideCastle:
12829             case BlackKingSideCastle:
12830             case BlackQueenSideCastle:
12831             case WhiteKingSideCastleWild:
12832             case WhiteQueenSideCastleWild:
12833             case BlackKingSideCastleWild:
12834             case BlackQueenSideCastleWild:
12835             case WhiteHSideCastleFR:
12836             case WhiteASideCastleFR:
12837             case BlackHSideCastleFR:
12838             case BlackASideCastleFR:
12839                 fromX = currentMoveString[0] - AAA;
12840                 fromY = currentMoveString[1] - ONE;
12841                 toX = currentMoveString[2] - AAA;
12842                 toY = currentMoveString[3] - ONE;
12843                 promoChar = currentMoveString[4];
12844                 break;
12845             case WhiteDrop:
12846             case BlackDrop:
12847                 fromX = next == WhiteDrop ?
12848                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12849                   (int) CharToPiece(ToLower(currentMoveString[0]));
12850                 fromY = DROP_RANK;
12851                 toX = currentMoveString[2] - AAA;
12852                 toY = currentMoveString[3] - ONE;
12853                 promoChar = 0;
12854                 break;
12855         }
12856         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12857         plyNr++;
12858         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12859         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12860         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12861         if(appData.findMirror) {
12862             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12863             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12864         }
12865     }
12866 }
12867
12868 /* Load the nth game from open file f */
12869 int
12870 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12871 {
12872     ChessMove cm;
12873     char buf[MSG_SIZ];
12874     int gn = gameNumber;
12875     ListGame *lg = NULL;
12876     int numPGNTags = 0;
12877     int err, pos = -1;
12878     GameMode oldGameMode;
12879     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12880     char oldName[MSG_SIZ];
12881
12882     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12883
12884     if (appData.debugMode)
12885         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12886
12887     if (gameMode == Training )
12888         SetTrainingModeOff();
12889
12890     oldGameMode = gameMode;
12891     if (gameMode != BeginningOfGame) {
12892       Reset(FALSE, TRUE);
12893     }
12894     killX = killY = -1; // [HGM] lion: in case we did not Reset
12895
12896     gameFileFP = f;
12897     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12898         fclose(lastLoadGameFP);
12899     }
12900
12901     if (useList) {
12902         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12903
12904         if (lg) {
12905             fseek(f, lg->offset, 0);
12906             GameListHighlight(gameNumber);
12907             pos = lg->position;
12908             gn = 1;
12909         }
12910         else {
12911             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12912               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12913             else
12914             DisplayError(_("Game number out of range"), 0);
12915             return FALSE;
12916         }
12917     } else {
12918         GameListDestroy();
12919         if (fseek(f, 0, 0) == -1) {
12920             if (f == lastLoadGameFP ?
12921                 gameNumber == lastLoadGameNumber + 1 :
12922                 gameNumber == 1) {
12923                 gn = 1;
12924             } else {
12925                 DisplayError(_("Can't seek on game file"), 0);
12926                 return FALSE;
12927             }
12928         }
12929     }
12930     lastLoadGameFP = f;
12931     lastLoadGameNumber = gameNumber;
12932     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12933     lastLoadGameUseList = useList;
12934
12935     yynewfile(f);
12936
12937     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12938       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12939                 lg->gameInfo.black);
12940             DisplayTitle(buf);
12941     } else if (*title != NULLCHAR) {
12942         if (gameNumber > 1) {
12943           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12944             DisplayTitle(buf);
12945         } else {
12946             DisplayTitle(title);
12947         }
12948     }
12949
12950     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12951         gameMode = PlayFromGameFile;
12952         ModeHighlight();
12953     }
12954
12955     currentMove = forwardMostMove = backwardMostMove = 0;
12956     CopyBoard(boards[0], initialPosition);
12957     StopClocks();
12958
12959     /*
12960      * Skip the first gn-1 games in the file.
12961      * Also skip over anything that precedes an identifiable
12962      * start of game marker, to avoid being confused by
12963      * garbage at the start of the file.  Currently
12964      * recognized start of game markers are the move number "1",
12965      * the pattern "gnuchess .* game", the pattern
12966      * "^[#;%] [^ ]* game file", and a PGN tag block.
12967      * A game that starts with one of the latter two patterns
12968      * will also have a move number 1, possibly
12969      * following a position diagram.
12970      * 5-4-02: Let's try being more lenient and allowing a game to
12971      * start with an unnumbered move.  Does that break anything?
12972      */
12973     cm = lastLoadGameStart = EndOfFile;
12974     while (gn > 0) {
12975         yyboardindex = forwardMostMove;
12976         cm = (ChessMove) Myylex();
12977         switch (cm) {
12978           case EndOfFile:
12979             if (cmailMsgLoaded) {
12980                 nCmailGames = CMAIL_MAX_GAMES - gn;
12981             } else {
12982                 Reset(TRUE, TRUE);
12983                 DisplayError(_("Game not found in file"), 0);
12984             }
12985             return FALSE;
12986
12987           case GNUChessGame:
12988           case XBoardGame:
12989             gn--;
12990             lastLoadGameStart = cm;
12991             break;
12992
12993           case MoveNumberOne:
12994             switch (lastLoadGameStart) {
12995               case GNUChessGame:
12996               case XBoardGame:
12997               case PGNTag:
12998                 break;
12999               case MoveNumberOne:
13000               case EndOfFile:
13001                 gn--;           /* count this game */
13002                 lastLoadGameStart = cm;
13003                 break;
13004               default:
13005                 /* impossible */
13006                 break;
13007             }
13008             break;
13009
13010           case PGNTag:
13011             switch (lastLoadGameStart) {
13012               case GNUChessGame:
13013               case PGNTag:
13014               case MoveNumberOne:
13015               case EndOfFile:
13016                 gn--;           /* count this game */
13017                 lastLoadGameStart = cm;
13018                 break;
13019               case XBoardGame:
13020                 lastLoadGameStart = cm; /* game counted already */
13021                 break;
13022               default:
13023                 /* impossible */
13024                 break;
13025             }
13026             if (gn > 0) {
13027                 do {
13028                     yyboardindex = forwardMostMove;
13029                     cm = (ChessMove) Myylex();
13030                 } while (cm == PGNTag || cm == Comment);
13031             }
13032             break;
13033
13034           case WhiteWins:
13035           case BlackWins:
13036           case GameIsDrawn:
13037             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13038                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13039                     != CMAIL_OLD_RESULT) {
13040                     nCmailResults ++ ;
13041                     cmailResult[  CMAIL_MAX_GAMES
13042                                 - gn - 1] = CMAIL_OLD_RESULT;
13043                 }
13044             }
13045             break;
13046
13047           case NormalMove:
13048           case FirstLeg:
13049             /* Only a NormalMove can be at the start of a game
13050              * without a position diagram. */
13051             if (lastLoadGameStart == EndOfFile ) {
13052               gn--;
13053               lastLoadGameStart = MoveNumberOne;
13054             }
13055             break;
13056
13057           default:
13058             break;
13059         }
13060     }
13061
13062     if (appData.debugMode)
13063       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13064
13065     if (cm == XBoardGame) {
13066         /* Skip any header junk before position diagram and/or move 1 */
13067         for (;;) {
13068             yyboardindex = forwardMostMove;
13069             cm = (ChessMove) Myylex();
13070
13071             if (cm == EndOfFile ||
13072                 cm == GNUChessGame || cm == XBoardGame) {
13073                 /* Empty game; pretend end-of-file and handle later */
13074                 cm = EndOfFile;
13075                 break;
13076             }
13077
13078             if (cm == MoveNumberOne || cm == PositionDiagram ||
13079                 cm == PGNTag || cm == Comment)
13080               break;
13081         }
13082     } else if (cm == GNUChessGame) {
13083         if (gameInfo.event != NULL) {
13084             free(gameInfo.event);
13085         }
13086         gameInfo.event = StrSave(yy_text);
13087     }
13088
13089     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13090     while (cm == PGNTag) {
13091         if (appData.debugMode)
13092           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13093         err = ParsePGNTag(yy_text, &gameInfo);
13094         if (!err) numPGNTags++;
13095
13096         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13097         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13098             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13099             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13100             InitPosition(TRUE);
13101             oldVariant = gameInfo.variant;
13102             if (appData.debugMode)
13103               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13104         }
13105
13106
13107         if (gameInfo.fen != NULL) {
13108           Board initial_position;
13109           startedFromSetupPosition = TRUE;
13110           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13111             Reset(TRUE, TRUE);
13112             DisplayError(_("Bad FEN position in file"), 0);
13113             return FALSE;
13114           }
13115           CopyBoard(boards[0], initial_position);
13116           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13117             CopyBoard(initialPosition, initial_position);
13118           if (blackPlaysFirst) {
13119             currentMove = forwardMostMove = backwardMostMove = 1;
13120             CopyBoard(boards[1], initial_position);
13121             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13122             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13123             timeRemaining[0][1] = whiteTimeRemaining;
13124             timeRemaining[1][1] = blackTimeRemaining;
13125             if (commentList[0] != NULL) {
13126               commentList[1] = commentList[0];
13127               commentList[0] = NULL;
13128             }
13129           } else {
13130             currentMove = forwardMostMove = backwardMostMove = 0;
13131           }
13132           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13133           {   int i;
13134               initialRulePlies = FENrulePlies;
13135               for( i=0; i< nrCastlingRights; i++ )
13136                   initialRights[i] = initial_position[CASTLING][i];
13137           }
13138           yyboardindex = forwardMostMove;
13139           free(gameInfo.fen);
13140           gameInfo.fen = NULL;
13141         }
13142
13143         yyboardindex = forwardMostMove;
13144         cm = (ChessMove) Myylex();
13145
13146         /* Handle comments interspersed among the tags */
13147         while (cm == Comment) {
13148             char *p;
13149             if (appData.debugMode)
13150               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13151             p = yy_text;
13152             AppendComment(currentMove, p, FALSE);
13153             yyboardindex = forwardMostMove;
13154             cm = (ChessMove) Myylex();
13155         }
13156     }
13157
13158     /* don't rely on existence of Event tag since if game was
13159      * pasted from clipboard the Event tag may not exist
13160      */
13161     if (numPGNTags > 0){
13162         char *tags;
13163         if (gameInfo.variant == VariantNormal) {
13164           VariantClass v = StringToVariant(gameInfo.event);
13165           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13166           if(v < VariantShogi) gameInfo.variant = v;
13167         }
13168         if (!matchMode) {
13169           if( appData.autoDisplayTags ) {
13170             tags = PGNTags(&gameInfo);
13171             TagsPopUp(tags, CmailMsg());
13172             free(tags);
13173           }
13174         }
13175     } else {
13176         /* Make something up, but don't display it now */
13177         SetGameInfo();
13178         TagsPopDown();
13179     }
13180
13181     if (cm == PositionDiagram) {
13182         int i, j;
13183         char *p;
13184         Board initial_position;
13185
13186         if (appData.debugMode)
13187           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13188
13189         if (!startedFromSetupPosition) {
13190             p = yy_text;
13191             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13192               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13193                 switch (*p) {
13194                   case '{':
13195                   case '[':
13196                   case '-':
13197                   case ' ':
13198                   case '\t':
13199                   case '\n':
13200                   case '\r':
13201                     break;
13202                   default:
13203                     initial_position[i][j++] = CharToPiece(*p);
13204                     break;
13205                 }
13206             while (*p == ' ' || *p == '\t' ||
13207                    *p == '\n' || *p == '\r') p++;
13208
13209             if (strncmp(p, "black", strlen("black"))==0)
13210               blackPlaysFirst = TRUE;
13211             else
13212               blackPlaysFirst = FALSE;
13213             startedFromSetupPosition = TRUE;
13214
13215             CopyBoard(boards[0], initial_position);
13216             if (blackPlaysFirst) {
13217                 currentMove = forwardMostMove = backwardMostMove = 1;
13218                 CopyBoard(boards[1], initial_position);
13219                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13220                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13221                 timeRemaining[0][1] = whiteTimeRemaining;
13222                 timeRemaining[1][1] = blackTimeRemaining;
13223                 if (commentList[0] != NULL) {
13224                     commentList[1] = commentList[0];
13225                     commentList[0] = NULL;
13226                 }
13227             } else {
13228                 currentMove = forwardMostMove = backwardMostMove = 0;
13229             }
13230         }
13231         yyboardindex = forwardMostMove;
13232         cm = (ChessMove) Myylex();
13233     }
13234
13235   if(!creatingBook) {
13236     if (first.pr == NoProc) {
13237         StartChessProgram(&first);
13238     }
13239     InitChessProgram(&first, FALSE);
13240     if(gameInfo.variant == VariantUnknown && *oldName) {
13241         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13242         gameInfo.variant = v;
13243     }
13244     SendToProgram("force\n", &first);
13245     if (startedFromSetupPosition) {
13246         SendBoard(&first, forwardMostMove);
13247     if (appData.debugMode) {
13248         fprintf(debugFP, "Load Game\n");
13249     }
13250         DisplayBothClocks();
13251     }
13252   }
13253
13254     /* [HGM] server: flag to write setup moves in broadcast file as one */
13255     loadFlag = appData.suppressLoadMoves;
13256
13257     while (cm == Comment) {
13258         char *p;
13259         if (appData.debugMode)
13260           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13261         p = yy_text;
13262         AppendComment(currentMove, p, FALSE);
13263         yyboardindex = forwardMostMove;
13264         cm = (ChessMove) Myylex();
13265     }
13266
13267     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13268         cm == WhiteWins || cm == BlackWins ||
13269         cm == GameIsDrawn || cm == GameUnfinished) {
13270         DisplayMessage("", _("No moves in game"));
13271         if (cmailMsgLoaded) {
13272             if (appData.debugMode)
13273               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13274             ClearHighlights();
13275             flipView = FALSE;
13276         }
13277         DrawPosition(FALSE, boards[currentMove]);
13278         DisplayBothClocks();
13279         gameMode = EditGame;
13280         ModeHighlight();
13281         gameFileFP = NULL;
13282         cmailOldMove = 0;
13283         return TRUE;
13284     }
13285
13286     // [HGM] PV info: routine tests if comment empty
13287     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13288         DisplayComment(currentMove - 1, commentList[currentMove]);
13289     }
13290     if (!matchMode && appData.timeDelay != 0)
13291       DrawPosition(FALSE, boards[currentMove]);
13292
13293     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13294       programStats.ok_to_send = 1;
13295     }
13296
13297     /* if the first token after the PGN tags is a move
13298      * and not move number 1, retrieve it from the parser
13299      */
13300     if (cm != MoveNumberOne)
13301         LoadGameOneMove(cm);
13302
13303     /* load the remaining moves from the file */
13304     while (LoadGameOneMove(EndOfFile)) {
13305       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13306       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13307     }
13308
13309     /* rewind to the start of the game */
13310     currentMove = backwardMostMove;
13311
13312     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13313
13314     if (oldGameMode == AnalyzeFile) {
13315       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13316       AnalyzeFileEvent();
13317     } else
13318     if (oldGameMode == AnalyzeMode) {
13319       AnalyzeFileEvent();
13320     }
13321
13322     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13323         long int w, b; // [HGM] adjourn: restore saved clock times
13324         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13325         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13326             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13327             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13328         }
13329     }
13330
13331     if(creatingBook) return TRUE;
13332     if (!matchMode && pos > 0) {
13333         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13334     } else
13335     if (matchMode || appData.timeDelay == 0) {
13336       ToEndEvent();
13337     } else if (appData.timeDelay > 0) {
13338       AutoPlayGameLoop();
13339     }
13340
13341     if (appData.debugMode)
13342         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13343
13344     loadFlag = 0; /* [HGM] true game starts */
13345     return TRUE;
13346 }
13347
13348 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13349 int
13350 ReloadPosition (int offset)
13351 {
13352     int positionNumber = lastLoadPositionNumber + offset;
13353     if (lastLoadPositionFP == NULL) {
13354         DisplayError(_("No position has been loaded yet"), 0);
13355         return FALSE;
13356     }
13357     if (positionNumber <= 0) {
13358         DisplayError(_("Can't back up any further"), 0);
13359         return FALSE;
13360     }
13361     return LoadPosition(lastLoadPositionFP, positionNumber,
13362                         lastLoadPositionTitle);
13363 }
13364
13365 /* Load the nth position from the given file */
13366 int
13367 LoadPositionFromFile (char *filename, int n, char *title)
13368 {
13369     FILE *f;
13370     char buf[MSG_SIZ];
13371
13372     if (strcmp(filename, "-") == 0) {
13373         return LoadPosition(stdin, n, "stdin");
13374     } else {
13375         f = fopen(filename, "rb");
13376         if (f == NULL) {
13377             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13378             DisplayError(buf, errno);
13379             return FALSE;
13380         } else {
13381             return LoadPosition(f, n, title);
13382         }
13383     }
13384 }
13385
13386 /* Load the nth position from the given open file, and close it */
13387 int
13388 LoadPosition (FILE *f, int positionNumber, char *title)
13389 {
13390     char *p, line[MSG_SIZ];
13391     Board initial_position;
13392     int i, j, fenMode, pn;
13393
13394     if (gameMode == Training )
13395         SetTrainingModeOff();
13396
13397     if (gameMode != BeginningOfGame) {
13398         Reset(FALSE, TRUE);
13399     }
13400     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13401         fclose(lastLoadPositionFP);
13402     }
13403     if (positionNumber == 0) positionNumber = 1;
13404     lastLoadPositionFP = f;
13405     lastLoadPositionNumber = positionNumber;
13406     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13407     if (first.pr == NoProc && !appData.noChessProgram) {
13408       StartChessProgram(&first);
13409       InitChessProgram(&first, FALSE);
13410     }
13411     pn = positionNumber;
13412     if (positionNumber < 0) {
13413         /* Negative position number means to seek to that byte offset */
13414         if (fseek(f, -positionNumber, 0) == -1) {
13415             DisplayError(_("Can't seek on position file"), 0);
13416             return FALSE;
13417         };
13418         pn = 1;
13419     } else {
13420         if (fseek(f, 0, 0) == -1) {
13421             if (f == lastLoadPositionFP ?
13422                 positionNumber == lastLoadPositionNumber + 1 :
13423                 positionNumber == 1) {
13424                 pn = 1;
13425             } else {
13426                 DisplayError(_("Can't seek on position file"), 0);
13427                 return FALSE;
13428             }
13429         }
13430     }
13431     /* See if this file is FEN or old-style xboard */
13432     if (fgets(line, MSG_SIZ, f) == NULL) {
13433         DisplayError(_("Position not found in file"), 0);
13434         return FALSE;
13435     }
13436     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13437     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13438
13439     if (pn >= 2) {
13440         if (fenMode || line[0] == '#') pn--;
13441         while (pn > 0) {
13442             /* skip positions before number pn */
13443             if (fgets(line, MSG_SIZ, f) == NULL) {
13444                 Reset(TRUE, TRUE);
13445                 DisplayError(_("Position not found in file"), 0);
13446                 return FALSE;
13447             }
13448             if (fenMode || line[0] == '#') pn--;
13449         }
13450     }
13451
13452     if (fenMode) {
13453         char *p;
13454         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13455             DisplayError(_("Bad FEN position in file"), 0);
13456             return FALSE;
13457         }
13458         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13459             sscanf(p+3, "%s", bestMove);
13460         } else *bestMove = NULLCHAR;
13461     } else {
13462         (void) fgets(line, MSG_SIZ, f);
13463         (void) fgets(line, MSG_SIZ, f);
13464
13465         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13466             (void) fgets(line, MSG_SIZ, f);
13467             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13468                 if (*p == ' ')
13469                   continue;
13470                 initial_position[i][j++] = CharToPiece(*p);
13471             }
13472         }
13473
13474         blackPlaysFirst = FALSE;
13475         if (!feof(f)) {
13476             (void) fgets(line, MSG_SIZ, f);
13477             if (strncmp(line, "black", strlen("black"))==0)
13478               blackPlaysFirst = TRUE;
13479         }
13480     }
13481     startedFromSetupPosition = TRUE;
13482
13483     CopyBoard(boards[0], initial_position);
13484     if (blackPlaysFirst) {
13485         currentMove = forwardMostMove = backwardMostMove = 1;
13486         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13487         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13488         CopyBoard(boards[1], initial_position);
13489         DisplayMessage("", _("Black to play"));
13490     } else {
13491         currentMove = forwardMostMove = backwardMostMove = 0;
13492         DisplayMessage("", _("White to play"));
13493     }
13494     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13495     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13496         SendToProgram("force\n", &first);
13497         SendBoard(&first, forwardMostMove);
13498     }
13499     if (appData.debugMode) {
13500 int i, j;
13501   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13502   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13503         fprintf(debugFP, "Load Position\n");
13504     }
13505
13506     if (positionNumber > 1) {
13507       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13508         DisplayTitle(line);
13509     } else {
13510         DisplayTitle(title);
13511     }
13512     gameMode = EditGame;
13513     ModeHighlight();
13514     ResetClocks();
13515     timeRemaining[0][1] = whiteTimeRemaining;
13516     timeRemaining[1][1] = blackTimeRemaining;
13517     DrawPosition(FALSE, boards[currentMove]);
13518
13519     return TRUE;
13520 }
13521
13522
13523 void
13524 CopyPlayerNameIntoFileName (char **dest, char *src)
13525 {
13526     while (*src != NULLCHAR && *src != ',') {
13527         if (*src == ' ') {
13528             *(*dest)++ = '_';
13529             src++;
13530         } else {
13531             *(*dest)++ = *src++;
13532         }
13533     }
13534 }
13535
13536 char *
13537 DefaultFileName (char *ext)
13538 {
13539     static char def[MSG_SIZ];
13540     char *p;
13541
13542     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13543         p = def;
13544         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13545         *p++ = '-';
13546         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13547         *p++ = '.';
13548         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13549     } else {
13550         def[0] = NULLCHAR;
13551     }
13552     return def;
13553 }
13554
13555 /* Save the current game to the given file */
13556 int
13557 SaveGameToFile (char *filename, int append)
13558 {
13559     FILE *f;
13560     char buf[MSG_SIZ];
13561     int result, i, t,tot=0;
13562
13563     if (strcmp(filename, "-") == 0) {
13564         return SaveGame(stdout, 0, NULL);
13565     } else {
13566         for(i=0; i<10; i++) { // upto 10 tries
13567              f = fopen(filename, append ? "a" : "w");
13568              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13569              if(f || errno != 13) break;
13570              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13571              tot += t;
13572         }
13573         if (f == NULL) {
13574             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13575             DisplayError(buf, errno);
13576             return FALSE;
13577         } else {
13578             safeStrCpy(buf, lastMsg, MSG_SIZ);
13579             DisplayMessage(_("Waiting for access to save file"), "");
13580             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13581             DisplayMessage(_("Saving game"), "");
13582             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13583             result = SaveGame(f, 0, NULL);
13584             DisplayMessage(buf, "");
13585             return result;
13586         }
13587     }
13588 }
13589
13590 char *
13591 SavePart (char *str)
13592 {
13593     static char buf[MSG_SIZ];
13594     char *p;
13595
13596     p = strchr(str, ' ');
13597     if (p == NULL) return str;
13598     strncpy(buf, str, p - str);
13599     buf[p - str] = NULLCHAR;
13600     return buf;
13601 }
13602
13603 #define PGN_MAX_LINE 75
13604
13605 #define PGN_SIDE_WHITE  0
13606 #define PGN_SIDE_BLACK  1
13607
13608 static int
13609 FindFirstMoveOutOfBook (int side)
13610 {
13611     int result = -1;
13612
13613     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13614         int index = backwardMostMove;
13615         int has_book_hit = 0;
13616
13617         if( (index % 2) != side ) {
13618             index++;
13619         }
13620
13621         while( index < forwardMostMove ) {
13622             /* Check to see if engine is in book */
13623             int depth = pvInfoList[index].depth;
13624             int score = pvInfoList[index].score;
13625             int in_book = 0;
13626
13627             if( depth <= 2 ) {
13628                 in_book = 1;
13629             }
13630             else if( score == 0 && depth == 63 ) {
13631                 in_book = 1; /* Zappa */
13632             }
13633             else if( score == 2 && depth == 99 ) {
13634                 in_book = 1; /* Abrok */
13635             }
13636
13637             has_book_hit += in_book;
13638
13639             if( ! in_book ) {
13640                 result = index;
13641
13642                 break;
13643             }
13644
13645             index += 2;
13646         }
13647     }
13648
13649     return result;
13650 }
13651
13652 void
13653 GetOutOfBookInfo (char * buf)
13654 {
13655     int oob[2];
13656     int i;
13657     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13658
13659     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13660     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13661
13662     *buf = '\0';
13663
13664     if( oob[0] >= 0 || oob[1] >= 0 ) {
13665         for( i=0; i<2; i++ ) {
13666             int idx = oob[i];
13667
13668             if( idx >= 0 ) {
13669                 if( i > 0 && oob[0] >= 0 ) {
13670                     strcat( buf, "   " );
13671                 }
13672
13673                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13674                 sprintf( buf+strlen(buf), "%s%.2f",
13675                     pvInfoList[idx].score >= 0 ? "+" : "",
13676                     pvInfoList[idx].score / 100.0 );
13677             }
13678         }
13679     }
13680 }
13681
13682 /* Save game in PGN style */
13683 static void
13684 SaveGamePGN2 (FILE *f)
13685 {
13686     int i, offset, linelen, newblock;
13687 //    char *movetext;
13688     char numtext[32];
13689     int movelen, numlen, blank;
13690     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13691
13692     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13693
13694     PrintPGNTags(f, &gameInfo);
13695
13696     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13697
13698     if (backwardMostMove > 0 || startedFromSetupPosition) {
13699         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13700         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13701         fprintf(f, "\n{--------------\n");
13702         PrintPosition(f, backwardMostMove);
13703         fprintf(f, "--------------}\n");
13704         free(fen);
13705     }
13706     else {
13707         /* [AS] Out of book annotation */
13708         if( appData.saveOutOfBookInfo ) {
13709             char buf[64];
13710
13711             GetOutOfBookInfo( buf );
13712
13713             if( buf[0] != '\0' ) {
13714                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13715             }
13716         }
13717
13718         fprintf(f, "\n");
13719     }
13720
13721     i = backwardMostMove;
13722     linelen = 0;
13723     newblock = TRUE;
13724
13725     while (i < forwardMostMove) {
13726         /* Print comments preceding this move */
13727         if (commentList[i] != NULL) {
13728             if (linelen > 0) fprintf(f, "\n");
13729             fprintf(f, "%s", commentList[i]);
13730             linelen = 0;
13731             newblock = TRUE;
13732         }
13733
13734         /* Format move number */
13735         if ((i % 2) == 0)
13736           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13737         else
13738           if (newblock)
13739             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13740           else
13741             numtext[0] = NULLCHAR;
13742
13743         numlen = strlen(numtext);
13744         newblock = FALSE;
13745
13746         /* Print move number */
13747         blank = linelen > 0 && numlen > 0;
13748         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13749             fprintf(f, "\n");
13750             linelen = 0;
13751             blank = 0;
13752         }
13753         if (blank) {
13754             fprintf(f, " ");
13755             linelen++;
13756         }
13757         fprintf(f, "%s", numtext);
13758         linelen += numlen;
13759
13760         /* Get move */
13761         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13762         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13763
13764         /* Print move */
13765         blank = linelen > 0 && movelen > 0;
13766         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13767             fprintf(f, "\n");
13768             linelen = 0;
13769             blank = 0;
13770         }
13771         if (blank) {
13772             fprintf(f, " ");
13773             linelen++;
13774         }
13775         fprintf(f, "%s", move_buffer);
13776         linelen += movelen;
13777
13778         /* [AS] Add PV info if present */
13779         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13780             /* [HGM] add time */
13781             char buf[MSG_SIZ]; int seconds;
13782
13783             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13784
13785             if( seconds <= 0)
13786               buf[0] = 0;
13787             else
13788               if( seconds < 30 )
13789                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13790               else
13791                 {
13792                   seconds = (seconds + 4)/10; // round to full seconds
13793                   if( seconds < 60 )
13794                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13795                   else
13796                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13797                 }
13798
13799             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13800                       pvInfoList[i].score >= 0 ? "+" : "",
13801                       pvInfoList[i].score / 100.0,
13802                       pvInfoList[i].depth,
13803                       buf );
13804
13805             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13806
13807             /* Print score/depth */
13808             blank = linelen > 0 && movelen > 0;
13809             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13810                 fprintf(f, "\n");
13811                 linelen = 0;
13812                 blank = 0;
13813             }
13814             if (blank) {
13815                 fprintf(f, " ");
13816                 linelen++;
13817             }
13818             fprintf(f, "%s", move_buffer);
13819             linelen += movelen;
13820         }
13821
13822         i++;
13823     }
13824
13825     /* Start a new line */
13826     if (linelen > 0) fprintf(f, "\n");
13827
13828     /* Print comments after last move */
13829     if (commentList[i] != NULL) {
13830         fprintf(f, "%s\n", commentList[i]);
13831     }
13832
13833     /* Print result */
13834     if (gameInfo.resultDetails != NULL &&
13835         gameInfo.resultDetails[0] != NULLCHAR) {
13836         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13837         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13838            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13839             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13840         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13841     } else {
13842         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13843     }
13844 }
13845
13846 /* Save game in PGN style and close the file */
13847 int
13848 SaveGamePGN (FILE *f)
13849 {
13850     SaveGamePGN2(f);
13851     fclose(f);
13852     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13853     return TRUE;
13854 }
13855
13856 /* Save game in old style and close the file */
13857 int
13858 SaveGameOldStyle (FILE *f)
13859 {
13860     int i, offset;
13861     time_t tm;
13862
13863     tm = time((time_t *) NULL);
13864
13865     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13866     PrintOpponents(f);
13867
13868     if (backwardMostMove > 0 || startedFromSetupPosition) {
13869         fprintf(f, "\n[--------------\n");
13870         PrintPosition(f, backwardMostMove);
13871         fprintf(f, "--------------]\n");
13872     } else {
13873         fprintf(f, "\n");
13874     }
13875
13876     i = backwardMostMove;
13877     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13878
13879     while (i < forwardMostMove) {
13880         if (commentList[i] != NULL) {
13881             fprintf(f, "[%s]\n", commentList[i]);
13882         }
13883
13884         if ((i % 2) == 1) {
13885             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13886             i++;
13887         } else {
13888             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13889             i++;
13890             if (commentList[i] != NULL) {
13891                 fprintf(f, "\n");
13892                 continue;
13893             }
13894             if (i >= forwardMostMove) {
13895                 fprintf(f, "\n");
13896                 break;
13897             }
13898             fprintf(f, "%s\n", parseList[i]);
13899             i++;
13900         }
13901     }
13902
13903     if (commentList[i] != NULL) {
13904         fprintf(f, "[%s]\n", commentList[i]);
13905     }
13906
13907     /* This isn't really the old style, but it's close enough */
13908     if (gameInfo.resultDetails != NULL &&
13909         gameInfo.resultDetails[0] != NULLCHAR) {
13910         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13911                 gameInfo.resultDetails);
13912     } else {
13913         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13914     }
13915
13916     fclose(f);
13917     return TRUE;
13918 }
13919
13920 /* Save the current game to open file f and close the file */
13921 int
13922 SaveGame (FILE *f, int dummy, char *dummy2)
13923 {
13924     if (gameMode == EditPosition) EditPositionDone(TRUE);
13925     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13926     if (appData.oldSaveStyle)
13927       return SaveGameOldStyle(f);
13928     else
13929       return SaveGamePGN(f);
13930 }
13931
13932 /* Save the current position to the given file */
13933 int
13934 SavePositionToFile (char *filename)
13935 {
13936     FILE *f;
13937     char buf[MSG_SIZ];
13938
13939     if (strcmp(filename, "-") == 0) {
13940         return SavePosition(stdout, 0, NULL);
13941     } else {
13942         f = fopen(filename, "a");
13943         if (f == NULL) {
13944             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13945             DisplayError(buf, errno);
13946             return FALSE;
13947         } else {
13948             safeStrCpy(buf, lastMsg, MSG_SIZ);
13949             DisplayMessage(_("Waiting for access to save file"), "");
13950             flock(fileno(f), LOCK_EX); // [HGM] lock
13951             DisplayMessage(_("Saving position"), "");
13952             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13953             SavePosition(f, 0, NULL);
13954             DisplayMessage(buf, "");
13955             return TRUE;
13956         }
13957     }
13958 }
13959
13960 /* Save the current position to the given open file and close the file */
13961 int
13962 SavePosition (FILE *f, int dummy, char *dummy2)
13963 {
13964     time_t tm;
13965     char *fen;
13966
13967     if (gameMode == EditPosition) EditPositionDone(TRUE);
13968     if (appData.oldSaveStyle) {
13969         tm = time((time_t *) NULL);
13970
13971         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13972         PrintOpponents(f);
13973         fprintf(f, "[--------------\n");
13974         PrintPosition(f, currentMove);
13975         fprintf(f, "--------------]\n");
13976     } else {
13977         fen = PositionToFEN(currentMove, NULL, 1);
13978         fprintf(f, "%s\n", fen);
13979         free(fen);
13980     }
13981     fclose(f);
13982     return TRUE;
13983 }
13984
13985 void
13986 ReloadCmailMsgEvent (int unregister)
13987 {
13988 #if !WIN32
13989     static char *inFilename = NULL;
13990     static char *outFilename;
13991     int i;
13992     struct stat inbuf, outbuf;
13993     int status;
13994
13995     /* Any registered moves are unregistered if unregister is set, */
13996     /* i.e. invoked by the signal handler */
13997     if (unregister) {
13998         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13999             cmailMoveRegistered[i] = FALSE;
14000             if (cmailCommentList[i] != NULL) {
14001                 free(cmailCommentList[i]);
14002                 cmailCommentList[i] = NULL;
14003             }
14004         }
14005         nCmailMovesRegistered = 0;
14006     }
14007
14008     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14009         cmailResult[i] = CMAIL_NOT_RESULT;
14010     }
14011     nCmailResults = 0;
14012
14013     if (inFilename == NULL) {
14014         /* Because the filenames are static they only get malloced once  */
14015         /* and they never get freed                                      */
14016         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14017         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14018
14019         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14020         sprintf(outFilename, "%s.out", appData.cmailGameName);
14021     }
14022
14023     status = stat(outFilename, &outbuf);
14024     if (status < 0) {
14025         cmailMailedMove = FALSE;
14026     } else {
14027         status = stat(inFilename, &inbuf);
14028         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14029     }
14030
14031     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14032        counts the games, notes how each one terminated, etc.
14033
14034        It would be nice to remove this kludge and instead gather all
14035        the information while building the game list.  (And to keep it
14036        in the game list nodes instead of having a bunch of fixed-size
14037        parallel arrays.)  Note this will require getting each game's
14038        termination from the PGN tags, as the game list builder does
14039        not process the game moves.  --mann
14040        */
14041     cmailMsgLoaded = TRUE;
14042     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14043
14044     /* Load first game in the file or popup game menu */
14045     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14046
14047 #endif /* !WIN32 */
14048     return;
14049 }
14050
14051 int
14052 RegisterMove ()
14053 {
14054     FILE *f;
14055     char string[MSG_SIZ];
14056
14057     if (   cmailMailedMove
14058         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14059         return TRUE;            /* Allow free viewing  */
14060     }
14061
14062     /* Unregister move to ensure that we don't leave RegisterMove        */
14063     /* with the move registered when the conditions for registering no   */
14064     /* longer hold                                                       */
14065     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14066         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14067         nCmailMovesRegistered --;
14068
14069         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14070           {
14071               free(cmailCommentList[lastLoadGameNumber - 1]);
14072               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14073           }
14074     }
14075
14076     if (cmailOldMove == -1) {
14077         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14078         return FALSE;
14079     }
14080
14081     if (currentMove > cmailOldMove + 1) {
14082         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14083         return FALSE;
14084     }
14085
14086     if (currentMove < cmailOldMove) {
14087         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14088         return FALSE;
14089     }
14090
14091     if (forwardMostMove > currentMove) {
14092         /* Silently truncate extra moves */
14093         TruncateGame();
14094     }
14095
14096     if (   (currentMove == cmailOldMove + 1)
14097         || (   (currentMove == cmailOldMove)
14098             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14099                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14100         if (gameInfo.result != GameUnfinished) {
14101             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14102         }
14103
14104         if (commentList[currentMove] != NULL) {
14105             cmailCommentList[lastLoadGameNumber - 1]
14106               = StrSave(commentList[currentMove]);
14107         }
14108         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14109
14110         if (appData.debugMode)
14111           fprintf(debugFP, "Saving %s for game %d\n",
14112                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14113
14114         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14115
14116         f = fopen(string, "w");
14117         if (appData.oldSaveStyle) {
14118             SaveGameOldStyle(f); /* also closes the file */
14119
14120             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14121             f = fopen(string, "w");
14122             SavePosition(f, 0, NULL); /* also closes the file */
14123         } else {
14124             fprintf(f, "{--------------\n");
14125             PrintPosition(f, currentMove);
14126             fprintf(f, "--------------}\n\n");
14127
14128             SaveGame(f, 0, NULL); /* also closes the file*/
14129         }
14130
14131         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14132         nCmailMovesRegistered ++;
14133     } else if (nCmailGames == 1) {
14134         DisplayError(_("You have not made a move yet"), 0);
14135         return FALSE;
14136     }
14137
14138     return TRUE;
14139 }
14140
14141 void
14142 MailMoveEvent ()
14143 {
14144 #if !WIN32
14145     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14146     FILE *commandOutput;
14147     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14148     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14149     int nBuffers;
14150     int i;
14151     int archived;
14152     char *arcDir;
14153
14154     if (! cmailMsgLoaded) {
14155         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14156         return;
14157     }
14158
14159     if (nCmailGames == nCmailResults) {
14160         DisplayError(_("No unfinished games"), 0);
14161         return;
14162     }
14163
14164 #if CMAIL_PROHIBIT_REMAIL
14165     if (cmailMailedMove) {
14166       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);
14167         DisplayError(msg, 0);
14168         return;
14169     }
14170 #endif
14171
14172     if (! (cmailMailedMove || RegisterMove())) return;
14173
14174     if (   cmailMailedMove
14175         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14176       snprintf(string, MSG_SIZ, partCommandString,
14177                appData.debugMode ? " -v" : "", appData.cmailGameName);
14178         commandOutput = popen(string, "r");
14179
14180         if (commandOutput == NULL) {
14181             DisplayError(_("Failed to invoke cmail"), 0);
14182         } else {
14183             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14184                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14185             }
14186             if (nBuffers > 1) {
14187                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14188                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14189                 nBytes = MSG_SIZ - 1;
14190             } else {
14191                 (void) memcpy(msg, buffer, nBytes);
14192             }
14193             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14194
14195             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14196                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14197
14198                 archived = TRUE;
14199                 for (i = 0; i < nCmailGames; i ++) {
14200                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14201                         archived = FALSE;
14202                     }
14203                 }
14204                 if (   archived
14205                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14206                         != NULL)) {
14207                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14208                            arcDir,
14209                            appData.cmailGameName,
14210                            gameInfo.date);
14211                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14212                     cmailMsgLoaded = FALSE;
14213                 }
14214             }
14215
14216             DisplayInformation(msg);
14217             pclose(commandOutput);
14218         }
14219     } else {
14220         if ((*cmailMsg) != '\0') {
14221             DisplayInformation(cmailMsg);
14222         }
14223     }
14224
14225     return;
14226 #endif /* !WIN32 */
14227 }
14228
14229 char *
14230 CmailMsg ()
14231 {
14232 #if WIN32
14233     return NULL;
14234 #else
14235     int  prependComma = 0;
14236     char number[5];
14237     char string[MSG_SIZ];       /* Space for game-list */
14238     int  i;
14239
14240     if (!cmailMsgLoaded) return "";
14241
14242     if (cmailMailedMove) {
14243       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14244     } else {
14245         /* Create a list of games left */
14246       snprintf(string, MSG_SIZ, "[");
14247         for (i = 0; i < nCmailGames; i ++) {
14248             if (! (   cmailMoveRegistered[i]
14249                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14250                 if (prependComma) {
14251                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14252                 } else {
14253                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14254                     prependComma = 1;
14255                 }
14256
14257                 strcat(string, number);
14258             }
14259         }
14260         strcat(string, "]");
14261
14262         if (nCmailMovesRegistered + nCmailResults == 0) {
14263             switch (nCmailGames) {
14264               case 1:
14265                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14266                 break;
14267
14268               case 2:
14269                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14270                 break;
14271
14272               default:
14273                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14274                          nCmailGames);
14275                 break;
14276             }
14277         } else {
14278             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14279               case 1:
14280                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14281                          string);
14282                 break;
14283
14284               case 0:
14285                 if (nCmailResults == nCmailGames) {
14286                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14287                 } else {
14288                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14289                 }
14290                 break;
14291
14292               default:
14293                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14294                          string);
14295             }
14296         }
14297     }
14298     return cmailMsg;
14299 #endif /* WIN32 */
14300 }
14301
14302 void
14303 ResetGameEvent ()
14304 {
14305     if (gameMode == Training)
14306       SetTrainingModeOff();
14307
14308     Reset(TRUE, TRUE);
14309     cmailMsgLoaded = FALSE;
14310     if (appData.icsActive) {
14311       SendToICS(ics_prefix);
14312       SendToICS("refresh\n");
14313     }
14314 }
14315
14316 void
14317 ExitEvent (int status)
14318 {
14319     exiting++;
14320     if (exiting > 2) {
14321       /* Give up on clean exit */
14322       exit(status);
14323     }
14324     if (exiting > 1) {
14325       /* Keep trying for clean exit */
14326       return;
14327     }
14328
14329     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14330     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14331
14332     if (telnetISR != NULL) {
14333       RemoveInputSource(telnetISR);
14334     }
14335     if (icsPR != NoProc) {
14336       DestroyChildProcess(icsPR, TRUE);
14337     }
14338
14339     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14340     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14341
14342     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14343     /* make sure this other one finishes before killing it!                  */
14344     if(endingGame) { int count = 0;
14345         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14346         while(endingGame && count++ < 10) DoSleep(1);
14347         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14348     }
14349
14350     /* Kill off chess programs */
14351     if (first.pr != NoProc) {
14352         ExitAnalyzeMode();
14353
14354         DoSleep( appData.delayBeforeQuit );
14355         SendToProgram("quit\n", &first);
14356         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14357     }
14358     if (second.pr != NoProc) {
14359         DoSleep( appData.delayBeforeQuit );
14360         SendToProgram("quit\n", &second);
14361         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14362     }
14363     if (first.isr != NULL) {
14364         RemoveInputSource(first.isr);
14365     }
14366     if (second.isr != NULL) {
14367         RemoveInputSource(second.isr);
14368     }
14369
14370     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14371     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14372
14373     ShutDownFrontEnd();
14374     exit(status);
14375 }
14376
14377 void
14378 PauseEngine (ChessProgramState *cps)
14379 {
14380     SendToProgram("pause\n", cps);
14381     cps->pause = 2;
14382 }
14383
14384 void
14385 UnPauseEngine (ChessProgramState *cps)
14386 {
14387     SendToProgram("resume\n", cps);
14388     cps->pause = 1;
14389 }
14390
14391 void
14392 PauseEvent ()
14393 {
14394     if (appData.debugMode)
14395         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14396     if (pausing) {
14397         pausing = FALSE;
14398         ModeHighlight();
14399         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14400             StartClocks();
14401             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14402                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14403                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14404             }
14405             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14406             HandleMachineMove(stashedInputMove, stalledEngine);
14407             stalledEngine = NULL;
14408             return;
14409         }
14410         if (gameMode == MachinePlaysWhite ||
14411             gameMode == TwoMachinesPlay   ||
14412             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14413             if(first.pause)  UnPauseEngine(&first);
14414             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14415             if(second.pause) UnPauseEngine(&second);
14416             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14417             StartClocks();
14418         } else {
14419             DisplayBothClocks();
14420         }
14421         if (gameMode == PlayFromGameFile) {
14422             if (appData.timeDelay >= 0)
14423                 AutoPlayGameLoop();
14424         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14425             Reset(FALSE, TRUE);
14426             SendToICS(ics_prefix);
14427             SendToICS("refresh\n");
14428         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14429             ForwardInner(forwardMostMove);
14430         }
14431         pauseExamInvalid = FALSE;
14432     } else {
14433         switch (gameMode) {
14434           default:
14435             return;
14436           case IcsExamining:
14437             pauseExamForwardMostMove = forwardMostMove;
14438             pauseExamInvalid = FALSE;
14439             /* fall through */
14440           case IcsObserving:
14441           case IcsPlayingWhite:
14442           case IcsPlayingBlack:
14443             pausing = TRUE;
14444             ModeHighlight();
14445             return;
14446           case PlayFromGameFile:
14447             (void) StopLoadGameTimer();
14448             pausing = TRUE;
14449             ModeHighlight();
14450             break;
14451           case BeginningOfGame:
14452             if (appData.icsActive) return;
14453             /* else fall through */
14454           case MachinePlaysWhite:
14455           case MachinePlaysBlack:
14456           case TwoMachinesPlay:
14457             if (forwardMostMove == 0)
14458               return;           /* don't pause if no one has moved */
14459             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14460                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14461                 if(onMove->pause) {           // thinking engine can be paused
14462                     PauseEngine(onMove);      // do it
14463                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14464                         PauseEngine(onMove->other);
14465                     else
14466                         SendToProgram("easy\n", onMove->other);
14467                     StopClocks();
14468                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14469             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14470                 if(first.pause) {
14471                     PauseEngine(&first);
14472                     StopClocks();
14473                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14474             } else { // human on move, pause pondering by either method
14475                 if(first.pause)
14476                     PauseEngine(&first);
14477                 else if(appData.ponderNextMove)
14478                     SendToProgram("easy\n", &first);
14479                 StopClocks();
14480             }
14481             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14482           case AnalyzeMode:
14483             pausing = TRUE;
14484             ModeHighlight();
14485             break;
14486         }
14487     }
14488 }
14489
14490 void
14491 EditCommentEvent ()
14492 {
14493     char title[MSG_SIZ];
14494
14495     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14496       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14497     } else {
14498       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14499                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14500                parseList[currentMove - 1]);
14501     }
14502
14503     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14504 }
14505
14506
14507 void
14508 EditTagsEvent ()
14509 {
14510     char *tags = PGNTags(&gameInfo);
14511     bookUp = FALSE;
14512     EditTagsPopUp(tags, NULL);
14513     free(tags);
14514 }
14515
14516 void
14517 ToggleSecond ()
14518 {
14519   if(second.analyzing) {
14520     SendToProgram("exit\n", &second);
14521     second.analyzing = FALSE;
14522   } else {
14523     if (second.pr == NoProc) StartChessProgram(&second);
14524     InitChessProgram(&second, FALSE);
14525     FeedMovesToProgram(&second, currentMove);
14526
14527     SendToProgram("analyze\n", &second);
14528     second.analyzing = TRUE;
14529   }
14530 }
14531
14532 /* Toggle ShowThinking */
14533 void
14534 ToggleShowThinking()
14535 {
14536   appData.showThinking = !appData.showThinking;
14537   ShowThinkingEvent();
14538 }
14539
14540 int
14541 AnalyzeModeEvent ()
14542 {
14543     char buf[MSG_SIZ];
14544
14545     if (!first.analysisSupport) {
14546       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14547       DisplayError(buf, 0);
14548       return 0;
14549     }
14550     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14551     if (appData.icsActive) {
14552         if (gameMode != IcsObserving) {
14553           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14554             DisplayError(buf, 0);
14555             /* secure check */
14556             if (appData.icsEngineAnalyze) {
14557                 if (appData.debugMode)
14558                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14559                 ExitAnalyzeMode();
14560                 ModeHighlight();
14561             }
14562             return 0;
14563         }
14564         /* if enable, user wants to disable icsEngineAnalyze */
14565         if (appData.icsEngineAnalyze) {
14566                 ExitAnalyzeMode();
14567                 ModeHighlight();
14568                 return 0;
14569         }
14570         appData.icsEngineAnalyze = TRUE;
14571         if (appData.debugMode)
14572             fprintf(debugFP, "ICS engine analyze starting... \n");
14573     }
14574
14575     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14576     if (appData.noChessProgram || gameMode == AnalyzeMode)
14577       return 0;
14578
14579     if (gameMode != AnalyzeFile) {
14580         if (!appData.icsEngineAnalyze) {
14581                EditGameEvent();
14582                if (gameMode != EditGame) return 0;
14583         }
14584         if (!appData.showThinking) ToggleShowThinking();
14585         ResurrectChessProgram();
14586         SendToProgram("analyze\n", &first);
14587         first.analyzing = TRUE;
14588         /*first.maybeThinking = TRUE;*/
14589         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14590         EngineOutputPopUp();
14591     }
14592     if (!appData.icsEngineAnalyze) {
14593         gameMode = AnalyzeMode;
14594         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14595     }
14596     pausing = FALSE;
14597     ModeHighlight();
14598     SetGameInfo();
14599
14600     StartAnalysisClock();
14601     GetTimeMark(&lastNodeCountTime);
14602     lastNodeCount = 0;
14603     return 1;
14604 }
14605
14606 void
14607 AnalyzeFileEvent ()
14608 {
14609     if (appData.noChessProgram || gameMode == AnalyzeFile)
14610       return;
14611
14612     if (!first.analysisSupport) {
14613       char buf[MSG_SIZ];
14614       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14615       DisplayError(buf, 0);
14616       return;
14617     }
14618
14619     if (gameMode != AnalyzeMode) {
14620         keepInfo = 1; // mere annotating should not alter PGN tags
14621         EditGameEvent();
14622         keepInfo = 0;
14623         if (gameMode != EditGame) return;
14624         if (!appData.showThinking) ToggleShowThinking();
14625         ResurrectChessProgram();
14626         SendToProgram("analyze\n", &first);
14627         first.analyzing = TRUE;
14628         /*first.maybeThinking = TRUE;*/
14629         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14630         EngineOutputPopUp();
14631     }
14632     gameMode = AnalyzeFile;
14633     pausing = FALSE;
14634     ModeHighlight();
14635
14636     StartAnalysisClock();
14637     GetTimeMark(&lastNodeCountTime);
14638     lastNodeCount = 0;
14639     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14640     AnalysisPeriodicEvent(1);
14641 }
14642
14643 void
14644 MachineWhiteEvent ()
14645 {
14646     char buf[MSG_SIZ];
14647     char *bookHit = NULL;
14648
14649     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14650       return;
14651
14652
14653     if (gameMode == PlayFromGameFile ||
14654         gameMode == TwoMachinesPlay  ||
14655         gameMode == Training         ||
14656         gameMode == AnalyzeMode      ||
14657         gameMode == EndOfGame)
14658         EditGameEvent();
14659
14660     if (gameMode == EditPosition)
14661         EditPositionDone(TRUE);
14662
14663     if (!WhiteOnMove(currentMove)) {
14664         DisplayError(_("It is not White's turn"), 0);
14665         return;
14666     }
14667
14668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14669       ExitAnalyzeMode();
14670
14671     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14672         gameMode == AnalyzeFile)
14673         TruncateGame();
14674
14675     ResurrectChessProgram();    /* in case it isn't running */
14676     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14677         gameMode = MachinePlaysWhite;
14678         ResetClocks();
14679     } else
14680     gameMode = MachinePlaysWhite;
14681     pausing = FALSE;
14682     ModeHighlight();
14683     SetGameInfo();
14684     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14685     DisplayTitle(buf);
14686     if (first.sendName) {
14687       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14688       SendToProgram(buf, &first);
14689     }
14690     if (first.sendTime) {
14691       if (first.useColors) {
14692         SendToProgram("black\n", &first); /*gnu kludge*/
14693       }
14694       SendTimeRemaining(&first, TRUE);
14695     }
14696     if (first.useColors) {
14697       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14698     }
14699     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14700     SetMachineThinkingEnables();
14701     first.maybeThinking = TRUE;
14702     StartClocks();
14703     firstMove = FALSE;
14704
14705     if (appData.autoFlipView && !flipView) {
14706       flipView = !flipView;
14707       DrawPosition(FALSE, NULL);
14708       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14709     }
14710
14711     if(bookHit) { // [HGM] book: simulate book reply
14712         static char bookMove[MSG_SIZ]; // a bit generous?
14713
14714         programStats.nodes = programStats.depth = programStats.time =
14715         programStats.score = programStats.got_only_move = 0;
14716         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14717
14718         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14719         strcat(bookMove, bookHit);
14720         HandleMachineMove(bookMove, &first);
14721     }
14722 }
14723
14724 void
14725 MachineBlackEvent ()
14726 {
14727   char buf[MSG_SIZ];
14728   char *bookHit = NULL;
14729
14730     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14731         return;
14732
14733
14734     if (gameMode == PlayFromGameFile ||
14735         gameMode == TwoMachinesPlay  ||
14736         gameMode == Training         ||
14737         gameMode == AnalyzeMode      ||
14738         gameMode == EndOfGame)
14739         EditGameEvent();
14740
14741     if (gameMode == EditPosition)
14742         EditPositionDone(TRUE);
14743
14744     if (WhiteOnMove(currentMove)) {
14745         DisplayError(_("It is not Black's turn"), 0);
14746         return;
14747     }
14748
14749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14750       ExitAnalyzeMode();
14751
14752     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14753         gameMode == AnalyzeFile)
14754         TruncateGame();
14755
14756     ResurrectChessProgram();    /* in case it isn't running */
14757     gameMode = MachinePlaysBlack;
14758     pausing = FALSE;
14759     ModeHighlight();
14760     SetGameInfo();
14761     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14762     DisplayTitle(buf);
14763     if (first.sendName) {
14764       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14765       SendToProgram(buf, &first);
14766     }
14767     if (first.sendTime) {
14768       if (first.useColors) {
14769         SendToProgram("white\n", &first); /*gnu kludge*/
14770       }
14771       SendTimeRemaining(&first, FALSE);
14772     }
14773     if (first.useColors) {
14774       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14775     }
14776     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14777     SetMachineThinkingEnables();
14778     first.maybeThinking = TRUE;
14779     StartClocks();
14780
14781     if (appData.autoFlipView && flipView) {
14782       flipView = !flipView;
14783       DrawPosition(FALSE, NULL);
14784       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14785     }
14786     if(bookHit) { // [HGM] book: simulate book reply
14787         static char bookMove[MSG_SIZ]; // a bit generous?
14788
14789         programStats.nodes = programStats.depth = programStats.time =
14790         programStats.score = programStats.got_only_move = 0;
14791         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14792
14793         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14794         strcat(bookMove, bookHit);
14795         HandleMachineMove(bookMove, &first);
14796     }
14797 }
14798
14799
14800 void
14801 DisplayTwoMachinesTitle ()
14802 {
14803     char buf[MSG_SIZ];
14804     if (appData.matchGames > 0) {
14805         if(appData.tourneyFile[0]) {
14806           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14807                    gameInfo.white, _("vs."), gameInfo.black,
14808                    nextGame+1, appData.matchGames+1,
14809                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14810         } else
14811         if (first.twoMachinesColor[0] == 'w') {
14812           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14813                    gameInfo.white, _("vs."),  gameInfo.black,
14814                    first.matchWins, second.matchWins,
14815                    matchGame - 1 - (first.matchWins + second.matchWins));
14816         } else {
14817           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14818                    gameInfo.white, _("vs."), gameInfo.black,
14819                    second.matchWins, first.matchWins,
14820                    matchGame - 1 - (first.matchWins + second.matchWins));
14821         }
14822     } else {
14823       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14824     }
14825     DisplayTitle(buf);
14826 }
14827
14828 void
14829 SettingsMenuIfReady ()
14830 {
14831   if (second.lastPing != second.lastPong) {
14832     DisplayMessage("", _("Waiting for second chess program"));
14833     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14834     return;
14835   }
14836   ThawUI();
14837   DisplayMessage("", "");
14838   SettingsPopUp(&second);
14839 }
14840
14841 int
14842 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14843 {
14844     char buf[MSG_SIZ];
14845     if (cps->pr == NoProc) {
14846         StartChessProgram(cps);
14847         if (cps->protocolVersion == 1) {
14848           retry();
14849           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14850         } else {
14851           /* kludge: allow timeout for initial "feature" command */
14852           if(retry != TwoMachinesEventIfReady) FreezeUI();
14853           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14854           DisplayMessage("", buf);
14855           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14856         }
14857         return 1;
14858     }
14859     return 0;
14860 }
14861
14862 void
14863 TwoMachinesEvent P((void))
14864 {
14865     int i;
14866     char buf[MSG_SIZ];
14867     ChessProgramState *onmove;
14868     char *bookHit = NULL;
14869     static int stalling = 0;
14870     TimeMark now;
14871     long wait;
14872
14873     if (appData.noChessProgram) return;
14874
14875     switch (gameMode) {
14876       case TwoMachinesPlay:
14877         return;
14878       case MachinePlaysWhite:
14879       case MachinePlaysBlack:
14880         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14881             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14882             return;
14883         }
14884         /* fall through */
14885       case BeginningOfGame:
14886       case PlayFromGameFile:
14887       case EndOfGame:
14888         EditGameEvent();
14889         if (gameMode != EditGame) return;
14890         break;
14891       case EditPosition:
14892         EditPositionDone(TRUE);
14893         break;
14894       case AnalyzeMode:
14895       case AnalyzeFile:
14896         ExitAnalyzeMode();
14897         break;
14898       case EditGame:
14899       default:
14900         break;
14901     }
14902
14903 //    forwardMostMove = currentMove;
14904     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14905     startingEngine = TRUE;
14906
14907     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14908
14909     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14910     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14911       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14912       return;
14913     }
14914     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14915
14916     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14917                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14918         startingEngine = matchMode = FALSE;
14919         DisplayError("second engine does not play this", 0);
14920         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14921         EditGameEvent(); // switch back to EditGame mode
14922         return;
14923     }
14924
14925     if(!stalling) {
14926       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14927       SendToProgram("force\n", &second);
14928       stalling = 1;
14929       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14930       return;
14931     }
14932     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14933     if(appData.matchPause>10000 || appData.matchPause<10)
14934                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14935     wait = SubtractTimeMarks(&now, &pauseStart);
14936     if(wait < appData.matchPause) {
14937         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14938         return;
14939     }
14940     // we are now committed to starting the game
14941     stalling = 0;
14942     DisplayMessage("", "");
14943     if (startedFromSetupPosition) {
14944         SendBoard(&second, backwardMostMove);
14945     if (appData.debugMode) {
14946         fprintf(debugFP, "Two Machines\n");
14947     }
14948     }
14949     for (i = backwardMostMove; i < forwardMostMove; i++) {
14950         SendMoveToProgram(i, &second);
14951     }
14952
14953     gameMode = TwoMachinesPlay;
14954     pausing = startingEngine = FALSE;
14955     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14956     SetGameInfo();
14957     DisplayTwoMachinesTitle();
14958     firstMove = TRUE;
14959     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14960         onmove = &first;
14961     } else {
14962         onmove = &second;
14963     }
14964     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14965     SendToProgram(first.computerString, &first);
14966     if (first.sendName) {
14967       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14968       SendToProgram(buf, &first);
14969     }
14970     SendToProgram(second.computerString, &second);
14971     if (second.sendName) {
14972       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14973       SendToProgram(buf, &second);
14974     }
14975
14976     ResetClocks();
14977     if (!first.sendTime || !second.sendTime) {
14978         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14979         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14980     }
14981     if (onmove->sendTime) {
14982       if (onmove->useColors) {
14983         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14984       }
14985       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14986     }
14987     if (onmove->useColors) {
14988       SendToProgram(onmove->twoMachinesColor, onmove);
14989     }
14990     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14991 //    SendToProgram("go\n", onmove);
14992     onmove->maybeThinking = TRUE;
14993     SetMachineThinkingEnables();
14994
14995     StartClocks();
14996
14997     if(bookHit) { // [HGM] book: simulate book reply
14998         static char bookMove[MSG_SIZ]; // a bit generous?
14999
15000         programStats.nodes = programStats.depth = programStats.time =
15001         programStats.score = programStats.got_only_move = 0;
15002         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15003
15004         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15005         strcat(bookMove, bookHit);
15006         savedMessage = bookMove; // args for deferred call
15007         savedState = onmove;
15008         ScheduleDelayedEvent(DeferredBookMove, 1);
15009     }
15010 }
15011
15012 void
15013 TrainingEvent ()
15014 {
15015     if (gameMode == Training) {
15016       SetTrainingModeOff();
15017       gameMode = PlayFromGameFile;
15018       DisplayMessage("", _("Training mode off"));
15019     } else {
15020       gameMode = Training;
15021       animateTraining = appData.animate;
15022
15023       /* make sure we are not already at the end of the game */
15024       if (currentMove < forwardMostMove) {
15025         SetTrainingModeOn();
15026         DisplayMessage("", _("Training mode on"));
15027       } else {
15028         gameMode = PlayFromGameFile;
15029         DisplayError(_("Already at end of game"), 0);
15030       }
15031     }
15032     ModeHighlight();
15033 }
15034
15035 void
15036 IcsClientEvent ()
15037 {
15038     if (!appData.icsActive) return;
15039     switch (gameMode) {
15040       case IcsPlayingWhite:
15041       case IcsPlayingBlack:
15042       case IcsObserving:
15043       case IcsIdle:
15044       case BeginningOfGame:
15045       case IcsExamining:
15046         return;
15047
15048       case EditGame:
15049         break;
15050
15051       case EditPosition:
15052         EditPositionDone(TRUE);
15053         break;
15054
15055       case AnalyzeMode:
15056       case AnalyzeFile:
15057         ExitAnalyzeMode();
15058         break;
15059
15060       default:
15061         EditGameEvent();
15062         break;
15063     }
15064
15065     gameMode = IcsIdle;
15066     ModeHighlight();
15067     return;
15068 }
15069
15070 void
15071 EditGameEvent ()
15072 {
15073     int i;
15074
15075     switch (gameMode) {
15076       case Training:
15077         SetTrainingModeOff();
15078         break;
15079       case MachinePlaysWhite:
15080       case MachinePlaysBlack:
15081       case BeginningOfGame:
15082         SendToProgram("force\n", &first);
15083         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15084             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15085                 char buf[MSG_SIZ];
15086                 abortEngineThink = TRUE;
15087                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15088                 SendToProgram(buf, &first);
15089                 DisplayMessage("Aborting engine think", "");
15090                 FreezeUI();
15091             }
15092         }
15093         SetUserThinkingEnables();
15094         break;
15095       case PlayFromGameFile:
15096         (void) StopLoadGameTimer();
15097         if (gameFileFP != NULL) {
15098             gameFileFP = NULL;
15099         }
15100         break;
15101       case EditPosition:
15102         EditPositionDone(TRUE);
15103         break;
15104       case AnalyzeMode:
15105       case AnalyzeFile:
15106         ExitAnalyzeMode();
15107         SendToProgram("force\n", &first);
15108         break;
15109       case TwoMachinesPlay:
15110         GameEnds(EndOfFile, NULL, GE_PLAYER);
15111         ResurrectChessProgram();
15112         SetUserThinkingEnables();
15113         break;
15114       case EndOfGame:
15115         ResurrectChessProgram();
15116         break;
15117       case IcsPlayingBlack:
15118       case IcsPlayingWhite:
15119         DisplayError(_("Warning: You are still playing a game"), 0);
15120         break;
15121       case IcsObserving:
15122         DisplayError(_("Warning: You are still observing a game"), 0);
15123         break;
15124       case IcsExamining:
15125         DisplayError(_("Warning: You are still examining a game"), 0);
15126         break;
15127       case IcsIdle:
15128         break;
15129       case EditGame:
15130       default:
15131         return;
15132     }
15133
15134     pausing = FALSE;
15135     StopClocks();
15136     first.offeredDraw = second.offeredDraw = 0;
15137
15138     if (gameMode == PlayFromGameFile) {
15139         whiteTimeRemaining = timeRemaining[0][currentMove];
15140         blackTimeRemaining = timeRemaining[1][currentMove];
15141         DisplayTitle("");
15142     }
15143
15144     if (gameMode == MachinePlaysWhite ||
15145         gameMode == MachinePlaysBlack ||
15146         gameMode == TwoMachinesPlay ||
15147         gameMode == EndOfGame) {
15148         i = forwardMostMove;
15149         while (i > currentMove) {
15150             SendToProgram("undo\n", &first);
15151             i--;
15152         }
15153         if(!adjustedClock) {
15154         whiteTimeRemaining = timeRemaining[0][currentMove];
15155         blackTimeRemaining = timeRemaining[1][currentMove];
15156         DisplayBothClocks();
15157         }
15158         if (whiteFlag || blackFlag) {
15159             whiteFlag = blackFlag = 0;
15160         }
15161         DisplayTitle("");
15162     }
15163
15164     gameMode = EditGame;
15165     ModeHighlight();
15166     SetGameInfo();
15167 }
15168
15169
15170 void
15171 EditPositionEvent ()
15172 {
15173     if (gameMode == EditPosition) {
15174         EditGameEvent();
15175         return;
15176     }
15177
15178     EditGameEvent();
15179     if (gameMode != EditGame) return;
15180
15181     gameMode = EditPosition;
15182     ModeHighlight();
15183     SetGameInfo();
15184     if (currentMove > 0)
15185       CopyBoard(boards[0], boards[currentMove]);
15186
15187     blackPlaysFirst = !WhiteOnMove(currentMove);
15188     ResetClocks();
15189     currentMove = forwardMostMove = backwardMostMove = 0;
15190     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15191     DisplayMove(-1);
15192     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15193 }
15194
15195 void
15196 ExitAnalyzeMode ()
15197 {
15198     /* [DM] icsEngineAnalyze - possible call from other functions */
15199     if (appData.icsEngineAnalyze) {
15200         appData.icsEngineAnalyze = FALSE;
15201
15202         DisplayMessage("",_("Close ICS engine analyze..."));
15203     }
15204     if (first.analysisSupport && first.analyzing) {
15205       SendToBoth("exit\n");
15206       first.analyzing = second.analyzing = FALSE;
15207     }
15208     thinkOutput[0] = NULLCHAR;
15209 }
15210
15211 void
15212 EditPositionDone (Boolean fakeRights)
15213 {
15214     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15215
15216     startedFromSetupPosition = TRUE;
15217     InitChessProgram(&first, FALSE);
15218     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15219       boards[0][EP_STATUS] = EP_NONE;
15220       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15221       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15222         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15223         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15224       } else boards[0][CASTLING][2] = NoRights;
15225       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15226         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15227         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15228       } else boards[0][CASTLING][5] = NoRights;
15229       if(gameInfo.variant == VariantSChess) {
15230         int i;
15231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15232           boards[0][VIRGIN][i] = 0;
15233           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15234           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15235         }
15236       }
15237     }
15238     SendToProgram("force\n", &first);
15239     if (blackPlaysFirst) {
15240         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15241         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15242         currentMove = forwardMostMove = backwardMostMove = 1;
15243         CopyBoard(boards[1], boards[0]);
15244     } else {
15245         currentMove = forwardMostMove = backwardMostMove = 0;
15246     }
15247     SendBoard(&first, forwardMostMove);
15248     if (appData.debugMode) {
15249         fprintf(debugFP, "EditPosDone\n");
15250     }
15251     DisplayTitle("");
15252     DisplayMessage("", "");
15253     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15254     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15255     gameMode = EditGame;
15256     ModeHighlight();
15257     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15258     ClearHighlights(); /* [AS] */
15259 }
15260
15261 /* Pause for `ms' milliseconds */
15262 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15263 void
15264 TimeDelay (long ms)
15265 {
15266     TimeMark m1, m2;
15267
15268     GetTimeMark(&m1);
15269     do {
15270         GetTimeMark(&m2);
15271     } while (SubtractTimeMarks(&m2, &m1) < ms);
15272 }
15273
15274 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15275 void
15276 SendMultiLineToICS (char *buf)
15277 {
15278     char temp[MSG_SIZ+1], *p;
15279     int len;
15280
15281     len = strlen(buf);
15282     if (len > MSG_SIZ)
15283       len = MSG_SIZ;
15284
15285     strncpy(temp, buf, len);
15286     temp[len] = 0;
15287
15288     p = temp;
15289     while (*p) {
15290         if (*p == '\n' || *p == '\r')
15291           *p = ' ';
15292         ++p;
15293     }
15294
15295     strcat(temp, "\n");
15296     SendToICS(temp);
15297     SendToPlayer(temp, strlen(temp));
15298 }
15299
15300 void
15301 SetWhiteToPlayEvent ()
15302 {
15303     if (gameMode == EditPosition) {
15304         blackPlaysFirst = FALSE;
15305         DisplayBothClocks();    /* works because currentMove is 0 */
15306     } else if (gameMode == IcsExamining) {
15307         SendToICS(ics_prefix);
15308         SendToICS("tomove white\n");
15309     }
15310 }
15311
15312 void
15313 SetBlackToPlayEvent ()
15314 {
15315     if (gameMode == EditPosition) {
15316         blackPlaysFirst = TRUE;
15317         currentMove = 1;        /* kludge */
15318         DisplayBothClocks();
15319         currentMove = 0;
15320     } else if (gameMode == IcsExamining) {
15321         SendToICS(ics_prefix);
15322         SendToICS("tomove black\n");
15323     }
15324 }
15325
15326 void
15327 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15328 {
15329     char buf[MSG_SIZ];
15330     ChessSquare piece = boards[0][y][x];
15331     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15332     static int lastVariant;
15333
15334     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15335
15336     switch (selection) {
15337       case ClearBoard:
15338         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15339         MarkTargetSquares(1);
15340         CopyBoard(currentBoard, boards[0]);
15341         CopyBoard(menuBoard, initialPosition);
15342         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15343             SendToICS(ics_prefix);
15344             SendToICS("bsetup clear\n");
15345         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15346             SendToICS(ics_prefix);
15347             SendToICS("clearboard\n");
15348         } else {
15349             int nonEmpty = 0;
15350             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15351                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15352                 for (y = 0; y < BOARD_HEIGHT; y++) {
15353                     if (gameMode == IcsExamining) {
15354                         if (boards[currentMove][y][x] != EmptySquare) {
15355                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15356                                     AAA + x, ONE + y);
15357                             SendToICS(buf);
15358                         }
15359                     } else if(boards[0][y][x] != DarkSquare) {
15360                         if(boards[0][y][x] != p) nonEmpty++;
15361                         boards[0][y][x] = p;
15362                     }
15363                 }
15364             }
15365             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15366                 int r;
15367                 for(r = 0; r < BOARD_HEIGHT; r++) {
15368                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15369                     ChessSquare p = menuBoard[r][x];
15370                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15371                   }
15372                 }
15373                 DisplayMessage("Clicking clock again restores position", "");
15374                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15375                 if(!nonEmpty) { // asked to clear an empty board
15376                     CopyBoard(boards[0], menuBoard);
15377                 } else
15378                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15379                     CopyBoard(boards[0], initialPosition);
15380                 } else
15381                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15382                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15383                     CopyBoard(boards[0], erasedBoard);
15384                 } else
15385                     CopyBoard(erasedBoard, currentBoard);
15386
15387             }
15388         }
15389         if (gameMode == EditPosition) {
15390             DrawPosition(FALSE, boards[0]);
15391         }
15392         break;
15393
15394       case WhitePlay:
15395         SetWhiteToPlayEvent();
15396         break;
15397
15398       case BlackPlay:
15399         SetBlackToPlayEvent();
15400         break;
15401
15402       case EmptySquare:
15403         if (gameMode == IcsExamining) {
15404             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15405             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15406             SendToICS(buf);
15407         } else {
15408             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15409                 if(x == BOARD_LEFT-2) {
15410                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15411                     boards[0][y][1] = 0;
15412                 } else
15413                 if(x == BOARD_RGHT+1) {
15414                     if(y >= gameInfo.holdingsSize) break;
15415                     boards[0][y][BOARD_WIDTH-2] = 0;
15416                 } else break;
15417             }
15418             boards[0][y][x] = EmptySquare;
15419             DrawPosition(FALSE, boards[0]);
15420         }
15421         break;
15422
15423       case PromotePiece:
15424         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15425            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15426             selection = (ChessSquare) (PROMOTED(piece));
15427         } else if(piece == EmptySquare) selection = WhiteSilver;
15428         else selection = (ChessSquare)((int)piece - 1);
15429         goto defaultlabel;
15430
15431       case DemotePiece:
15432         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15433            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15434             selection = (ChessSquare) (DEMOTED(piece));
15435         } else if(piece == EmptySquare) selection = BlackSilver;
15436         else selection = (ChessSquare)((int)piece + 1);
15437         goto defaultlabel;
15438
15439       case WhiteQueen:
15440       case BlackQueen:
15441         if(gameInfo.variant == VariantShatranj ||
15442            gameInfo.variant == VariantXiangqi  ||
15443            gameInfo.variant == VariantCourier  ||
15444            gameInfo.variant == VariantASEAN    ||
15445            gameInfo.variant == VariantMakruk     )
15446             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15447         goto defaultlabel;
15448
15449       case WhiteKing:
15450       case BlackKing:
15451         if(gameInfo.variant == VariantXiangqi)
15452             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15453         if(gameInfo.variant == VariantKnightmate)
15454             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15455       default:
15456         defaultlabel:
15457         if (gameMode == IcsExamining) {
15458             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15459             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15460                      PieceToChar(selection), AAA + x, ONE + y);
15461             SendToICS(buf);
15462         } else {
15463             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15464                 int n;
15465                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15466                     n = PieceToNumber(selection - BlackPawn);
15467                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15468                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15469                     boards[0][BOARD_HEIGHT-1-n][1]++;
15470                 } else
15471                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15472                     n = PieceToNumber(selection);
15473                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15474                     boards[0][n][BOARD_WIDTH-1] = selection;
15475                     boards[0][n][BOARD_WIDTH-2]++;
15476                 }
15477             } else
15478             boards[0][y][x] = selection;
15479             DrawPosition(TRUE, boards[0]);
15480             ClearHighlights();
15481             fromX = fromY = -1;
15482         }
15483         break;
15484     }
15485 }
15486
15487
15488 void
15489 DropMenuEvent (ChessSquare selection, int x, int y)
15490 {
15491     ChessMove moveType;
15492
15493     switch (gameMode) {
15494       case IcsPlayingWhite:
15495       case MachinePlaysBlack:
15496         if (!WhiteOnMove(currentMove)) {
15497             DisplayMoveError(_("It is Black's turn"));
15498             return;
15499         }
15500         moveType = WhiteDrop;
15501         break;
15502       case IcsPlayingBlack:
15503       case MachinePlaysWhite:
15504         if (WhiteOnMove(currentMove)) {
15505             DisplayMoveError(_("It is White's turn"));
15506             return;
15507         }
15508         moveType = BlackDrop;
15509         break;
15510       case EditGame:
15511         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15512         break;
15513       default:
15514         return;
15515     }
15516
15517     if (moveType == BlackDrop && selection < BlackPawn) {
15518       selection = (ChessSquare) ((int) selection
15519                                  + (int) BlackPawn - (int) WhitePawn);
15520     }
15521     if (boards[currentMove][y][x] != EmptySquare) {
15522         DisplayMoveError(_("That square is occupied"));
15523         return;
15524     }
15525
15526     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15527 }
15528
15529 void
15530 AcceptEvent ()
15531 {
15532     /* Accept a pending offer of any kind from opponent */
15533
15534     if (appData.icsActive) {
15535         SendToICS(ics_prefix);
15536         SendToICS("accept\n");
15537     } else if (cmailMsgLoaded) {
15538         if (currentMove == cmailOldMove &&
15539             commentList[cmailOldMove] != NULL &&
15540             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15541                    "Black offers a draw" : "White offers a draw")) {
15542             TruncateGame();
15543             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15545         } else {
15546             DisplayError(_("There is no pending offer on this move"), 0);
15547             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15548         }
15549     } else {
15550         /* Not used for offers from chess program */
15551     }
15552 }
15553
15554 void
15555 DeclineEvent ()
15556 {
15557     /* Decline a pending offer of any kind from opponent */
15558
15559     if (appData.icsActive) {
15560         SendToICS(ics_prefix);
15561         SendToICS("decline\n");
15562     } else if (cmailMsgLoaded) {
15563         if (currentMove == cmailOldMove &&
15564             commentList[cmailOldMove] != NULL &&
15565             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15566                    "Black offers a draw" : "White offers a draw")) {
15567 #ifdef NOTDEF
15568             AppendComment(cmailOldMove, "Draw declined", TRUE);
15569             DisplayComment(cmailOldMove - 1, "Draw declined");
15570 #endif /*NOTDEF*/
15571         } else {
15572             DisplayError(_("There is no pending offer on this move"), 0);
15573         }
15574     } else {
15575         /* Not used for offers from chess program */
15576     }
15577 }
15578
15579 void
15580 RematchEvent ()
15581 {
15582     /* Issue ICS rematch command */
15583     if (appData.icsActive) {
15584         SendToICS(ics_prefix);
15585         SendToICS("rematch\n");
15586     }
15587 }
15588
15589 void
15590 CallFlagEvent ()
15591 {
15592     /* Call your opponent's flag (claim a win on time) */
15593     if (appData.icsActive) {
15594         SendToICS(ics_prefix);
15595         SendToICS("flag\n");
15596     } else {
15597         switch (gameMode) {
15598           default:
15599             return;
15600           case MachinePlaysWhite:
15601             if (whiteFlag) {
15602                 if (blackFlag)
15603                   GameEnds(GameIsDrawn, "Both players ran out of time",
15604                            GE_PLAYER);
15605                 else
15606                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15607             } else {
15608                 DisplayError(_("Your opponent is not out of time"), 0);
15609             }
15610             break;
15611           case MachinePlaysBlack:
15612             if (blackFlag) {
15613                 if (whiteFlag)
15614                   GameEnds(GameIsDrawn, "Both players ran out of time",
15615                            GE_PLAYER);
15616                 else
15617                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15618             } else {
15619                 DisplayError(_("Your opponent is not out of time"), 0);
15620             }
15621             break;
15622         }
15623     }
15624 }
15625
15626 void
15627 ClockClick (int which)
15628 {       // [HGM] code moved to back-end from winboard.c
15629         if(which) { // black clock
15630           if (gameMode == EditPosition || gameMode == IcsExamining) {
15631             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15632             SetBlackToPlayEvent();
15633           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15634                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15635           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15636           } else if (shiftKey) {
15637             AdjustClock(which, -1);
15638           } else if (gameMode == IcsPlayingWhite ||
15639                      gameMode == MachinePlaysBlack) {
15640             CallFlagEvent();
15641           }
15642         } else { // white clock
15643           if (gameMode == EditPosition || gameMode == IcsExamining) {
15644             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15645             SetWhiteToPlayEvent();
15646           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15647                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15648           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15649           } else if (shiftKey) {
15650             AdjustClock(which, -1);
15651           } else if (gameMode == IcsPlayingBlack ||
15652                    gameMode == MachinePlaysWhite) {
15653             CallFlagEvent();
15654           }
15655         }
15656 }
15657
15658 void
15659 DrawEvent ()
15660 {
15661     /* Offer draw or accept pending draw offer from opponent */
15662
15663     if (appData.icsActive) {
15664         /* Note: tournament rules require draw offers to be
15665            made after you make your move but before you punch
15666            your clock.  Currently ICS doesn't let you do that;
15667            instead, you immediately punch your clock after making
15668            a move, but you can offer a draw at any time. */
15669
15670         SendToICS(ics_prefix);
15671         SendToICS("draw\n");
15672         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15673     } else if (cmailMsgLoaded) {
15674         if (currentMove == cmailOldMove &&
15675             commentList[cmailOldMove] != NULL &&
15676             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15677                    "Black offers a draw" : "White offers a draw")) {
15678             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15679             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15680         } else if (currentMove == cmailOldMove + 1) {
15681             char *offer = WhiteOnMove(cmailOldMove) ?
15682               "White offers a draw" : "Black offers a draw";
15683             AppendComment(currentMove, offer, TRUE);
15684             DisplayComment(currentMove - 1, offer);
15685             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15686         } else {
15687             DisplayError(_("You must make your move before offering a draw"), 0);
15688             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15689         }
15690     } else if (first.offeredDraw) {
15691         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15692     } else {
15693         if (first.sendDrawOffers) {
15694             SendToProgram("draw\n", &first);
15695             userOfferedDraw = TRUE;
15696         }
15697     }
15698 }
15699
15700 void
15701 AdjournEvent ()
15702 {
15703     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15704
15705     if (appData.icsActive) {
15706         SendToICS(ics_prefix);
15707         SendToICS("adjourn\n");
15708     } else {
15709         /* Currently GNU Chess doesn't offer or accept Adjourns */
15710     }
15711 }
15712
15713
15714 void
15715 AbortEvent ()
15716 {
15717     /* Offer Abort or accept pending Abort offer from opponent */
15718
15719     if (appData.icsActive) {
15720         SendToICS(ics_prefix);
15721         SendToICS("abort\n");
15722     } else {
15723         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15724     }
15725 }
15726
15727 void
15728 ResignEvent ()
15729 {
15730     /* Resign.  You can do this even if it's not your turn. */
15731
15732     if (appData.icsActive) {
15733         SendToICS(ics_prefix);
15734         SendToICS("resign\n");
15735     } else {
15736         switch (gameMode) {
15737           case MachinePlaysWhite:
15738             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15739             break;
15740           case MachinePlaysBlack:
15741             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15742             break;
15743           case EditGame:
15744             if (cmailMsgLoaded) {
15745                 TruncateGame();
15746                 if (WhiteOnMove(cmailOldMove)) {
15747                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15748                 } else {
15749                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15750                 }
15751                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15752             }
15753             break;
15754           default:
15755             break;
15756         }
15757     }
15758 }
15759
15760
15761 void
15762 StopObservingEvent ()
15763 {
15764     /* Stop observing current games */
15765     SendToICS(ics_prefix);
15766     SendToICS("unobserve\n");
15767 }
15768
15769 void
15770 StopExaminingEvent ()
15771 {
15772     /* Stop observing current game */
15773     SendToICS(ics_prefix);
15774     SendToICS("unexamine\n");
15775 }
15776
15777 void
15778 ForwardInner (int target)
15779 {
15780     int limit; int oldSeekGraphUp = seekGraphUp;
15781
15782     if (appData.debugMode)
15783         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15784                 target, currentMove, forwardMostMove);
15785
15786     if (gameMode == EditPosition)
15787       return;
15788
15789     seekGraphUp = FALSE;
15790     MarkTargetSquares(1);
15791     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15792
15793     if (gameMode == PlayFromGameFile && !pausing)
15794       PauseEvent();
15795
15796     if (gameMode == IcsExamining && pausing)
15797       limit = pauseExamForwardMostMove;
15798     else
15799       limit = forwardMostMove;
15800
15801     if (target > limit) target = limit;
15802
15803     if (target > 0 && moveList[target - 1][0]) {
15804         int fromX, fromY, toX, toY;
15805         toX = moveList[target - 1][2] - AAA;
15806         toY = moveList[target - 1][3] - ONE;
15807         if (moveList[target - 1][1] == '@') {
15808             if (appData.highlightLastMove) {
15809                 SetHighlights(-1, -1, toX, toY);
15810             }
15811         } else {
15812             int viaX = moveList[target - 1][5] - AAA;
15813             int viaY = moveList[target - 1][6] - ONE;
15814             fromX = moveList[target - 1][0] - AAA;
15815             fromY = moveList[target - 1][1] - ONE;
15816             if (target == currentMove + 1) {
15817                 if(moveList[target - 1][4] == ';') { // multi-leg
15818                     ChessSquare piece = boards[currentMove][viaY][viaX];
15819                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15820                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15821                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15822                     boards[currentMove][viaY][viaX] = piece;
15823                 } else
15824                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15825             }
15826             if (appData.highlightLastMove) {
15827                 SetHighlights(fromX, fromY, toX, toY);
15828             }
15829         }
15830     }
15831     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15832         gameMode == Training || gameMode == PlayFromGameFile ||
15833         gameMode == AnalyzeFile) {
15834         while (currentMove < target) {
15835             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15836             SendMoveToProgram(currentMove++, &first);
15837         }
15838     } else {
15839         currentMove = target;
15840     }
15841
15842     if (gameMode == EditGame || gameMode == EndOfGame) {
15843         whiteTimeRemaining = timeRemaining[0][currentMove];
15844         blackTimeRemaining = timeRemaining[1][currentMove];
15845     }
15846     DisplayBothClocks();
15847     DisplayMove(currentMove - 1);
15848     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15849     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15850     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15851         DisplayComment(currentMove - 1, commentList[currentMove]);
15852     }
15853     ClearMap(); // [HGM] exclude: invalidate map
15854 }
15855
15856
15857 void
15858 ForwardEvent ()
15859 {
15860     if (gameMode == IcsExamining && !pausing) {
15861         SendToICS(ics_prefix);
15862         SendToICS("forward\n");
15863     } else {
15864         ForwardInner(currentMove + 1);
15865     }
15866 }
15867
15868 void
15869 ToEndEvent ()
15870 {
15871     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15872         /* to optimze, we temporarily turn off analysis mode while we feed
15873          * the remaining moves to the engine. Otherwise we get analysis output
15874          * after each move.
15875          */
15876         if (first.analysisSupport) {
15877           SendToProgram("exit\nforce\n", &first);
15878           first.analyzing = FALSE;
15879         }
15880     }
15881
15882     if (gameMode == IcsExamining && !pausing) {
15883         SendToICS(ics_prefix);
15884         SendToICS("forward 999999\n");
15885     } else {
15886         ForwardInner(forwardMostMove);
15887     }
15888
15889     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15890         /* we have fed all the moves, so reactivate analysis mode */
15891         SendToProgram("analyze\n", &first);
15892         first.analyzing = TRUE;
15893         /*first.maybeThinking = TRUE;*/
15894         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15895     }
15896 }
15897
15898 void
15899 BackwardInner (int target)
15900 {
15901     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15902
15903     if (appData.debugMode)
15904         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15905                 target, currentMove, forwardMostMove);
15906
15907     if (gameMode == EditPosition) return;
15908     seekGraphUp = FALSE;
15909     MarkTargetSquares(1);
15910     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15911     if (currentMove <= backwardMostMove) {
15912         ClearHighlights();
15913         DrawPosition(full_redraw, boards[currentMove]);
15914         return;
15915     }
15916     if (gameMode == PlayFromGameFile && !pausing)
15917       PauseEvent();
15918
15919     if (moveList[target][0]) {
15920         int fromX, fromY, toX, toY;
15921         toX = moveList[target][2] - AAA;
15922         toY = moveList[target][3] - ONE;
15923         if (moveList[target][1] == '@') {
15924             if (appData.highlightLastMove) {
15925                 SetHighlights(-1, -1, toX, toY);
15926             }
15927         } else {
15928             fromX = moveList[target][0] - AAA;
15929             fromY = moveList[target][1] - ONE;
15930             if (target == currentMove - 1) {
15931                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15932             }
15933             if (appData.highlightLastMove) {
15934                 SetHighlights(fromX, fromY, toX, toY);
15935             }
15936         }
15937     }
15938     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15939         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15940         while (currentMove > target) {
15941             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15942                 // null move cannot be undone. Reload program with move history before it.
15943                 int i;
15944                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15945                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15946                 }
15947                 SendBoard(&first, i);
15948               if(second.analyzing) SendBoard(&second, i);
15949                 for(currentMove=i; currentMove<target; currentMove++) {
15950                     SendMoveToProgram(currentMove, &first);
15951                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15952                 }
15953                 break;
15954             }
15955             SendToBoth("undo\n");
15956             currentMove--;
15957         }
15958     } else {
15959         currentMove = target;
15960     }
15961
15962     if (gameMode == EditGame || gameMode == EndOfGame) {
15963         whiteTimeRemaining = timeRemaining[0][currentMove];
15964         blackTimeRemaining = timeRemaining[1][currentMove];
15965     }
15966     DisplayBothClocks();
15967     DisplayMove(currentMove - 1);
15968     DrawPosition(full_redraw, boards[currentMove]);
15969     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15970     // [HGM] PV info: routine tests if comment empty
15971     DisplayComment(currentMove - 1, commentList[currentMove]);
15972     ClearMap(); // [HGM] exclude: invalidate map
15973 }
15974
15975 void
15976 BackwardEvent ()
15977 {
15978     if (gameMode == IcsExamining && !pausing) {
15979         SendToICS(ics_prefix);
15980         SendToICS("backward\n");
15981     } else {
15982         BackwardInner(currentMove - 1);
15983     }
15984 }
15985
15986 void
15987 ToStartEvent ()
15988 {
15989     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15990         /* to optimize, we temporarily turn off analysis mode while we undo
15991          * all the moves. Otherwise we get analysis output after each undo.
15992          */
15993         if (first.analysisSupport) {
15994           SendToProgram("exit\nforce\n", &first);
15995           first.analyzing = FALSE;
15996         }
15997     }
15998
15999     if (gameMode == IcsExamining && !pausing) {
16000         SendToICS(ics_prefix);
16001         SendToICS("backward 999999\n");
16002     } else {
16003         BackwardInner(backwardMostMove);
16004     }
16005
16006     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16007         /* we have fed all the moves, so reactivate analysis mode */
16008         SendToProgram("analyze\n", &first);
16009         first.analyzing = TRUE;
16010         /*first.maybeThinking = TRUE;*/
16011         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16012     }
16013 }
16014
16015 void
16016 ToNrEvent (int to)
16017 {
16018   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16019   if (to >= forwardMostMove) to = forwardMostMove;
16020   if (to <= backwardMostMove) to = backwardMostMove;
16021   if (to < currentMove) {
16022     BackwardInner(to);
16023   } else {
16024     ForwardInner(to);
16025   }
16026 }
16027
16028 void
16029 RevertEvent (Boolean annotate)
16030 {
16031     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16032         return;
16033     }
16034     if (gameMode != IcsExamining) {
16035         DisplayError(_("You are not examining a game"), 0);
16036         return;
16037     }
16038     if (pausing) {
16039         DisplayError(_("You can't revert while pausing"), 0);
16040         return;
16041     }
16042     SendToICS(ics_prefix);
16043     SendToICS("revert\n");
16044 }
16045
16046 void
16047 RetractMoveEvent ()
16048 {
16049     switch (gameMode) {
16050       case MachinePlaysWhite:
16051       case MachinePlaysBlack:
16052         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16053             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16054             return;
16055         }
16056         if (forwardMostMove < 2) return;
16057         currentMove = forwardMostMove = forwardMostMove - 2;
16058         whiteTimeRemaining = timeRemaining[0][currentMove];
16059         blackTimeRemaining = timeRemaining[1][currentMove];
16060         DisplayBothClocks();
16061         DisplayMove(currentMove - 1);
16062         ClearHighlights();/*!! could figure this out*/
16063         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16064         SendToProgram("remove\n", &first);
16065         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16066         break;
16067
16068       case BeginningOfGame:
16069       default:
16070         break;
16071
16072       case IcsPlayingWhite:
16073       case IcsPlayingBlack:
16074         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16075             SendToICS(ics_prefix);
16076             SendToICS("takeback 2\n");
16077         } else {
16078             SendToICS(ics_prefix);
16079             SendToICS("takeback 1\n");
16080         }
16081         break;
16082     }
16083 }
16084
16085 void
16086 MoveNowEvent ()
16087 {
16088     ChessProgramState *cps;
16089
16090     switch (gameMode) {
16091       case MachinePlaysWhite:
16092         if (!WhiteOnMove(forwardMostMove)) {
16093             DisplayError(_("It is your turn"), 0);
16094             return;
16095         }
16096         cps = &first;
16097         break;
16098       case MachinePlaysBlack:
16099         if (WhiteOnMove(forwardMostMove)) {
16100             DisplayError(_("It is your turn"), 0);
16101             return;
16102         }
16103         cps = &first;
16104         break;
16105       case TwoMachinesPlay:
16106         if (WhiteOnMove(forwardMostMove) ==
16107             (first.twoMachinesColor[0] == 'w')) {
16108             cps = &first;
16109         } else {
16110             cps = &second;
16111         }
16112         break;
16113       case BeginningOfGame:
16114       default:
16115         return;
16116     }
16117     SendToProgram("?\n", cps);
16118 }
16119
16120 void
16121 TruncateGameEvent ()
16122 {
16123     EditGameEvent();
16124     if (gameMode != EditGame) return;
16125     TruncateGame();
16126 }
16127
16128 void
16129 TruncateGame ()
16130 {
16131     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16132     if (forwardMostMove > currentMove) {
16133         if (gameInfo.resultDetails != NULL) {
16134             free(gameInfo.resultDetails);
16135             gameInfo.resultDetails = NULL;
16136             gameInfo.result = GameUnfinished;
16137         }
16138         forwardMostMove = currentMove;
16139         HistorySet(parseList, backwardMostMove, forwardMostMove,
16140                    currentMove-1);
16141     }
16142 }
16143
16144 void
16145 HintEvent ()
16146 {
16147     if (appData.noChessProgram) return;
16148     switch (gameMode) {
16149       case MachinePlaysWhite:
16150         if (WhiteOnMove(forwardMostMove)) {
16151             DisplayError(_("Wait until your turn."), 0);
16152             return;
16153         }
16154         break;
16155       case BeginningOfGame:
16156       case MachinePlaysBlack:
16157         if (!WhiteOnMove(forwardMostMove)) {
16158             DisplayError(_("Wait until your turn."), 0);
16159             return;
16160         }
16161         break;
16162       default:
16163         DisplayError(_("No hint available"), 0);
16164         return;
16165     }
16166     SendToProgram("hint\n", &first);
16167     hintRequested = TRUE;
16168 }
16169
16170 int
16171 SaveSelected (FILE *g, int dummy, char *dummy2)
16172 {
16173     ListGame * lg = (ListGame *) gameList.head;
16174     int nItem, cnt=0;
16175     FILE *f;
16176
16177     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16178         DisplayError(_("Game list not loaded or empty"), 0);
16179         return 0;
16180     }
16181
16182     creatingBook = TRUE; // suppresses stuff during load game
16183
16184     /* Get list size */
16185     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16186         if(lg->position >= 0) { // selected?
16187             LoadGame(f, nItem, "", TRUE);
16188             SaveGamePGN2(g); // leaves g open
16189             cnt++; DoEvents();
16190         }
16191         lg = (ListGame *) lg->node.succ;
16192     }
16193
16194     fclose(g);
16195     creatingBook = FALSE;
16196
16197     return cnt;
16198 }
16199
16200 void
16201 CreateBookEvent ()
16202 {
16203     ListGame * lg = (ListGame *) gameList.head;
16204     FILE *f, *g;
16205     int nItem;
16206     static int secondTime = FALSE;
16207
16208     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16209         DisplayError(_("Game list not loaded or empty"), 0);
16210         return;
16211     }
16212
16213     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16214         fclose(g);
16215         secondTime++;
16216         DisplayNote(_("Book file exists! Try again for overwrite."));
16217         return;
16218     }
16219
16220     creatingBook = TRUE;
16221     secondTime = FALSE;
16222
16223     /* Get list size */
16224     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16225         if(lg->position >= 0) {
16226             LoadGame(f, nItem, "", TRUE);
16227             AddGameToBook(TRUE);
16228             DoEvents();
16229         }
16230         lg = (ListGame *) lg->node.succ;
16231     }
16232
16233     creatingBook = FALSE;
16234     FlushBook();
16235 }
16236
16237 void
16238 BookEvent ()
16239 {
16240     if (appData.noChessProgram) return;
16241     switch (gameMode) {
16242       case MachinePlaysWhite:
16243         if (WhiteOnMove(forwardMostMove)) {
16244             DisplayError(_("Wait until your turn."), 0);
16245             return;
16246         }
16247         break;
16248       case BeginningOfGame:
16249       case MachinePlaysBlack:
16250         if (!WhiteOnMove(forwardMostMove)) {
16251             DisplayError(_("Wait until your turn."), 0);
16252             return;
16253         }
16254         break;
16255       case EditPosition:
16256         EditPositionDone(TRUE);
16257         break;
16258       case TwoMachinesPlay:
16259         return;
16260       default:
16261         break;
16262     }
16263     SendToProgram("bk\n", &first);
16264     bookOutput[0] = NULLCHAR;
16265     bookRequested = TRUE;
16266 }
16267
16268 void
16269 AboutGameEvent ()
16270 {
16271     char *tags = PGNTags(&gameInfo);
16272     TagsPopUp(tags, CmailMsg());
16273     free(tags);
16274 }
16275
16276 /* end button procedures */
16277
16278 void
16279 PrintPosition (FILE *fp, int move)
16280 {
16281     int i, j;
16282
16283     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16284         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16285             char c = PieceToChar(boards[move][i][j]);
16286             fputc(c == 'x' ? '.' : c, fp);
16287             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16288         }
16289     }
16290     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16291       fprintf(fp, "white to play\n");
16292     else
16293       fprintf(fp, "black to play\n");
16294 }
16295
16296 void
16297 PrintOpponents (FILE *fp)
16298 {
16299     if (gameInfo.white != NULL) {
16300         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16301     } else {
16302         fprintf(fp, "\n");
16303     }
16304 }
16305
16306 /* Find last component of program's own name, using some heuristics */
16307 void
16308 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16309 {
16310     char *p, *q, c;
16311     int local = (strcmp(host, "localhost") == 0);
16312     while (!local && (p = strchr(prog, ';')) != NULL) {
16313         p++;
16314         while (*p == ' ') p++;
16315         prog = p;
16316     }
16317     if (*prog == '"' || *prog == '\'') {
16318         q = strchr(prog + 1, *prog);
16319     } else {
16320         q = strchr(prog, ' ');
16321     }
16322     if (q == NULL) q = prog + strlen(prog);
16323     p = q;
16324     while (p >= prog && *p != '/' && *p != '\\') p--;
16325     p++;
16326     if(p == prog && *p == '"') p++;
16327     c = *q; *q = 0;
16328     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16329     memcpy(buf, p, q - p);
16330     buf[q - p] = NULLCHAR;
16331     if (!local) {
16332         strcat(buf, "@");
16333         strcat(buf, host);
16334     }
16335 }
16336
16337 char *
16338 TimeControlTagValue ()
16339 {
16340     char buf[MSG_SIZ];
16341     if (!appData.clockMode) {
16342       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16343     } else if (movesPerSession > 0) {
16344       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16345     } else if (timeIncrement == 0) {
16346       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16347     } else {
16348       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16349     }
16350     return StrSave(buf);
16351 }
16352
16353 void
16354 SetGameInfo ()
16355 {
16356     /* This routine is used only for certain modes */
16357     VariantClass v = gameInfo.variant;
16358     ChessMove r = GameUnfinished;
16359     char *p = NULL;
16360
16361     if(keepInfo) return;
16362
16363     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16364         r = gameInfo.result;
16365         p = gameInfo.resultDetails;
16366         gameInfo.resultDetails = NULL;
16367     }
16368     ClearGameInfo(&gameInfo);
16369     gameInfo.variant = v;
16370
16371     switch (gameMode) {
16372       case MachinePlaysWhite:
16373         gameInfo.event = StrSave( appData.pgnEventHeader );
16374         gameInfo.site = StrSave(HostName());
16375         gameInfo.date = PGNDate();
16376         gameInfo.round = StrSave("-");
16377         gameInfo.white = StrSave(first.tidy);
16378         gameInfo.black = StrSave(UserName());
16379         gameInfo.timeControl = TimeControlTagValue();
16380         break;
16381
16382       case MachinePlaysBlack:
16383         gameInfo.event = StrSave( appData.pgnEventHeader );
16384         gameInfo.site = StrSave(HostName());
16385         gameInfo.date = PGNDate();
16386         gameInfo.round = StrSave("-");
16387         gameInfo.white = StrSave(UserName());
16388         gameInfo.black = StrSave(first.tidy);
16389         gameInfo.timeControl = TimeControlTagValue();
16390         break;
16391
16392       case TwoMachinesPlay:
16393         gameInfo.event = StrSave( appData.pgnEventHeader );
16394         gameInfo.site = StrSave(HostName());
16395         gameInfo.date = PGNDate();
16396         if (roundNr > 0) {
16397             char buf[MSG_SIZ];
16398             snprintf(buf, MSG_SIZ, "%d", roundNr);
16399             gameInfo.round = StrSave(buf);
16400         } else {
16401             gameInfo.round = StrSave("-");
16402         }
16403         if (first.twoMachinesColor[0] == 'w') {
16404             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16405             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16406         } else {
16407             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16408             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16409         }
16410         gameInfo.timeControl = TimeControlTagValue();
16411         break;
16412
16413       case EditGame:
16414         gameInfo.event = StrSave("Edited game");
16415         gameInfo.site = StrSave(HostName());
16416         gameInfo.date = PGNDate();
16417         gameInfo.round = StrSave("-");
16418         gameInfo.white = StrSave("-");
16419         gameInfo.black = StrSave("-");
16420         gameInfo.result = r;
16421         gameInfo.resultDetails = p;
16422         break;
16423
16424       case EditPosition:
16425         gameInfo.event = StrSave("Edited position");
16426         gameInfo.site = StrSave(HostName());
16427         gameInfo.date = PGNDate();
16428         gameInfo.round = StrSave("-");
16429         gameInfo.white = StrSave("-");
16430         gameInfo.black = StrSave("-");
16431         break;
16432
16433       case IcsPlayingWhite:
16434       case IcsPlayingBlack:
16435       case IcsObserving:
16436       case IcsExamining:
16437         break;
16438
16439       case PlayFromGameFile:
16440         gameInfo.event = StrSave("Game from non-PGN file");
16441         gameInfo.site = StrSave(HostName());
16442         gameInfo.date = PGNDate();
16443         gameInfo.round = StrSave("-");
16444         gameInfo.white = StrSave("?");
16445         gameInfo.black = StrSave("?");
16446         break;
16447
16448       default:
16449         break;
16450     }
16451 }
16452
16453 void
16454 ReplaceComment (int index, char *text)
16455 {
16456     int len;
16457     char *p;
16458     float score;
16459
16460     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16461        pvInfoList[index-1].depth == len &&
16462        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16463        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16464     while (*text == '\n') text++;
16465     len = strlen(text);
16466     while (len > 0 && text[len - 1] == '\n') len--;
16467
16468     if (commentList[index] != NULL)
16469       free(commentList[index]);
16470
16471     if (len == 0) {
16472         commentList[index] = NULL;
16473         return;
16474     }
16475   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16476       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16477       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16478     commentList[index] = (char *) malloc(len + 2);
16479     strncpy(commentList[index], text, len);
16480     commentList[index][len] = '\n';
16481     commentList[index][len + 1] = NULLCHAR;
16482   } else {
16483     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16484     char *p;
16485     commentList[index] = (char *) malloc(len + 7);
16486     safeStrCpy(commentList[index], "{\n", 3);
16487     safeStrCpy(commentList[index]+2, text, len+1);
16488     commentList[index][len+2] = NULLCHAR;
16489     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16490     strcat(commentList[index], "\n}\n");
16491   }
16492 }
16493
16494 void
16495 CrushCRs (char *text)
16496 {
16497   char *p = text;
16498   char *q = text;
16499   char ch;
16500
16501   do {
16502     ch = *p++;
16503     if (ch == '\r') continue;
16504     *q++ = ch;
16505   } while (ch != '\0');
16506 }
16507
16508 void
16509 AppendComment (int index, char *text, Boolean addBraces)
16510 /* addBraces  tells if we should add {} */
16511 {
16512     int oldlen, len;
16513     char *old;
16514
16515 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16516     if(addBraces == 3) addBraces = 0; else // force appending literally
16517     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16518
16519     CrushCRs(text);
16520     while (*text == '\n') text++;
16521     len = strlen(text);
16522     while (len > 0 && text[len - 1] == '\n') len--;
16523     text[len] = NULLCHAR;
16524
16525     if (len == 0) return;
16526
16527     if (commentList[index] != NULL) {
16528       Boolean addClosingBrace = addBraces;
16529         old = commentList[index];
16530         oldlen = strlen(old);
16531         while(commentList[index][oldlen-1] ==  '\n')
16532           commentList[index][--oldlen] = NULLCHAR;
16533         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16534         safeStrCpy(commentList[index], old, oldlen + len + 6);
16535         free(old);
16536         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16537         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16538           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16539           while (*text == '\n') { text++; len--; }
16540           commentList[index][--oldlen] = NULLCHAR;
16541       }
16542         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16543         else          strcat(commentList[index], "\n");
16544         strcat(commentList[index], text);
16545         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16546         else          strcat(commentList[index], "\n");
16547     } else {
16548         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16549         if(addBraces)
16550           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16551         else commentList[index][0] = NULLCHAR;
16552         strcat(commentList[index], text);
16553         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16554         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16555     }
16556 }
16557
16558 static char *
16559 FindStr (char * text, char * sub_text)
16560 {
16561     char * result = strstr( text, sub_text );
16562
16563     if( result != NULL ) {
16564         result += strlen( sub_text );
16565     }
16566
16567     return result;
16568 }
16569
16570 /* [AS] Try to extract PV info from PGN comment */
16571 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16572 char *
16573 GetInfoFromComment (int index, char * text)
16574 {
16575     char * sep = text, *p;
16576
16577     if( text != NULL && index > 0 ) {
16578         int score = 0;
16579         int depth = 0;
16580         int time = -1, sec = 0, deci;
16581         char * s_eval = FindStr( text, "[%eval " );
16582         char * s_emt = FindStr( text, "[%emt " );
16583 #if 0
16584         if( s_eval != NULL || s_emt != NULL ) {
16585 #else
16586         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16587 #endif
16588             /* New style */
16589             char delim;
16590
16591             if( s_eval != NULL ) {
16592                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16593                     return text;
16594                 }
16595
16596                 if( delim != ']' ) {
16597                     return text;
16598                 }
16599             }
16600
16601             if( s_emt != NULL ) {
16602             }
16603                 return text;
16604         }
16605         else {
16606             /* We expect something like: [+|-]nnn.nn/dd */
16607             int score_lo = 0;
16608
16609             if(*text != '{') return text; // [HGM] braces: must be normal comment
16610
16611             sep = strchr( text, '/' );
16612             if( sep == NULL || sep < (text+4) ) {
16613                 return text;
16614             }
16615
16616             p = text;
16617             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16618             if(p[1] == '(') { // comment starts with PV
16619                p = strchr(p, ')'); // locate end of PV
16620                if(p == NULL || sep < p+5) return text;
16621                // at this point we have something like "{(.*) +0.23/6 ..."
16622                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16623                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16624                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16625             }
16626             time = -1; sec = -1; deci = -1;
16627             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16628                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16629                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16630                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16631                 return text;
16632             }
16633
16634             if( score_lo < 0 || score_lo >= 100 ) {
16635                 return text;
16636             }
16637
16638             if(sec >= 0) time = 600*time + 10*sec; else
16639             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16640
16641             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16642
16643             /* [HGM] PV time: now locate end of PV info */
16644             while( *++sep >= '0' && *sep <= '9'); // strip depth
16645             if(time >= 0)
16646             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16647             if(sec >= 0)
16648             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16649             if(deci >= 0)
16650             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16651             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16652         }
16653
16654         if( depth <= 0 ) {
16655             return text;
16656         }
16657
16658         if( time < 0 ) {
16659             time = -1;
16660         }
16661
16662         pvInfoList[index-1].depth = depth;
16663         pvInfoList[index-1].score = score;
16664         pvInfoList[index-1].time  = 10*time; // centi-sec
16665         if(*sep == '}') *sep = 0; else *--sep = '{';
16666         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16667     }
16668     return sep;
16669 }
16670
16671 void
16672 SendToProgram (char *message, ChessProgramState *cps)
16673 {
16674     int count, outCount, error;
16675     char buf[MSG_SIZ];
16676
16677     if (cps->pr == NoProc) return;
16678     Attention(cps);
16679
16680     if (appData.debugMode) {
16681         TimeMark now;
16682         GetTimeMark(&now);
16683         fprintf(debugFP, "%ld >%-6s: %s",
16684                 SubtractTimeMarks(&now, &programStartTime),
16685                 cps->which, message);
16686         if(serverFP)
16687             fprintf(serverFP, "%ld >%-6s: %s",
16688                 SubtractTimeMarks(&now, &programStartTime),
16689                 cps->which, message), fflush(serverFP);
16690     }
16691
16692     count = strlen(message);
16693     outCount = OutputToProcess(cps->pr, message, count, &error);
16694     if (outCount < count && !exiting
16695                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16696       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16697       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16698         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16699             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16700                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16701                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16702                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16703             } else {
16704                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16705                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16706                 gameInfo.result = res;
16707             }
16708             gameInfo.resultDetails = StrSave(buf);
16709         }
16710         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16711         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16712     }
16713 }
16714
16715 void
16716 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16717 {
16718     char *end_str;
16719     char buf[MSG_SIZ];
16720     ChessProgramState *cps = (ChessProgramState *)closure;
16721
16722     if (isr != cps->isr) return; /* Killed intentionally */
16723     if (count <= 0) {
16724         if (count == 0) {
16725             RemoveInputSource(cps->isr);
16726             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16727                     _(cps->which), cps->program);
16728             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16729             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16730                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16731                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16732                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16733                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16734                 } else {
16735                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16736                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16737                     gameInfo.result = res;
16738                 }
16739                 gameInfo.resultDetails = StrSave(buf);
16740             }
16741             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16742             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16743         } else {
16744             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16745                     _(cps->which), cps->program);
16746             RemoveInputSource(cps->isr);
16747
16748             /* [AS] Program is misbehaving badly... kill it */
16749             if( count == -2 ) {
16750                 DestroyChildProcess( cps->pr, 9 );
16751                 cps->pr = NoProc;
16752             }
16753
16754             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16755         }
16756         return;
16757     }
16758
16759     if ((end_str = strchr(message, '\r')) != NULL)
16760       *end_str = NULLCHAR;
16761     if ((end_str = strchr(message, '\n')) != NULL)
16762       *end_str = NULLCHAR;
16763
16764     if (appData.debugMode) {
16765         TimeMark now; int print = 1;
16766         char *quote = ""; char c; int i;
16767
16768         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16769                 char start = message[0];
16770                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16771                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16772                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16773                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16774                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16775                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16776                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16777                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16778                    sscanf(message, "hint: %c", &c)!=1 &&
16779                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16780                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16781                     print = (appData.engineComments >= 2);
16782                 }
16783                 message[0] = start; // restore original message
16784         }
16785         if(print) {
16786                 GetTimeMark(&now);
16787                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16788                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16789                         quote,
16790                         message);
16791                 if(serverFP)
16792                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16793                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16794                         quote,
16795                         message), fflush(serverFP);
16796         }
16797     }
16798
16799     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16800     if (appData.icsEngineAnalyze) {
16801         if (strstr(message, "whisper") != NULL ||
16802              strstr(message, "kibitz") != NULL ||
16803             strstr(message, "tellics") != NULL) return;
16804     }
16805
16806     HandleMachineMove(message, cps);
16807 }
16808
16809
16810 void
16811 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16812 {
16813     char buf[MSG_SIZ];
16814     int seconds;
16815
16816     if( timeControl_2 > 0 ) {
16817         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16818             tc = timeControl_2;
16819         }
16820     }
16821     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16822     inc /= cps->timeOdds;
16823     st  /= cps->timeOdds;
16824
16825     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16826
16827     if (st > 0) {
16828       /* Set exact time per move, normally using st command */
16829       if (cps->stKludge) {
16830         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16831         seconds = st % 60;
16832         if (seconds == 0) {
16833           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16834         } else {
16835           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16836         }
16837       } else {
16838         snprintf(buf, MSG_SIZ, "st %d\n", st);
16839       }
16840     } else {
16841       /* Set conventional or incremental time control, using level command */
16842       if (seconds == 0) {
16843         /* Note old gnuchess bug -- minutes:seconds used to not work.
16844            Fixed in later versions, but still avoid :seconds
16845            when seconds is 0. */
16846         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16847       } else {
16848         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16849                  seconds, inc/1000.);
16850       }
16851     }
16852     SendToProgram(buf, cps);
16853
16854     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16855     /* Orthogonally, limit search to given depth */
16856     if (sd > 0) {
16857       if (cps->sdKludge) {
16858         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16859       } else {
16860         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16861       }
16862       SendToProgram(buf, cps);
16863     }
16864
16865     if(cps->nps >= 0) { /* [HGM] nps */
16866         if(cps->supportsNPS == FALSE)
16867           cps->nps = -1; // don't use if engine explicitly says not supported!
16868         else {
16869           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16870           SendToProgram(buf, cps);
16871         }
16872     }
16873 }
16874
16875 ChessProgramState *
16876 WhitePlayer ()
16877 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16878 {
16879     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16880        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16881         return &second;
16882     return &first;
16883 }
16884
16885 void
16886 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16887 {
16888     char message[MSG_SIZ];
16889     long time, otime;
16890
16891     /* Note: this routine must be called when the clocks are stopped
16892        or when they have *just* been set or switched; otherwise
16893        it will be off by the time since the current tick started.
16894     */
16895     if (machineWhite) {
16896         time = whiteTimeRemaining / 10;
16897         otime = blackTimeRemaining / 10;
16898     } else {
16899         time = blackTimeRemaining / 10;
16900         otime = whiteTimeRemaining / 10;
16901     }
16902     /* [HGM] translate opponent's time by time-odds factor */
16903     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16904
16905     if (time <= 0) time = 1;
16906     if (otime <= 0) otime = 1;
16907
16908     snprintf(message, MSG_SIZ, "time %ld\n", time);
16909     SendToProgram(message, cps);
16910
16911     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16912     SendToProgram(message, cps);
16913 }
16914
16915 char *
16916 EngineDefinedVariant (ChessProgramState *cps, int n)
16917 {   // return name of n-th unknown variant that engine supports
16918     static char buf[MSG_SIZ];
16919     char *p, *s = cps->variants;
16920     if(!s) return NULL;
16921     do { // parse string from variants feature
16922       VariantClass v;
16923         p = strchr(s, ',');
16924         if(p) *p = NULLCHAR;
16925       v = StringToVariant(s);
16926       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16927         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16928             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16929                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16930                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16931                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16932             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16933         }
16934         if(p) *p++ = ',';
16935         if(n < 0) return buf;
16936     } while(s = p);
16937     return NULL;
16938 }
16939
16940 int
16941 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16942 {
16943   char buf[MSG_SIZ];
16944   int len = strlen(name);
16945   int val;
16946
16947   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16948     (*p) += len + 1;
16949     sscanf(*p, "%d", &val);
16950     *loc = (val != 0);
16951     while (**p && **p != ' ')
16952       (*p)++;
16953     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16954     SendToProgram(buf, cps);
16955     return TRUE;
16956   }
16957   return FALSE;
16958 }
16959
16960 int
16961 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16962 {
16963   char buf[MSG_SIZ];
16964   int len = strlen(name);
16965   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16966     (*p) += len + 1;
16967     sscanf(*p, "%d", loc);
16968     while (**p && **p != ' ') (*p)++;
16969     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16970     SendToProgram(buf, cps);
16971     return TRUE;
16972   }
16973   return FALSE;
16974 }
16975
16976 int
16977 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16978 {
16979   char buf[MSG_SIZ];
16980   int len = strlen(name);
16981   if (strncmp((*p), name, len) == 0
16982       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16983     (*p) += len + 2;
16984     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16985     sscanf(*p, "%[^\"]", *loc);
16986     while (**p && **p != '\"') (*p)++;
16987     if (**p == '\"') (*p)++;
16988     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16989     SendToProgram(buf, cps);
16990     return TRUE;
16991   }
16992   return FALSE;
16993 }
16994
16995 int
16996 ParseOption (Option *opt, ChessProgramState *cps)
16997 // [HGM] options: process the string that defines an engine option, and determine
16998 // name, type, default value, and allowed value range
16999 {
17000         char *p, *q, buf[MSG_SIZ];
17001         int n, min = (-1)<<31, max = 1<<31, def;
17002
17003         opt->target = &opt->value;   // OK for spin/slider and checkbox
17004         if(p = strstr(opt->name, " -spin ")) {
17005             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17006             if(max < min) max = min; // enforce consistency
17007             if(def < min) def = min;
17008             if(def > max) def = max;
17009             opt->value = def;
17010             opt->min = min;
17011             opt->max = max;
17012             opt->type = Spin;
17013         } else if((p = strstr(opt->name, " -slider "))) {
17014             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17015             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17016             if(max < min) max = min; // enforce consistency
17017             if(def < min) def = min;
17018             if(def > max) def = max;
17019             opt->value = def;
17020             opt->min = min;
17021             opt->max = max;
17022             opt->type = Spin; // Slider;
17023         } else if((p = strstr(opt->name, " -string "))) {
17024             opt->textValue = p+9;
17025             opt->type = TextBox;
17026             opt->target = &opt->textValue;
17027         } else if((p = strstr(opt->name, " -file "))) {
17028             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17029             opt->target = opt->textValue = p+7;
17030             opt->type = FileName; // FileName;
17031             opt->target = &opt->textValue;
17032         } else if((p = strstr(opt->name, " -path "))) {
17033             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17034             opt->target = opt->textValue = p+7;
17035             opt->type = PathName; // PathName;
17036             opt->target = &opt->textValue;
17037         } else if(p = strstr(opt->name, " -check ")) {
17038             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17039             opt->value = (def != 0);
17040             opt->type = CheckBox;
17041         } else if(p = strstr(opt->name, " -combo ")) {
17042             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17043             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17044             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17045             opt->value = n = 0;
17046             while(q = StrStr(q, " /// ")) {
17047                 n++; *q = 0;    // count choices, and null-terminate each of them
17048                 q += 5;
17049                 if(*q == '*') { // remember default, which is marked with * prefix
17050                     q++;
17051                     opt->value = n;
17052                 }
17053                 cps->comboList[cps->comboCnt++] = q;
17054             }
17055             cps->comboList[cps->comboCnt++] = NULL;
17056             opt->max = n + 1;
17057             opt->type = ComboBox;
17058         } else if(p = strstr(opt->name, " -button")) {
17059             opt->type = Button;
17060         } else if(p = strstr(opt->name, " -save")) {
17061             opt->type = SaveButton;
17062         } else return FALSE;
17063         *p = 0; // terminate option name
17064         // now look if the command-line options define a setting for this engine option.
17065         if(cps->optionSettings && cps->optionSettings[0])
17066             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17067         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17068           snprintf(buf, MSG_SIZ, "option %s", p);
17069                 if(p = strstr(buf, ",")) *p = 0;
17070                 if(q = strchr(buf, '=')) switch(opt->type) {
17071                     case ComboBox:
17072                         for(n=0; n<opt->max; n++)
17073                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17074                         break;
17075                     case TextBox:
17076                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17077                         break;
17078                     case Spin:
17079                     case CheckBox:
17080                         opt->value = atoi(q+1);
17081                     default:
17082                         break;
17083                 }
17084                 strcat(buf, "\n");
17085                 SendToProgram(buf, cps);
17086         }
17087         return TRUE;
17088 }
17089
17090 void
17091 FeatureDone (ChessProgramState *cps, int val)
17092 {
17093   DelayedEventCallback cb = GetDelayedEvent();
17094   if ((cb == InitBackEnd3 && cps == &first) ||
17095       (cb == SettingsMenuIfReady && cps == &second) ||
17096       (cb == LoadEngine) ||
17097       (cb == TwoMachinesEventIfReady)) {
17098     CancelDelayedEvent();
17099     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17100   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17101   cps->initDone = val;
17102   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17103 }
17104
17105 /* Parse feature command from engine */
17106 void
17107 ParseFeatures (char *args, ChessProgramState *cps)
17108 {
17109   char *p = args;
17110   char *q = NULL;
17111   int val;
17112   char buf[MSG_SIZ];
17113
17114   for (;;) {
17115     while (*p == ' ') p++;
17116     if (*p == NULLCHAR) return;
17117
17118     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17119     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17120     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17121     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17122     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17123     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17124     if (BoolFeature(&p, "reuse", &val, cps)) {
17125       /* Engine can disable reuse, but can't enable it if user said no */
17126       if (!val) cps->reuse = FALSE;
17127       continue;
17128     }
17129     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17130     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17131       if (gameMode == TwoMachinesPlay) {
17132         DisplayTwoMachinesTitle();
17133       } else {
17134         DisplayTitle("");
17135       }
17136       continue;
17137     }
17138     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17139     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17140     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17141     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17142     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17143     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17144     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17145     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17146     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17147     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17148     if (IntFeature(&p, "done", &val, cps)) {
17149       FeatureDone(cps, val);
17150       continue;
17151     }
17152     /* Added by Tord: */
17153     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17154     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17155     /* End of additions by Tord */
17156
17157     /* [HGM] added features: */
17158     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17159     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17160     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17161     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17162     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17163     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17164     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17165     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17166         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17167         FREE(cps->option[cps->nrOptions].name);
17168         cps->option[cps->nrOptions].name = q; q = NULL;
17169         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17170           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17171             SendToProgram(buf, cps);
17172             continue;
17173         }
17174         if(cps->nrOptions >= MAX_OPTIONS) {
17175             cps->nrOptions--;
17176             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17177             DisplayError(buf, 0);
17178         }
17179         continue;
17180     }
17181     /* End of additions by HGM */
17182
17183     /* unknown feature: complain and skip */
17184     q = p;
17185     while (*q && *q != '=') q++;
17186     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17187     SendToProgram(buf, cps);
17188     p = q;
17189     if (*p == '=') {
17190       p++;
17191       if (*p == '\"') {
17192         p++;
17193         while (*p && *p != '\"') p++;
17194         if (*p == '\"') p++;
17195       } else {
17196         while (*p && *p != ' ') p++;
17197       }
17198     }
17199   }
17200
17201 }
17202
17203 void
17204 PeriodicUpdatesEvent (int newState)
17205 {
17206     if (newState == appData.periodicUpdates)
17207       return;
17208
17209     appData.periodicUpdates=newState;
17210
17211     /* Display type changes, so update it now */
17212 //    DisplayAnalysis();
17213
17214     /* Get the ball rolling again... */
17215     if (newState) {
17216         AnalysisPeriodicEvent(1);
17217         StartAnalysisClock();
17218     }
17219 }
17220
17221 void
17222 PonderNextMoveEvent (int newState)
17223 {
17224     if (newState == appData.ponderNextMove) return;
17225     if (gameMode == EditPosition) EditPositionDone(TRUE);
17226     if (newState) {
17227         SendToProgram("hard\n", &first);
17228         if (gameMode == TwoMachinesPlay) {
17229             SendToProgram("hard\n", &second);
17230         }
17231     } else {
17232         SendToProgram("easy\n", &first);
17233         thinkOutput[0] = NULLCHAR;
17234         if (gameMode == TwoMachinesPlay) {
17235             SendToProgram("easy\n", &second);
17236         }
17237     }
17238     appData.ponderNextMove = newState;
17239 }
17240
17241 void
17242 NewSettingEvent (int option, int *feature, char *command, int value)
17243 {
17244     char buf[MSG_SIZ];
17245
17246     if (gameMode == EditPosition) EditPositionDone(TRUE);
17247     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17248     if(feature == NULL || *feature) SendToProgram(buf, &first);
17249     if (gameMode == TwoMachinesPlay) {
17250         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17251     }
17252 }
17253
17254 void
17255 ShowThinkingEvent ()
17256 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17257 {
17258     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17259     int newState = appData.showThinking
17260         // [HGM] thinking: other features now need thinking output as well
17261         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17262
17263     if (oldState == newState) return;
17264     oldState = newState;
17265     if (gameMode == EditPosition) EditPositionDone(TRUE);
17266     if (oldState) {
17267         SendToProgram("post\n", &first);
17268         if (gameMode == TwoMachinesPlay) {
17269             SendToProgram("post\n", &second);
17270         }
17271     } else {
17272         SendToProgram("nopost\n", &first);
17273         thinkOutput[0] = NULLCHAR;
17274         if (gameMode == TwoMachinesPlay) {
17275             SendToProgram("nopost\n", &second);
17276         }
17277     }
17278 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17279 }
17280
17281 void
17282 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17283 {
17284   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17285   if (pr == NoProc) return;
17286   AskQuestion(title, question, replyPrefix, pr);
17287 }
17288
17289 void
17290 TypeInEvent (char firstChar)
17291 {
17292     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17293         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17294         gameMode == AnalyzeMode || gameMode == EditGame ||
17295         gameMode == EditPosition || gameMode == IcsExamining ||
17296         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17297         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17298                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17299                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17300         gameMode == Training) PopUpMoveDialog(firstChar);
17301 }
17302
17303 void
17304 TypeInDoneEvent (char *move)
17305 {
17306         Board board;
17307         int n, fromX, fromY, toX, toY;
17308         char promoChar;
17309         ChessMove moveType;
17310
17311         // [HGM] FENedit
17312         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17313                 EditPositionPasteFEN(move);
17314                 return;
17315         }
17316         // [HGM] movenum: allow move number to be typed in any mode
17317         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17318           ToNrEvent(2*n-1);
17319           return;
17320         }
17321         // undocumented kludge: allow command-line option to be typed in!
17322         // (potentially fatal, and does not implement the effect of the option.)
17323         // should only be used for options that are values on which future decisions will be made,
17324         // and definitely not on options that would be used during initialization.
17325         if(strstr(move, "!!! -") == move) {
17326             ParseArgsFromString(move+4);
17327             return;
17328         }
17329
17330       if (gameMode != EditGame && currentMove != forwardMostMove &&
17331         gameMode != Training) {
17332         DisplayMoveError(_("Displayed move is not current"));
17333       } else {
17334         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17335           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17336         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17337         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17338           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17339           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17340         } else {
17341           DisplayMoveError(_("Could not parse move"));
17342         }
17343       }
17344 }
17345
17346 void
17347 DisplayMove (int moveNumber)
17348 {
17349     char message[MSG_SIZ];
17350     char res[MSG_SIZ];
17351     char cpThinkOutput[MSG_SIZ];
17352
17353     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17354
17355     if (moveNumber == forwardMostMove - 1 ||
17356         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17357
17358         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17359
17360         if (strchr(cpThinkOutput, '\n')) {
17361             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17362         }
17363     } else {
17364         *cpThinkOutput = NULLCHAR;
17365     }
17366
17367     /* [AS] Hide thinking from human user */
17368     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17369         *cpThinkOutput = NULLCHAR;
17370         if( thinkOutput[0] != NULLCHAR ) {
17371             int i;
17372
17373             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17374                 cpThinkOutput[i] = '.';
17375             }
17376             cpThinkOutput[i] = NULLCHAR;
17377             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17378         }
17379     }
17380
17381     if (moveNumber == forwardMostMove - 1 &&
17382         gameInfo.resultDetails != NULL) {
17383         if (gameInfo.resultDetails[0] == NULLCHAR) {
17384           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17385         } else {
17386           snprintf(res, MSG_SIZ, " {%s} %s",
17387                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17388         }
17389     } else {
17390         res[0] = NULLCHAR;
17391     }
17392
17393     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17394         DisplayMessage(res, cpThinkOutput);
17395     } else {
17396       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17397                 WhiteOnMove(moveNumber) ? " " : ".. ",
17398                 parseList[moveNumber], res);
17399         DisplayMessage(message, cpThinkOutput);
17400     }
17401 }
17402
17403 void
17404 DisplayComment (int moveNumber, char *text)
17405 {
17406     char title[MSG_SIZ];
17407
17408     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17409       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17410     } else {
17411       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17412               WhiteOnMove(moveNumber) ? " " : ".. ",
17413               parseList[moveNumber]);
17414     }
17415     if (text != NULL && (appData.autoDisplayComment || commentUp))
17416         CommentPopUp(title, text);
17417 }
17418
17419 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17420  * might be busy thinking or pondering.  It can be omitted if your
17421  * gnuchess is configured to stop thinking immediately on any user
17422  * input.  However, that gnuchess feature depends on the FIONREAD
17423  * ioctl, which does not work properly on some flavors of Unix.
17424  */
17425 void
17426 Attention (ChessProgramState *cps)
17427 {
17428 #if ATTENTION
17429     if (!cps->useSigint) return;
17430     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17431     switch (gameMode) {
17432       case MachinePlaysWhite:
17433       case MachinePlaysBlack:
17434       case TwoMachinesPlay:
17435       case IcsPlayingWhite:
17436       case IcsPlayingBlack:
17437       case AnalyzeMode:
17438       case AnalyzeFile:
17439         /* Skip if we know it isn't thinking */
17440         if (!cps->maybeThinking) return;
17441         if (appData.debugMode)
17442           fprintf(debugFP, "Interrupting %s\n", cps->which);
17443         InterruptChildProcess(cps->pr);
17444         cps->maybeThinking = FALSE;
17445         break;
17446       default:
17447         break;
17448     }
17449 #endif /*ATTENTION*/
17450 }
17451
17452 int
17453 CheckFlags ()
17454 {
17455     if (whiteTimeRemaining <= 0) {
17456         if (!whiteFlag) {
17457             whiteFlag = TRUE;
17458             if (appData.icsActive) {
17459                 if (appData.autoCallFlag &&
17460                     gameMode == IcsPlayingBlack && !blackFlag) {
17461                   SendToICS(ics_prefix);
17462                   SendToICS("flag\n");
17463                 }
17464             } else {
17465                 if (blackFlag) {
17466                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17467                 } else {
17468                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17469                     if (appData.autoCallFlag) {
17470                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17471                         return TRUE;
17472                     }
17473                 }
17474             }
17475         }
17476     }
17477     if (blackTimeRemaining <= 0) {
17478         if (!blackFlag) {
17479             blackFlag = TRUE;
17480             if (appData.icsActive) {
17481                 if (appData.autoCallFlag &&
17482                     gameMode == IcsPlayingWhite && !whiteFlag) {
17483                   SendToICS(ics_prefix);
17484                   SendToICS("flag\n");
17485                 }
17486             } else {
17487                 if (whiteFlag) {
17488                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17489                 } else {
17490                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17491                     if (appData.autoCallFlag) {
17492                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17493                         return TRUE;
17494                     }
17495                 }
17496             }
17497         }
17498     }
17499     return FALSE;
17500 }
17501
17502 void
17503 CheckTimeControl ()
17504 {
17505     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17506         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17507
17508     /*
17509      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17510      */
17511     if ( !WhiteOnMove(forwardMostMove) ) {
17512         /* White made time control */
17513         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17514         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17515         /* [HGM] time odds: correct new time quota for time odds! */
17516                                             / WhitePlayer()->timeOdds;
17517         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17518     } else {
17519         lastBlack -= blackTimeRemaining;
17520         /* Black made time control */
17521         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17522                                             / WhitePlayer()->other->timeOdds;
17523         lastWhite = whiteTimeRemaining;
17524     }
17525 }
17526
17527 void
17528 DisplayBothClocks ()
17529 {
17530     int wom = gameMode == EditPosition ?
17531       !blackPlaysFirst : WhiteOnMove(currentMove);
17532     DisplayWhiteClock(whiteTimeRemaining, wom);
17533     DisplayBlackClock(blackTimeRemaining, !wom);
17534 }
17535
17536
17537 /* Timekeeping seems to be a portability nightmare.  I think everyone
17538    has ftime(), but I'm really not sure, so I'm including some ifdefs
17539    to use other calls if you don't.  Clocks will be less accurate if
17540    you have neither ftime nor gettimeofday.
17541 */
17542
17543 /* VS 2008 requires the #include outside of the function */
17544 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17545 #include <sys/timeb.h>
17546 #endif
17547
17548 /* Get the current time as a TimeMark */
17549 void
17550 GetTimeMark (TimeMark *tm)
17551 {
17552 #if HAVE_GETTIMEOFDAY
17553
17554     struct timeval timeVal;
17555     struct timezone timeZone;
17556
17557     gettimeofday(&timeVal, &timeZone);
17558     tm->sec = (long) timeVal.tv_sec;
17559     tm->ms = (int) (timeVal.tv_usec / 1000L);
17560
17561 #else /*!HAVE_GETTIMEOFDAY*/
17562 #if HAVE_FTIME
17563
17564 // include <sys/timeb.h> / moved to just above start of function
17565     struct timeb timeB;
17566
17567     ftime(&timeB);
17568     tm->sec = (long) timeB.time;
17569     tm->ms = (int) timeB.millitm;
17570
17571 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17572     tm->sec = (long) time(NULL);
17573     tm->ms = 0;
17574 #endif
17575 #endif
17576 }
17577
17578 /* Return the difference in milliseconds between two
17579    time marks.  We assume the difference will fit in a long!
17580 */
17581 long
17582 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17583 {
17584     return 1000L*(tm2->sec - tm1->sec) +
17585            (long) (tm2->ms - tm1->ms);
17586 }
17587
17588
17589 /*
17590  * Code to manage the game clocks.
17591  *
17592  * In tournament play, black starts the clock and then white makes a move.
17593  * We give the human user a slight advantage if he is playing white---the
17594  * clocks don't run until he makes his first move, so it takes zero time.
17595  * Also, we don't account for network lag, so we could get out of sync
17596  * with GNU Chess's clock -- but then, referees are always right.
17597  */
17598
17599 static TimeMark tickStartTM;
17600 static long intendedTickLength;
17601
17602 long
17603 NextTickLength (long timeRemaining)
17604 {
17605     long nominalTickLength, nextTickLength;
17606
17607     if (timeRemaining > 0L && timeRemaining <= 10000L)
17608       nominalTickLength = 100L;
17609     else
17610       nominalTickLength = 1000L;
17611     nextTickLength = timeRemaining % nominalTickLength;
17612     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17613
17614     return nextTickLength;
17615 }
17616
17617 /* Adjust clock one minute up or down */
17618 void
17619 AdjustClock (Boolean which, int dir)
17620 {
17621     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17622     if(which) blackTimeRemaining += 60000*dir;
17623     else      whiteTimeRemaining += 60000*dir;
17624     DisplayBothClocks();
17625     adjustedClock = TRUE;
17626 }
17627
17628 /* Stop clocks and reset to a fresh time control */
17629 void
17630 ResetClocks ()
17631 {
17632     (void) StopClockTimer();
17633     if (appData.icsActive) {
17634         whiteTimeRemaining = blackTimeRemaining = 0;
17635     } else if (searchTime) {
17636         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17637         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17638     } else { /* [HGM] correct new time quote for time odds */
17639         whiteTC = blackTC = fullTimeControlString;
17640         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17641         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17642     }
17643     if (whiteFlag || blackFlag) {
17644         DisplayTitle("");
17645         whiteFlag = blackFlag = FALSE;
17646     }
17647     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17648     DisplayBothClocks();
17649     adjustedClock = FALSE;
17650 }
17651
17652 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17653
17654 /* Decrement running clock by amount of time that has passed */
17655 void
17656 DecrementClocks ()
17657 {
17658     long timeRemaining;
17659     long lastTickLength, fudge;
17660     TimeMark now;
17661
17662     if (!appData.clockMode) return;
17663     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17664
17665     GetTimeMark(&now);
17666
17667     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17668
17669     /* Fudge if we woke up a little too soon */
17670     fudge = intendedTickLength - lastTickLength;
17671     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17672
17673     if (WhiteOnMove(forwardMostMove)) {
17674         if(whiteNPS >= 0) lastTickLength = 0;
17675         timeRemaining = whiteTimeRemaining -= lastTickLength;
17676         if(timeRemaining < 0 && !appData.icsActive) {
17677             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17678             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17679                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17680                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17681             }
17682         }
17683         DisplayWhiteClock(whiteTimeRemaining - fudge,
17684                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17685     } else {
17686         if(blackNPS >= 0) lastTickLength = 0;
17687         timeRemaining = blackTimeRemaining -= lastTickLength;
17688         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17689             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17690             if(suddenDeath) {
17691                 blackStartMove = forwardMostMove;
17692                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17693             }
17694         }
17695         DisplayBlackClock(blackTimeRemaining - fudge,
17696                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17697     }
17698     if (CheckFlags()) return;
17699
17700     if(twoBoards) { // count down secondary board's clocks as well
17701         activePartnerTime -= lastTickLength;
17702         partnerUp = 1;
17703         if(activePartner == 'W')
17704             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17705         else
17706             DisplayBlackClock(activePartnerTime, TRUE);
17707         partnerUp = 0;
17708     }
17709
17710     tickStartTM = now;
17711     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17712     StartClockTimer(intendedTickLength);
17713
17714     /* if the time remaining has fallen below the alarm threshold, sound the
17715      * alarm. if the alarm has sounded and (due to a takeback or time control
17716      * with increment) the time remaining has increased to a level above the
17717      * threshold, reset the alarm so it can sound again.
17718      */
17719
17720     if (appData.icsActive && appData.icsAlarm) {
17721
17722         /* make sure we are dealing with the user's clock */
17723         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17724                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17725            )) return;
17726
17727         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17728             alarmSounded = FALSE;
17729         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17730             PlayAlarmSound();
17731             alarmSounded = TRUE;
17732         }
17733     }
17734 }
17735
17736
17737 /* A player has just moved, so stop the previously running
17738    clock and (if in clock mode) start the other one.
17739    We redisplay both clocks in case we're in ICS mode, because
17740    ICS gives us an update to both clocks after every move.
17741    Note that this routine is called *after* forwardMostMove
17742    is updated, so the last fractional tick must be subtracted
17743    from the color that is *not* on move now.
17744 */
17745 void
17746 SwitchClocks (int newMoveNr)
17747 {
17748     long lastTickLength;
17749     TimeMark now;
17750     int flagged = FALSE;
17751
17752     GetTimeMark(&now);
17753
17754     if (StopClockTimer() && appData.clockMode) {
17755         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17756         if (!WhiteOnMove(forwardMostMove)) {
17757             if(blackNPS >= 0) lastTickLength = 0;
17758             blackTimeRemaining -= lastTickLength;
17759            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17760 //         if(pvInfoList[forwardMostMove].time == -1)
17761                  pvInfoList[forwardMostMove].time =               // use GUI time
17762                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17763         } else {
17764            if(whiteNPS >= 0) lastTickLength = 0;
17765            whiteTimeRemaining -= lastTickLength;
17766            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17767 //         if(pvInfoList[forwardMostMove].time == -1)
17768                  pvInfoList[forwardMostMove].time =
17769                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17770         }
17771         flagged = CheckFlags();
17772     }
17773     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17774     CheckTimeControl();
17775
17776     if (flagged || !appData.clockMode) return;
17777
17778     switch (gameMode) {
17779       case MachinePlaysBlack:
17780       case MachinePlaysWhite:
17781       case BeginningOfGame:
17782         if (pausing) return;
17783         break;
17784
17785       case EditGame:
17786       case PlayFromGameFile:
17787       case IcsExamining:
17788         return;
17789
17790       default:
17791         break;
17792     }
17793
17794     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17795         if(WhiteOnMove(forwardMostMove))
17796              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17797         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17798     }
17799
17800     tickStartTM = now;
17801     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17802       whiteTimeRemaining : blackTimeRemaining);
17803     StartClockTimer(intendedTickLength);
17804 }
17805
17806
17807 /* Stop both clocks */
17808 void
17809 StopClocks ()
17810 {
17811     long lastTickLength;
17812     TimeMark now;
17813
17814     if (!StopClockTimer()) return;
17815     if (!appData.clockMode) return;
17816
17817     GetTimeMark(&now);
17818
17819     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17820     if (WhiteOnMove(forwardMostMove)) {
17821         if(whiteNPS >= 0) lastTickLength = 0;
17822         whiteTimeRemaining -= lastTickLength;
17823         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17824     } else {
17825         if(blackNPS >= 0) lastTickLength = 0;
17826         blackTimeRemaining -= lastTickLength;
17827         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17828     }
17829     CheckFlags();
17830 }
17831
17832 /* Start clock of player on move.  Time may have been reset, so
17833    if clock is already running, stop and restart it. */
17834 void
17835 StartClocks ()
17836 {
17837     (void) StopClockTimer(); /* in case it was running already */
17838     DisplayBothClocks();
17839     if (CheckFlags()) return;
17840
17841     if (!appData.clockMode) return;
17842     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17843
17844     GetTimeMark(&tickStartTM);
17845     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17846       whiteTimeRemaining : blackTimeRemaining);
17847
17848    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17849     whiteNPS = blackNPS = -1;
17850     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17851        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17852         whiteNPS = first.nps;
17853     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17854        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17855         blackNPS = first.nps;
17856     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17857         whiteNPS = second.nps;
17858     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17859         blackNPS = second.nps;
17860     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17861
17862     StartClockTimer(intendedTickLength);
17863 }
17864
17865 char *
17866 TimeString (long ms)
17867 {
17868     long second, minute, hour, day;
17869     char *sign = "";
17870     static char buf[32];
17871
17872     if (ms > 0 && ms <= 9900) {
17873       /* convert milliseconds to tenths, rounding up */
17874       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17875
17876       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17877       return buf;
17878     }
17879
17880     /* convert milliseconds to seconds, rounding up */
17881     /* use floating point to avoid strangeness of integer division
17882        with negative dividends on many machines */
17883     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17884
17885     if (second < 0) {
17886         sign = "-";
17887         second = -second;
17888     }
17889
17890     day = second / (60 * 60 * 24);
17891     second = second % (60 * 60 * 24);
17892     hour = second / (60 * 60);
17893     second = second % (60 * 60);
17894     minute = second / 60;
17895     second = second % 60;
17896
17897     if (day > 0)
17898       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17899               sign, day, hour, minute, second);
17900     else if (hour > 0)
17901       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17902     else
17903       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17904
17905     return buf;
17906 }
17907
17908
17909 /*
17910  * This is necessary because some C libraries aren't ANSI C compliant yet.
17911  */
17912 char *
17913 StrStr (char *string, char *match)
17914 {
17915     int i, length;
17916
17917     length = strlen(match);
17918
17919     for (i = strlen(string) - length; i >= 0; i--, string++)
17920       if (!strncmp(match, string, length))
17921         return string;
17922
17923     return NULL;
17924 }
17925
17926 char *
17927 StrCaseStr (char *string, char *match)
17928 {
17929     int i, j, length;
17930
17931     length = strlen(match);
17932
17933     for (i = strlen(string) - length; i >= 0; i--, string++) {
17934         for (j = 0; j < length; j++) {
17935             if (ToLower(match[j]) != ToLower(string[j]))
17936               break;
17937         }
17938         if (j == length) return string;
17939     }
17940
17941     return NULL;
17942 }
17943
17944 #ifndef _amigados
17945 int
17946 StrCaseCmp (char *s1, char *s2)
17947 {
17948     char c1, c2;
17949
17950     for (;;) {
17951         c1 = ToLower(*s1++);
17952         c2 = ToLower(*s2++);
17953         if (c1 > c2) return 1;
17954         if (c1 < c2) return -1;
17955         if (c1 == NULLCHAR) return 0;
17956     }
17957 }
17958
17959
17960 int
17961 ToLower (int c)
17962 {
17963     return isupper(c) ? tolower(c) : c;
17964 }
17965
17966
17967 int
17968 ToUpper (int c)
17969 {
17970     return islower(c) ? toupper(c) : c;
17971 }
17972 #endif /* !_amigados    */
17973
17974 char *
17975 StrSave (char *s)
17976 {
17977   char *ret;
17978
17979   if ((ret = (char *) malloc(strlen(s) + 1)))
17980     {
17981       safeStrCpy(ret, s, strlen(s)+1);
17982     }
17983   return ret;
17984 }
17985
17986 char *
17987 StrSavePtr (char *s, char **savePtr)
17988 {
17989     if (*savePtr) {
17990         free(*savePtr);
17991     }
17992     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17993       safeStrCpy(*savePtr, s, strlen(s)+1);
17994     }
17995     return(*savePtr);
17996 }
17997
17998 char *
17999 PGNDate ()
18000 {
18001     time_t clock;
18002     struct tm *tm;
18003     char buf[MSG_SIZ];
18004
18005     clock = time((time_t *)NULL);
18006     tm = localtime(&clock);
18007     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18008             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18009     return StrSave(buf);
18010 }
18011
18012
18013 char *
18014 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18015 {
18016     int i, j, fromX, fromY, toX, toY;
18017     int whiteToPlay, haveRights = nrCastlingRights;
18018     char buf[MSG_SIZ];
18019     char *p, *q;
18020     int emptycount;
18021     ChessSquare piece;
18022
18023     whiteToPlay = (gameMode == EditPosition) ?
18024       !blackPlaysFirst : (move % 2 == 0);
18025     p = buf;
18026
18027     /* Piece placement data */
18028     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18029         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18030         emptycount = 0;
18031         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18032             if (boards[move][i][j] == EmptySquare) {
18033                 emptycount++;
18034             } else { ChessSquare piece = boards[move][i][j];
18035                 if (emptycount > 0) {
18036                     if(emptycount<10) /* [HGM] can be >= 10 */
18037                         *p++ = '0' + emptycount;
18038                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18039                     emptycount = 0;
18040                 }
18041                 if(PieceToChar(piece) == '+') {
18042                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18043                     *p++ = '+';
18044                     piece = (ChessSquare)(CHUDEMOTED(piece));
18045                 }
18046                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18047                 if(*p = PieceSuffix(piece)) p++;
18048                 if(p[-1] == '~') {
18049                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18050                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18051                     *p++ = '~';
18052                 }
18053             }
18054         }
18055         if (emptycount > 0) {
18056             if(emptycount<10) /* [HGM] can be >= 10 */
18057                 *p++ = '0' + emptycount;
18058             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18059             emptycount = 0;
18060         }
18061         *p++ = '/';
18062     }
18063     *(p - 1) = ' ';
18064
18065     /* [HGM] print Crazyhouse or Shogi holdings */
18066     if( gameInfo.holdingsWidth ) {
18067         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18068         q = p;
18069         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18070             piece = boards[move][i][BOARD_WIDTH-1];
18071             if( piece != EmptySquare )
18072               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18073                   *p++ = PieceToChar(piece);
18074         }
18075         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18076             piece = boards[move][BOARD_HEIGHT-i-1][0];
18077             if( piece != EmptySquare )
18078               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18079                   *p++ = PieceToChar(piece);
18080         }
18081
18082         if( q == p ) *p++ = '-';
18083         *p++ = ']';
18084         *p++ = ' ';
18085     }
18086
18087     /* Active color */
18088     *p++ = whiteToPlay ? 'w' : 'b';
18089     *p++ = ' ';
18090
18091   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18092     haveRights = 0; q = p;
18093     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18094       piece = boards[move][0][i];
18095       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18096         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18097       }
18098     }
18099     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18100       piece = boards[move][BOARD_HEIGHT-1][i];
18101       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18102         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18103       }
18104     }
18105     if(p == q) *p++ = '-';
18106     *p++ = ' ';
18107   }
18108
18109   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18110     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18111   } else {
18112   if(haveRights) {
18113      int handW=0, handB=0;
18114      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18115         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18116         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18117      }
18118      q = p;
18119      if(appData.fischerCastling) {
18120         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18121            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18122                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18123         } else {
18124        /* [HGM] write directly from rights */
18125            if(boards[move][CASTLING][2] != NoRights &&
18126               boards[move][CASTLING][0] != NoRights   )
18127                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18128            if(boards[move][CASTLING][2] != NoRights &&
18129               boards[move][CASTLING][1] != NoRights   )
18130                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18131         }
18132         if(handB) {
18133            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18134                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18135         } else {
18136            if(boards[move][CASTLING][5] != NoRights &&
18137               boards[move][CASTLING][3] != NoRights   )
18138                 *p++ = boards[move][CASTLING][3] + AAA;
18139            if(boards[move][CASTLING][5] != NoRights &&
18140               boards[move][CASTLING][4] != NoRights   )
18141                 *p++ = boards[move][CASTLING][4] + AAA;
18142         }
18143      } else {
18144
18145         /* [HGM] write true castling rights */
18146         if( nrCastlingRights == 6 ) {
18147             int q, k=0;
18148             if(boards[move][CASTLING][0] != NoRights &&
18149                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18150             q = (boards[move][CASTLING][1] != NoRights &&
18151                  boards[move][CASTLING][2] != NoRights  );
18152             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18153                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18154                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18155                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18156             }
18157             if(q) *p++ = 'Q';
18158             k = 0;
18159             if(boards[move][CASTLING][3] != NoRights &&
18160                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18161             q = (boards[move][CASTLING][4] != NoRights &&
18162                  boards[move][CASTLING][5] != NoRights  );
18163             if(handB) {
18164                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18165                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18166                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18167             }
18168             if(q) *p++ = 'q';
18169         }
18170      }
18171      if (q == p) *p++ = '-'; /* No castling rights */
18172      *p++ = ' ';
18173   }
18174
18175   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18176      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18177      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18178     /* En passant target square */
18179     if (move > backwardMostMove) {
18180         fromX = moveList[move - 1][0] - AAA;
18181         fromY = moveList[move - 1][1] - ONE;
18182         toX = moveList[move - 1][2] - AAA;
18183         toY = moveList[move - 1][3] - ONE;
18184         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18185             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18186             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18187             fromX == toX) {
18188             /* 2-square pawn move just happened */
18189             *p++ = toX + AAA;
18190             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18191         } else {
18192             *p++ = '-';
18193         }
18194     } else if(move == backwardMostMove) {
18195         // [HGM] perhaps we should always do it like this, and forget the above?
18196         if((signed char)boards[move][EP_STATUS] >= 0) {
18197             *p++ = boards[move][EP_STATUS] + AAA;
18198             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18199         } else {
18200             *p++ = '-';
18201         }
18202     } else {
18203         *p++ = '-';
18204     }
18205     *p++ = ' ';
18206   }
18207   }
18208
18209     if(moveCounts)
18210     {   int i = 0, j=move;
18211
18212         /* [HGM] find reversible plies */
18213         if (appData.debugMode) { int k;
18214             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18215             for(k=backwardMostMove; k<=forwardMostMove; k++)
18216                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18217
18218         }
18219
18220         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18221         if( j == backwardMostMove ) i += initialRulePlies;
18222         sprintf(p, "%d ", i);
18223         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18224
18225         /* Fullmove number */
18226         sprintf(p, "%d", (move / 2) + 1);
18227     } else *--p = NULLCHAR;
18228
18229     return StrSave(buf);
18230 }
18231
18232 Boolean
18233 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18234 {
18235     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18236     char *p, c;
18237     int emptycount, virgin[BOARD_FILES];
18238     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18239
18240     p = fen;
18241
18242     /* Piece placement data */
18243     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18244         j = 0;
18245         for (;;) {
18246             if (*p == '/' || *p == ' ' || *p == '[' ) {
18247                 if(j > w) w = j;
18248                 emptycount = gameInfo.boardWidth - j;
18249                 while (emptycount--)
18250                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18251                 if (*p == '/') p++;
18252                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18253                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18254                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18255                     }
18256                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18257                 }
18258                 break;
18259 #if(BOARD_FILES >= 10)*0
18260             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18261                 p++; emptycount=10;
18262                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18263                 while (emptycount--)
18264                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18265 #endif
18266             } else if (*p == '*') {
18267                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18268             } else if (isdigit(*p)) {
18269                 emptycount = *p++ - '0';
18270                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18271                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18272                 while (emptycount--)
18273                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18274             } else if (*p == '<') {
18275                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18276                 else if (i != 0 || !shuffle) return FALSE;
18277                 p++;
18278             } else if (shuffle && *p == '>') {
18279                 p++; // for now ignore closing shuffle range, and assume rank-end
18280             } else if (*p == '?') {
18281                 if (j >= gameInfo.boardWidth) return FALSE;
18282                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18283                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18284             } else if (*p == '+' || isalpha(*p)) {
18285                 char *q, *s = SUFFIXES;
18286                 if (j >= gameInfo.boardWidth) return FALSE;
18287                 if(*p=='+') {
18288                     char c = *++p;
18289                     if(q = strchr(s, p[1])) p++;
18290                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18291                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18292                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18293                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18294                 } else {
18295                     char c = *p++;
18296                     if(q = strchr(s, *p)) p++;
18297                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18298                 }
18299
18300                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18301                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18302                     piece = (ChessSquare) (PROMOTED(piece));
18303                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18304                     p++;
18305                 }
18306                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18307                 if(piece == king) wKingRank = i;
18308                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18309             } else {
18310                 return FALSE;
18311             }
18312         }
18313     }
18314     while (*p == '/' || *p == ' ') p++;
18315
18316     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18317
18318     /* [HGM] by default clear Crazyhouse holdings, if present */
18319     if(gameInfo.holdingsWidth) {
18320        for(i=0; i<BOARD_HEIGHT; i++) {
18321            board[i][0]             = EmptySquare; /* black holdings */
18322            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18323            board[i][1]             = (ChessSquare) 0; /* black counts */
18324            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18325        }
18326     }
18327
18328     /* [HGM] look for Crazyhouse holdings here */
18329     while(*p==' ') p++;
18330     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18331         int swap=0, wcnt=0, bcnt=0;
18332         if(*p == '[') p++;
18333         if(*p == '<') swap++, p++;
18334         if(*p == '-' ) p++; /* empty holdings */ else {
18335             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18336             /* if we would allow FEN reading to set board size, we would   */
18337             /* have to add holdings and shift the board read so far here   */
18338             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18339                 p++;
18340                 if((int) piece >= (int) BlackPawn ) {
18341                     i = (int)piece - (int)BlackPawn;
18342                     i = PieceToNumber((ChessSquare)i);
18343                     if( i >= gameInfo.holdingsSize ) return FALSE;
18344                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18345                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18346                     bcnt++;
18347                 } else {
18348                     i = (int)piece - (int)WhitePawn;
18349                     i = PieceToNumber((ChessSquare)i);
18350                     if( i >= gameInfo.holdingsSize ) return FALSE;
18351                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18352                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18353                     wcnt++;
18354                 }
18355             }
18356             if(subst) { // substitute back-rank question marks by holdings pieces
18357                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18358                     int k, m, n = bcnt + 1;
18359                     if(board[0][j] == ClearBoard) {
18360                         if(!wcnt) return FALSE;
18361                         n = rand() % wcnt;
18362                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18363                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18364                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18365                             break;
18366                         }
18367                     }
18368                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18369                         if(!bcnt) return FALSE;
18370                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18371                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18372                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18373                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18374                             break;
18375                         }
18376                     }
18377                 }
18378                 subst = 0;
18379             }
18380         }
18381         if(*p == ']') p++;
18382     }
18383
18384     if(subst) return FALSE; // substitution requested, but no holdings
18385
18386     while(*p == ' ') p++;
18387
18388     /* Active color */
18389     c = *p++;
18390     if(appData.colorNickNames) {
18391       if( c == appData.colorNickNames[0] ) c = 'w'; else
18392       if( c == appData.colorNickNames[1] ) c = 'b';
18393     }
18394     switch (c) {
18395       case 'w':
18396         *blackPlaysFirst = FALSE;
18397         break;
18398       case 'b':
18399         *blackPlaysFirst = TRUE;
18400         break;
18401       default:
18402         return FALSE;
18403     }
18404
18405     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18406     /* return the extra info in global variiables             */
18407
18408     while(*p==' ') p++;
18409
18410     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18411         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18412         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18413     }
18414
18415     /* set defaults in case FEN is incomplete */
18416     board[EP_STATUS] = EP_UNKNOWN;
18417     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18418     for(i=0; i<nrCastlingRights; i++ ) {
18419         board[CASTLING][i] =
18420             appData.fischerCastling ? NoRights : initialRights[i];
18421     }   /* assume possible unless obviously impossible */
18422     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18423     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18424     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18425                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18426     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18427     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18428     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18429                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18430     FENrulePlies = 0;
18431
18432     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18433       char *q = p;
18434       int w=0, b=0;
18435       while(isalpha(*p)) {
18436         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18437         if(islower(*p)) b |= 1 << (*p++ - 'a');
18438       }
18439       if(*p == '-') p++;
18440       if(p != q) {
18441         board[TOUCHED_W] = ~w;
18442         board[TOUCHED_B] = ~b;
18443         while(*p == ' ') p++;
18444       }
18445     } else
18446
18447     if(nrCastlingRights) {
18448       int fischer = 0;
18449       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18450       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18451           /* castling indicator present, so default becomes no castlings */
18452           for(i=0; i<nrCastlingRights; i++ ) {
18453                  board[CASTLING][i] = NoRights;
18454           }
18455       }
18456       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18457              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18458              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18459              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18460         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18461
18462         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18463             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18464             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18465         }
18466         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18467             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18468         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18469                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18470         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18471                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18472         switch(c) {
18473           case'K':
18474               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18475               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18476               board[CASTLING][2] = whiteKingFile;
18477               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18478               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18479               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18480               break;
18481           case'Q':
18482               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18483               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18484               board[CASTLING][2] = whiteKingFile;
18485               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18486               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18487               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18488               break;
18489           case'k':
18490               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18491               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18492               board[CASTLING][5] = blackKingFile;
18493               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18494               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18495               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18496               break;
18497           case'q':
18498               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18499               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18500               board[CASTLING][5] = blackKingFile;
18501               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18502               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18503               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18504           case '-':
18505               break;
18506           default: /* FRC castlings */
18507               if(c >= 'a') { /* black rights */
18508                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18509                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18510                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18511                   if(i == BOARD_RGHT) break;
18512                   board[CASTLING][5] = i;
18513                   c -= AAA;
18514                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18515                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18516                   if(c > i)
18517                       board[CASTLING][3] = c;
18518                   else
18519                       board[CASTLING][4] = c;
18520               } else { /* white rights */
18521                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18522                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18523                     if(board[0][i] == WhiteKing) break;
18524                   if(i == BOARD_RGHT) break;
18525                   board[CASTLING][2] = i;
18526                   c -= AAA - 'a' + 'A';
18527                   if(board[0][c] >= WhiteKing) break;
18528                   if(c > i)
18529                       board[CASTLING][0] = c;
18530                   else
18531                       board[CASTLING][1] = c;
18532               }
18533         }
18534       }
18535       for(i=0; i<nrCastlingRights; i++)
18536         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18537       if(gameInfo.variant == VariantSChess)
18538         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18539       if(fischer && shuffle) appData.fischerCastling = TRUE;
18540     if (appData.debugMode) {
18541         fprintf(debugFP, "FEN castling rights:");
18542         for(i=0; i<nrCastlingRights; i++)
18543         fprintf(debugFP, " %d", board[CASTLING][i]);
18544         fprintf(debugFP, "\n");
18545     }
18546
18547       while(*p==' ') p++;
18548     }
18549
18550     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18551
18552     /* read e.p. field in games that know e.p. capture */
18553     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18554        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18555        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18556       if(*p=='-') {
18557         p++; board[EP_STATUS] = EP_NONE;
18558       } else {
18559          char c = *p++ - AAA;
18560
18561          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18562          if(*p >= '0' && *p <='9') p++;
18563          board[EP_STATUS] = c;
18564       }
18565     }
18566
18567
18568     if(sscanf(p, "%d", &i) == 1) {
18569         FENrulePlies = i; /* 50-move ply counter */
18570         /* (The move number is still ignored)    */
18571     }
18572
18573     return TRUE;
18574 }
18575
18576 void
18577 EditPositionPasteFEN (char *fen)
18578 {
18579   if (fen != NULL) {
18580     Board initial_position;
18581
18582     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18583       DisplayError(_("Bad FEN position in clipboard"), 0);
18584       return ;
18585     } else {
18586       int savedBlackPlaysFirst = blackPlaysFirst;
18587       EditPositionEvent();
18588       blackPlaysFirst = savedBlackPlaysFirst;
18589       CopyBoard(boards[0], initial_position);
18590       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18591       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18592       DisplayBothClocks();
18593       DrawPosition(FALSE, boards[currentMove]);
18594     }
18595   }
18596 }
18597
18598 static char cseq[12] = "\\   ";
18599
18600 Boolean
18601 set_cont_sequence (char *new_seq)
18602 {
18603     int len;
18604     Boolean ret;
18605
18606     // handle bad attempts to set the sequence
18607         if (!new_seq)
18608                 return 0; // acceptable error - no debug
18609
18610     len = strlen(new_seq);
18611     ret = (len > 0) && (len < sizeof(cseq));
18612     if (ret)
18613       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18614     else if (appData.debugMode)
18615       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18616     return ret;
18617 }
18618
18619 /*
18620     reformat a source message so words don't cross the width boundary.  internal
18621     newlines are not removed.  returns the wrapped size (no null character unless
18622     included in source message).  If dest is NULL, only calculate the size required
18623     for the dest buffer.  lp argument indicats line position upon entry, and it's
18624     passed back upon exit.
18625 */
18626 int
18627 wrap (char *dest, char *src, int count, int width, int *lp)
18628 {
18629     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18630
18631     cseq_len = strlen(cseq);
18632     old_line = line = *lp;
18633     ansi = len = clen = 0;
18634
18635     for (i=0; i < count; i++)
18636     {
18637         if (src[i] == '\033')
18638             ansi = 1;
18639
18640         // if we hit the width, back up
18641         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18642         {
18643             // store i & len in case the word is too long
18644             old_i = i, old_len = len;
18645
18646             // find the end of the last word
18647             while (i && src[i] != ' ' && src[i] != '\n')
18648             {
18649                 i--;
18650                 len--;
18651             }
18652
18653             // word too long?  restore i & len before splitting it
18654             if ((old_i-i+clen) >= width)
18655             {
18656                 i = old_i;
18657                 len = old_len;
18658             }
18659
18660             // extra space?
18661             if (i && src[i-1] == ' ')
18662                 len--;
18663
18664             if (src[i] != ' ' && src[i] != '\n')
18665             {
18666                 i--;
18667                 if (len)
18668                     len--;
18669             }
18670
18671             // now append the newline and continuation sequence
18672             if (dest)
18673                 dest[len] = '\n';
18674             len++;
18675             if (dest)
18676                 strncpy(dest+len, cseq, cseq_len);
18677             len += cseq_len;
18678             line = cseq_len;
18679             clen = cseq_len;
18680             continue;
18681         }
18682
18683         if (dest)
18684             dest[len] = src[i];
18685         len++;
18686         if (!ansi)
18687             line++;
18688         if (src[i] == '\n')
18689             line = 0;
18690         if (src[i] == 'm')
18691             ansi = 0;
18692     }
18693     if (dest && appData.debugMode)
18694     {
18695         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18696             count, width, line, len, *lp);
18697         show_bytes(debugFP, src, count);
18698         fprintf(debugFP, "\ndest: ");
18699         show_bytes(debugFP, dest, len);
18700         fprintf(debugFP, "\n");
18701     }
18702     *lp = dest ? line : old_line;
18703
18704     return len;
18705 }
18706
18707 // [HGM] vari: routines for shelving variations
18708 Boolean modeRestore = FALSE;
18709
18710 void
18711 PushInner (int firstMove, int lastMove)
18712 {
18713         int i, j, nrMoves = lastMove - firstMove;
18714
18715         // push current tail of game on stack
18716         savedResult[storedGames] = gameInfo.result;
18717         savedDetails[storedGames] = gameInfo.resultDetails;
18718         gameInfo.resultDetails = NULL;
18719         savedFirst[storedGames] = firstMove;
18720         savedLast [storedGames] = lastMove;
18721         savedFramePtr[storedGames] = framePtr;
18722         framePtr -= nrMoves; // reserve space for the boards
18723         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18724             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18725             for(j=0; j<MOVE_LEN; j++)
18726                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18727             for(j=0; j<2*MOVE_LEN; j++)
18728                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18729             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18730             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18731             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18732             pvInfoList[firstMove+i-1].depth = 0;
18733             commentList[framePtr+i] = commentList[firstMove+i];
18734             commentList[firstMove+i] = NULL;
18735         }
18736
18737         storedGames++;
18738         forwardMostMove = firstMove; // truncate game so we can start variation
18739 }
18740
18741 void
18742 PushTail (int firstMove, int lastMove)
18743 {
18744         if(appData.icsActive) { // only in local mode
18745                 forwardMostMove = currentMove; // mimic old ICS behavior
18746                 return;
18747         }
18748         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18749
18750         PushInner(firstMove, lastMove);
18751         if(storedGames == 1) GreyRevert(FALSE);
18752         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18753 }
18754
18755 void
18756 PopInner (Boolean annotate)
18757 {
18758         int i, j, nrMoves;
18759         char buf[8000], moveBuf[20];
18760
18761         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18762         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18763         nrMoves = savedLast[storedGames] - currentMove;
18764         if(annotate) {
18765                 int cnt = 10;
18766                 if(!WhiteOnMove(currentMove))
18767                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18768                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18769                 for(i=currentMove; i<forwardMostMove; i++) {
18770                         if(WhiteOnMove(i))
18771                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18772                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18773                         strcat(buf, moveBuf);
18774                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18775                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18776                 }
18777                 strcat(buf, ")");
18778         }
18779         for(i=1; i<=nrMoves; i++) { // copy last variation back
18780             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18781             for(j=0; j<MOVE_LEN; j++)
18782                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18783             for(j=0; j<2*MOVE_LEN; j++)
18784                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18785             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18786             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18787             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18788             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18789             commentList[currentMove+i] = commentList[framePtr+i];
18790             commentList[framePtr+i] = NULL;
18791         }
18792         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18793         framePtr = savedFramePtr[storedGames];
18794         gameInfo.result = savedResult[storedGames];
18795         if(gameInfo.resultDetails != NULL) {
18796             free(gameInfo.resultDetails);
18797       }
18798         gameInfo.resultDetails = savedDetails[storedGames];
18799         forwardMostMove = currentMove + nrMoves;
18800 }
18801
18802 Boolean
18803 PopTail (Boolean annotate)
18804 {
18805         if(appData.icsActive) return FALSE; // only in local mode
18806         if(!storedGames) return FALSE; // sanity
18807         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18808
18809         PopInner(annotate);
18810         if(currentMove < forwardMostMove) ForwardEvent(); else
18811         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18812
18813         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18814         return TRUE;
18815 }
18816
18817 void
18818 CleanupTail ()
18819 {       // remove all shelved variations
18820         int i;
18821         for(i=0; i<storedGames; i++) {
18822             if(savedDetails[i])
18823                 free(savedDetails[i]);
18824             savedDetails[i] = NULL;
18825         }
18826         for(i=framePtr; i<MAX_MOVES; i++) {
18827                 if(commentList[i]) free(commentList[i]);
18828                 commentList[i] = NULL;
18829         }
18830         framePtr = MAX_MOVES-1;
18831         storedGames = 0;
18832 }
18833
18834 void
18835 LoadVariation (int index, char *text)
18836 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18837         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18838         int level = 0, move;
18839
18840         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18841         // first find outermost bracketing variation
18842         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18843             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18844                 if(*p == '{') wait = '}'; else
18845                 if(*p == '[') wait = ']'; else
18846                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18847                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18848             }
18849             if(*p == wait) wait = NULLCHAR; // closing ]} found
18850             p++;
18851         }
18852         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18853         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18854         end[1] = NULLCHAR; // clip off comment beyond variation
18855         ToNrEvent(currentMove-1);
18856         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18857         // kludge: use ParsePV() to append variation to game
18858         move = currentMove;
18859         ParsePV(start, TRUE, TRUE);
18860         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18861         ClearPremoveHighlights();
18862         CommentPopDown();
18863         ToNrEvent(currentMove+1);
18864 }
18865
18866 void
18867 LoadTheme ()
18868 {
18869     char *p, *q, buf[MSG_SIZ];
18870     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18871         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18872         ParseArgsFromString(buf);
18873         ActivateTheme(TRUE); // also redo colors
18874         return;
18875     }
18876     p = nickName;
18877     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18878     {
18879         int len;
18880         q = appData.themeNames;
18881         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18882       if(appData.useBitmaps) {
18883         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18884                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18885                 appData.liteBackTextureMode,
18886                 appData.darkBackTextureMode );
18887       } else {
18888         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18889                 Col2Text(2),   // lightSquareColor
18890                 Col2Text(3) ); // darkSquareColor
18891       }
18892       if(appData.useBorder) {
18893         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18894                 appData.border);
18895       } else {
18896         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18897       }
18898       if(appData.useFont) {
18899         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18900                 appData.renderPiecesWithFont,
18901                 appData.fontToPieceTable,
18902                 Col2Text(9),    // appData.fontBackColorWhite
18903                 Col2Text(10) ); // appData.fontForeColorBlack
18904       } else {
18905         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18906                 appData.pieceDirectory);
18907         if(!appData.pieceDirectory[0])
18908           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18909                 Col2Text(0),   // whitePieceColor
18910                 Col2Text(1) ); // blackPieceColor
18911       }
18912       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18913                 Col2Text(4),   // highlightSquareColor
18914                 Col2Text(5) ); // premoveHighlightColor
18915         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18916         if(insert != q) insert[-1] = NULLCHAR;
18917         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18918         if(q)   free(q);
18919     }
18920     ActivateTheme(FALSE);
18921 }