Add support for Multi-PV Margin
[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 StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446
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 signed 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 Partner (ChessSquare *p)
5384 { // change piece into promotion partner if one shogi-promotes to the other
5385   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5386   ChessSquare partner;
5387   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5388   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5389   *p = partner;
5390   return 1;
5391 }
5392
5393 void
5394 Sweep (int step)
5395 {
5396     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5397     static int toggleFlag;
5398     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5399     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5400     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5401     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5402     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5403     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5404     do {
5405         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5406         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5407         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5408         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5409         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5410         if(!step) step = -1;
5411     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5412             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5413             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5414             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5415     if(toX >= 0) {
5416         int victim = boards[currentMove][toY][toX];
5417         boards[currentMove][toY][toX] = promoSweep;
5418         DrawPosition(FALSE, boards[currentMove]);
5419         boards[currentMove][toY][toX] = victim;
5420     } else
5421     ChangeDragPiece(promoSweep);
5422 }
5423
5424 int
5425 PromoScroll (int x, int y)
5426 {
5427   int step = 0;
5428
5429   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5430   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5431   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5432   if(!step) return FALSE;
5433   lastX = x; lastY = y;
5434   if((promoSweep < BlackPawn) == flipView) step = -step;
5435   if(step > 0) selectFlag = 1;
5436   if(!selectFlag) Sweep(step);
5437   return FALSE;
5438 }
5439
5440 void
5441 NextPiece (int step)
5442 {
5443     ChessSquare piece = boards[currentMove][toY][toX];
5444     do {
5445         pieceSweep -= step;
5446         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5447         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5448         if(!step) step = -1;
5449     } while(PieceToChar(pieceSweep) == '.');
5450     boards[currentMove][toY][toX] = pieceSweep;
5451     DrawPosition(FALSE, boards[currentMove]);
5452     boards[currentMove][toY][toX] = piece;
5453 }
5454 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5455 void
5456 AlphaRank (char *move, int n)
5457 {
5458 //    char *p = move, c; int x, y;
5459
5460     if (appData.debugMode) {
5461         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5462     }
5463
5464     if(move[1]=='*' &&
5465        move[2]>='0' && move[2]<='9' &&
5466        move[3]>='a' && move[3]<='x'    ) {
5467         move[1] = '@';
5468         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5469         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5470     } else
5471     if(move[0]>='0' && move[0]<='9' &&
5472        move[1]>='a' && move[1]<='x' &&
5473        move[2]>='0' && move[2]<='9' &&
5474        move[3]>='a' && move[3]<='x'    ) {
5475         /* input move, Shogi -> normal */
5476         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5477         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5478         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5479         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5480     } else
5481     if(move[1]=='@' &&
5482        move[3]>='0' && move[3]<='9' &&
5483        move[2]>='a' && move[2]<='x'    ) {
5484         move[1] = '*';
5485         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5486         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5487     } else
5488     if(
5489        move[0]>='a' && move[0]<='x' &&
5490        move[3]>='0' && move[3]<='9' &&
5491        move[2]>='a' && move[2]<='x'    ) {
5492          /* output move, normal -> Shogi */
5493         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5494         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5495         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5496         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5497         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5498     }
5499     if (appData.debugMode) {
5500         fprintf(debugFP, "   out = '%s'\n", move);
5501     }
5502 }
5503
5504 char yy_textstr[8000];
5505
5506 /* Parser for moves from gnuchess, ICS, or user typein box */
5507 Boolean
5508 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5509 {
5510     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5511
5512     switch (*moveType) {
5513       case WhitePromotion:
5514       case BlackPromotion:
5515       case WhiteNonPromotion:
5516       case BlackNonPromotion:
5517       case NormalMove:
5518       case FirstLeg:
5519       case WhiteCapturesEnPassant:
5520       case BlackCapturesEnPassant:
5521       case WhiteKingSideCastle:
5522       case WhiteQueenSideCastle:
5523       case BlackKingSideCastle:
5524       case BlackQueenSideCastle:
5525       case WhiteKingSideCastleWild:
5526       case WhiteQueenSideCastleWild:
5527       case BlackKingSideCastleWild:
5528       case BlackQueenSideCastleWild:
5529       /* Code added by Tord: */
5530       case WhiteHSideCastleFR:
5531       case WhiteASideCastleFR:
5532       case BlackHSideCastleFR:
5533       case BlackASideCastleFR:
5534       /* End of code added by Tord */
5535       case IllegalMove:         /* bug or odd chess variant */
5536         if(currentMoveString[1] == '@') { // illegal drop
5537           *fromX = WhiteOnMove(moveNum) ?
5538             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5539             (int) CharToPiece(ToLower(currentMoveString[0]));
5540           goto drop;
5541         }
5542         *fromX = currentMoveString[0] - AAA;
5543         *fromY = currentMoveString[1] - ONE;
5544         *toX = currentMoveString[2] - AAA;
5545         *toY = currentMoveString[3] - ONE;
5546         *promoChar = currentMoveString[4];
5547         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5548             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5549     if (appData.debugMode) {
5550         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5551     }
5552             *fromX = *fromY = *toX = *toY = 0;
5553             return FALSE;
5554         }
5555         if (appData.testLegality) {
5556           return (*moveType != IllegalMove);
5557         } else {
5558           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5559                          // [HGM] lion: if this is a double move we are less critical
5560                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5561         }
5562
5563       case WhiteDrop:
5564       case BlackDrop:
5565         *fromX = *moveType == WhiteDrop ?
5566           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5567           (int) CharToPiece(ToLower(currentMoveString[0]));
5568       drop:
5569         *fromY = DROP_RANK;
5570         *toX = currentMoveString[2] - AAA;
5571         *toY = currentMoveString[3] - ONE;
5572         *promoChar = NULLCHAR;
5573         return TRUE;
5574
5575       case AmbiguousMove:
5576       case ImpossibleMove:
5577       case EndOfFile:
5578       case ElapsedTime:
5579       case Comment:
5580       case PGNTag:
5581       case NAG:
5582       case WhiteWins:
5583       case BlackWins:
5584       case GameIsDrawn:
5585       default:
5586     if (appData.debugMode) {
5587         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5588     }
5589         /* bug? */
5590         *fromX = *fromY = *toX = *toY = 0;
5591         *promoChar = NULLCHAR;
5592         return FALSE;
5593     }
5594 }
5595
5596 Boolean pushed = FALSE;
5597 char *lastParseAttempt;
5598
5599 void
5600 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5601 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5602   int fromX, fromY, toX, toY; char promoChar;
5603   ChessMove moveType;
5604   Boolean valid;
5605   int nr = 0;
5606
5607   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5608   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5609     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5610     pushed = TRUE;
5611   }
5612   endPV = forwardMostMove;
5613   do {
5614     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5615     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5616     lastParseAttempt = pv;
5617     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5618     if(!valid && nr == 0 &&
5619        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5620         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5621         // Hande case where played move is different from leading PV move
5622         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5623         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5624         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5625         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5626           endPV += 2; // if position different, keep this
5627           moveList[endPV-1][0] = fromX + AAA;
5628           moveList[endPV-1][1] = fromY + ONE;
5629           moveList[endPV-1][2] = toX + AAA;
5630           moveList[endPV-1][3] = toY + ONE;
5631           parseList[endPV-1][0] = NULLCHAR;
5632           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5633         }
5634       }
5635     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5636     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5637     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5638     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5639         valid++; // allow comments in PV
5640         continue;
5641     }
5642     nr++;
5643     if(endPV+1 > framePtr) break; // no space, truncate
5644     if(!valid) break;
5645     endPV++;
5646     CopyBoard(boards[endPV], boards[endPV-1]);
5647     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5648     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5649     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5650     CoordsToAlgebraic(boards[endPV - 1],
5651                              PosFlags(endPV - 1),
5652                              fromY, fromX, toY, toX, promoChar,
5653                              parseList[endPV - 1]);
5654   } while(valid);
5655   if(atEnd == 2) return; // used hidden, for PV conversion
5656   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5657   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5658   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5659                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5660   DrawPosition(TRUE, boards[currentMove]);
5661 }
5662
5663 int
5664 MultiPV (ChessProgramState *cps, int kind)
5665 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5666         int i;
5667         for(i=0; i<cps->nrOptions; i++) {
5668             char *s = cps->option[i].name;
5669             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5670             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5671                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5672         }
5673         return -1;
5674 }
5675
5676 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5677 static int multi, pv_margin;
5678 static ChessProgramState *activeCps;
5679
5680 Boolean
5681 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5682 {
5683         int startPV, lineStart, origIndex = index;
5684         char *p, buf2[MSG_SIZ];
5685         ChessProgramState *cps = (pane ? &second : &first);
5686
5687         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5688         lastX = x; lastY = y;
5689         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5690         lineStart = startPV = index;
5691         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5692         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5693         index = startPV;
5694         do{ while(buf[index] && buf[index] != '\n') index++;
5695         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5696         buf[index] = 0;
5697         if(lineStart == 0 && gameMode == AnalyzeMode) {
5698             int n = 0;
5699             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5700             if(n == 0) { // click not on "fewer" or "more"
5701                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5702                     pv_margin = cps->option[multi].value;
5703                     activeCps = cps; // non-null signals margin adjustment
5704                 }
5705             } else if((multi = MultiPV(cps, 1)) >= 0) {
5706                 n += cps->option[multi].value; if(n < 1) n = 1;
5707                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5708                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5709                 cps->option[multi].value = n;
5710                 *start = *end = 0;
5711                 return FALSE;
5712             }
5713         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5714                 ExcludeClick(origIndex - lineStart);
5715                 return FALSE;
5716         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5717                 Collapse(origIndex - lineStart);
5718                 return FALSE;
5719         }
5720         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5721         *start = startPV; *end = index-1;
5722         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5723         return TRUE;
5724 }
5725
5726 char *
5727 PvToSAN (char *pv)
5728 {
5729         static char buf[10*MSG_SIZ];
5730         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5731         *buf = NULLCHAR;
5732         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5733         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5734         for(i = forwardMostMove; i<endPV; i++){
5735             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5736             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5737             k += strlen(buf+k);
5738         }
5739         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5740         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5741         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5742         endPV = savedEnd;
5743         return buf;
5744 }
5745
5746 Boolean
5747 LoadPV (int x, int y)
5748 { // called on right mouse click to load PV
5749   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5750   lastX = x; lastY = y;
5751   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5752   extendGame = FALSE;
5753   return TRUE;
5754 }
5755
5756 void
5757 UnLoadPV ()
5758 {
5759   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5760   if(activeCps) {
5761     if(pv_margin != activeCps->option[multi].value) {
5762       char buf[MSG_SIZ];
5763       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5764       SendToProgram(buf, activeCps);
5765       activeCps->option[multi].value = pv_margin;
5766     }
5767     activeCps = NULL;
5768     return;
5769   }
5770   if(endPV < 0) return;
5771   if(appData.autoCopyPV) CopyFENToClipboard();
5772   endPV = -1;
5773   if(extendGame && currentMove > forwardMostMove) {
5774         Boolean saveAnimate = appData.animate;
5775         if(pushed) {
5776             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5777                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5778             } else storedGames--; // abandon shelved tail of original game
5779         }
5780         pushed = FALSE;
5781         forwardMostMove = currentMove;
5782         currentMove = oldFMM;
5783         appData.animate = FALSE;
5784         ToNrEvent(forwardMostMove);
5785         appData.animate = saveAnimate;
5786   }
5787   currentMove = forwardMostMove;
5788   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5789   ClearPremoveHighlights();
5790   DrawPosition(TRUE, boards[currentMove]);
5791 }
5792
5793 void
5794 MovePV (int x, int y, int h)
5795 { // step through PV based on mouse coordinates (called on mouse move)
5796   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5797
5798   if(activeCps) { // adjusting engine's multi-pv margin
5799     if(x > lastX) pv_margin++; else
5800     if(x < lastX) pv_margin -= (pv_margin > 0);
5801     if(x != lastX) {
5802       char buf[MSG_SIZ];
5803       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5804       DisplayMessage(buf, "");
5805     }
5806     lastX = x;
5807     return;
5808   }
5809   // we must somehow check if right button is still down (might be released off board!)
5810   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5811   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5812   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5813   if(!step) return;
5814   lastX = x; lastY = y;
5815
5816   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5817   if(endPV < 0) return;
5818   if(y < margin) step = 1; else
5819   if(y > h - margin) step = -1;
5820   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5821   currentMove += step;
5822   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5823   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5824                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5825   DrawPosition(FALSE, boards[currentMove]);
5826 }
5827
5828
5829 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5830 // All positions will have equal probability, but the current method will not provide a unique
5831 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5832 #define DARK 1
5833 #define LITE 2
5834 #define ANY 3
5835
5836 int squaresLeft[4];
5837 int piecesLeft[(int)BlackPawn];
5838 int seed, nrOfShuffles;
5839
5840 void
5841 GetPositionNumber ()
5842 {       // sets global variable seed
5843         int i;
5844
5845         seed = appData.defaultFrcPosition;
5846         if(seed < 0) { // randomize based on time for negative FRC position numbers
5847                 for(i=0; i<50; i++) seed += random();
5848                 seed = random() ^ random() >> 8 ^ random() << 8;
5849                 if(seed<0) seed = -seed;
5850         }
5851 }
5852
5853 int
5854 put (Board board, int pieceType, int rank, int n, int shade)
5855 // put the piece on the (n-1)-th empty squares of the given shade
5856 {
5857         int i;
5858
5859         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5860                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5861                         board[rank][i] = (ChessSquare) pieceType;
5862                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5863                         squaresLeft[ANY]--;
5864                         piecesLeft[pieceType]--;
5865                         return i;
5866                 }
5867         }
5868         return -1;
5869 }
5870
5871
5872 void
5873 AddOnePiece (Board board, int pieceType, int rank, int shade)
5874 // calculate where the next piece goes, (any empty square), and put it there
5875 {
5876         int i;
5877
5878         i = seed % squaresLeft[shade];
5879         nrOfShuffles *= squaresLeft[shade];
5880         seed /= squaresLeft[shade];
5881         put(board, pieceType, rank, i, shade);
5882 }
5883
5884 void
5885 AddTwoPieces (Board board, int pieceType, int rank)
5886 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5887 {
5888         int i, n=squaresLeft[ANY], j=n-1, k;
5889
5890         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5891         i = seed % k;  // pick one
5892         nrOfShuffles *= k;
5893         seed /= k;
5894         while(i >= j) i -= j--;
5895         j = n - 1 - j; i += j;
5896         put(board, pieceType, rank, j, ANY);
5897         put(board, pieceType, rank, i, ANY);
5898 }
5899
5900 void
5901 SetUpShuffle (Board board, int number)
5902 {
5903         int i, p, first=1;
5904
5905         GetPositionNumber(); nrOfShuffles = 1;
5906
5907         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5908         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5909         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5910
5911         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5912
5913         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5914             p = (int) board[0][i];
5915             if(p < (int) BlackPawn) piecesLeft[p] ++;
5916             board[0][i] = EmptySquare;
5917         }
5918
5919         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5920             // shuffles restricted to allow normal castling put KRR first
5921             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5922                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5923             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5924                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5925             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5926                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5927             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5928                 put(board, WhiteRook, 0, 0, ANY);
5929             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5930         }
5931
5932         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5933             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5934             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5935                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5936                 while(piecesLeft[p] >= 2) {
5937                     AddOnePiece(board, p, 0, LITE);
5938                     AddOnePiece(board, p, 0, DARK);
5939                 }
5940                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5941             }
5942
5943         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5944             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5945             // but we leave King and Rooks for last, to possibly obey FRC restriction
5946             if(p == (int)WhiteRook) continue;
5947             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5948             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5949         }
5950
5951         // now everything is placed, except perhaps King (Unicorn) and Rooks
5952
5953         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5954             // Last King gets castling rights
5955             while(piecesLeft[(int)WhiteUnicorn]) {
5956                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5957                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5958             }
5959
5960             while(piecesLeft[(int)WhiteKing]) {
5961                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5962                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5963             }
5964
5965
5966         } else {
5967             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5968             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5969         }
5970
5971         // Only Rooks can be left; simply place them all
5972         while(piecesLeft[(int)WhiteRook]) {
5973                 i = put(board, WhiteRook, 0, 0, ANY);
5974                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5975                         if(first) {
5976                                 first=0;
5977                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5978                         }
5979                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5980                 }
5981         }
5982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5983             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5984         }
5985
5986         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5987 }
5988
5989 int
5990 ptclen (const char *s, char *escapes)
5991 {
5992     int n = 0;
5993     if(!*escapes) return strlen(s);
5994     while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
5995     return n;
5996 }
5997
5998 int
5999 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6000 /* [HGM] moved here from winboard.c because of its general usefulness */
6001 /*       Basically a safe strcpy that uses the last character as King */
6002 {
6003     int result = FALSE; int NrPieces, offs;
6004
6005     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6006                     && NrPieces >= 12 && !(NrPieces&1)) {
6007         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
6008
6009         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6010         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6011             char *p;
6012             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6013             table[i + offs] = map[j++];
6014             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
6015         }
6016         table[(int) WhiteKing]  = map[j++];
6017         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6018             char *p;
6019             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6020             table[WHITE_TO_BLACK i + offs] = map[j++];
6021             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
6022         }
6023         table[(int) BlackKing]  = map[j++];
6024
6025         result = TRUE;
6026     }
6027
6028     return result;
6029 }
6030
6031 int
6032 SetCharTable (unsigned char *table, const char * map)
6033 {
6034     return SetCharTableEsc(table, map, "");
6035 }
6036
6037 void
6038 Prelude (Board board)
6039 {       // [HGM] superchess: random selection of exo-pieces
6040         int i, j, k; ChessSquare p;
6041         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6042
6043         GetPositionNumber(); // use FRC position number
6044
6045         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6046             SetCharTable(pieceToChar, appData.pieceToCharTable);
6047             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6048                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6049         }
6050
6051         j = seed%4;                 seed /= 4;
6052         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6053         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6054         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6055         j = seed%3 + (seed%3 >= j); seed /= 3;
6056         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6057         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6058         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6059         j = seed%3;                 seed /= 3;
6060         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6061         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6062         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6063         j = seed%2 + (seed%2 >= j); seed /= 2;
6064         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6065         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6066         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6067         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6068         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6069         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6070         put(board, exoPieces[0],    0, 0, ANY);
6071         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6072 }
6073
6074 void
6075 InitPosition (int redraw)
6076 {
6077     ChessSquare (* pieces)[BOARD_FILES];
6078     int i, j, pawnRow=1, pieceRows=1, overrule,
6079     oldx = gameInfo.boardWidth,
6080     oldy = gameInfo.boardHeight,
6081     oldh = gameInfo.holdingsWidth;
6082     static int oldv;
6083
6084     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6085
6086     /* [AS] Initialize pv info list [HGM] and game status */
6087     {
6088         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6089             pvInfoList[i].depth = 0;
6090             boards[i][EP_STATUS] = EP_NONE;
6091             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6092         }
6093
6094         initialRulePlies = 0; /* 50-move counter start */
6095
6096         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6097         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6098     }
6099
6100
6101     /* [HGM] logic here is completely changed. In stead of full positions */
6102     /* the initialized data only consist of the two backranks. The switch */
6103     /* selects which one we will use, which is than copied to the Board   */
6104     /* initialPosition, which for the rest is initialized by Pawns and    */
6105     /* empty squares. This initial position is then copied to boards[0],  */
6106     /* possibly after shuffling, so that it remains available.            */
6107
6108     gameInfo.holdingsWidth = 0; /* default board sizes */
6109     gameInfo.boardWidth    = 8;
6110     gameInfo.boardHeight   = 8;
6111     gameInfo.holdingsSize  = 0;
6112     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6113     for(i=0; i<BOARD_FILES-6; i++)
6114       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6115     initialPosition[EP_STATUS] = EP_NONE;
6116     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6117     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6118     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6119          SetCharTable(pieceNickName, appData.pieceNickNames);
6120     else SetCharTable(pieceNickName, "............");
6121     pieces = FIDEArray;
6122
6123     switch (gameInfo.variant) {
6124     case VariantFischeRandom:
6125       shuffleOpenings = TRUE;
6126       appData.fischerCastling = TRUE;
6127     default:
6128       break;
6129     case VariantShatranj:
6130       pieces = ShatranjArray;
6131       nrCastlingRights = 0;
6132       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6133       break;
6134     case VariantMakruk:
6135       pieces = makrukArray;
6136       nrCastlingRights = 0;
6137       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6138       break;
6139     case VariantASEAN:
6140       pieces = aseanArray;
6141       nrCastlingRights = 0;
6142       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6143       break;
6144     case VariantTwoKings:
6145       pieces = twoKingsArray;
6146       break;
6147     case VariantGrand:
6148       pieces = GrandArray;
6149       nrCastlingRights = 0;
6150       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6151       gameInfo.boardWidth = 10;
6152       gameInfo.boardHeight = 10;
6153       gameInfo.holdingsSize = 7;
6154       break;
6155     case VariantCapaRandom:
6156       shuffleOpenings = TRUE;
6157       appData.fischerCastling = TRUE;
6158     case VariantCapablanca:
6159       pieces = CapablancaArray;
6160       gameInfo.boardWidth = 10;
6161       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6162       break;
6163     case VariantGothic:
6164       pieces = GothicArray;
6165       gameInfo.boardWidth = 10;
6166       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6167       break;
6168     case VariantSChess:
6169       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6170       gameInfo.holdingsSize = 7;
6171       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6172       break;
6173     case VariantJanus:
6174       pieces = JanusArray;
6175       gameInfo.boardWidth = 10;
6176       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6177       nrCastlingRights = 6;
6178         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6179         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6180         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6181         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6182         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6183         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6184       break;
6185     case VariantFalcon:
6186       pieces = FalconArray;
6187       gameInfo.boardWidth = 10;
6188       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6189       break;
6190     case VariantXiangqi:
6191       pieces = XiangqiArray;
6192       gameInfo.boardWidth  = 9;
6193       gameInfo.boardHeight = 10;
6194       nrCastlingRights = 0;
6195       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6196       break;
6197     case VariantShogi:
6198       pieces = ShogiArray;
6199       gameInfo.boardWidth  = 9;
6200       gameInfo.boardHeight = 9;
6201       gameInfo.holdingsSize = 7;
6202       nrCastlingRights = 0;
6203       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6204       break;
6205     case VariantChu:
6206       pieces = ChuArray; pieceRows = 3;
6207       gameInfo.boardWidth  = 12;
6208       gameInfo.boardHeight = 12;
6209       nrCastlingRights = 0;
6210       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
6211                                    "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
6212       break;
6213     case VariantCourier:
6214       pieces = CourierArray;
6215       gameInfo.boardWidth  = 12;
6216       nrCastlingRights = 0;
6217       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6218       break;
6219     case VariantKnightmate:
6220       pieces = KnightmateArray;
6221       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6222       break;
6223     case VariantSpartan:
6224       pieces = SpartanArray;
6225       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6226       break;
6227     case VariantLion:
6228       pieces = lionArray;
6229       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6230       break;
6231     case VariantChuChess:
6232       pieces = ChuChessArray;
6233       gameInfo.boardWidth = 10;
6234       gameInfo.boardHeight = 10;
6235       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6236       break;
6237     case VariantFairy:
6238       pieces = fairyArray;
6239       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6240       break;
6241     case VariantGreat:
6242       pieces = GreatArray;
6243       gameInfo.boardWidth = 10;
6244       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6245       gameInfo.holdingsSize = 8;
6246       break;
6247     case VariantSuper:
6248       pieces = FIDEArray;
6249       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6250       gameInfo.holdingsSize = 8;
6251       startedFromSetupPosition = TRUE;
6252       break;
6253     case VariantCrazyhouse:
6254     case VariantBughouse:
6255       pieces = FIDEArray;
6256       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6257       gameInfo.holdingsSize = 5;
6258       break;
6259     case VariantWildCastle:
6260       pieces = FIDEArray;
6261       /* !!?shuffle with kings guaranteed to be on d or e file */
6262       shuffleOpenings = 1;
6263       break;
6264     case VariantNoCastle:
6265       pieces = FIDEArray;
6266       nrCastlingRights = 0;
6267       /* !!?unconstrained back-rank shuffle */
6268       shuffleOpenings = 1;
6269       break;
6270     }
6271
6272     overrule = 0;
6273     if(appData.NrFiles >= 0) {
6274         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6275         gameInfo.boardWidth = appData.NrFiles;
6276     }
6277     if(appData.NrRanks >= 0) {
6278         gameInfo.boardHeight = appData.NrRanks;
6279     }
6280     if(appData.holdingsSize >= 0) {
6281         i = appData.holdingsSize;
6282         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6283         gameInfo.holdingsSize = i;
6284     }
6285     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6286     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6287         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6288
6289     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6290     if(pawnRow < 1) pawnRow = 1;
6291     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6292        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6293     if(gameInfo.variant == VariantChu) pawnRow = 3;
6294
6295     /* User pieceToChar list overrules defaults */
6296     if(appData.pieceToCharTable != NULL)
6297         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6298
6299     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6300
6301         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6302             s = (ChessSquare) 0; /* account holding counts in guard band */
6303         for( i=0; i<BOARD_HEIGHT; i++ )
6304             initialPosition[i][j] = s;
6305
6306         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6307         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6308         initialPosition[pawnRow][j] = WhitePawn;
6309         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6310         if(gameInfo.variant == VariantXiangqi) {
6311             if(j&1) {
6312                 initialPosition[pawnRow][j] =
6313                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6314                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6315                    initialPosition[2][j] = WhiteCannon;
6316                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6317                 }
6318             }
6319         }
6320         if(gameInfo.variant == VariantChu) {
6321              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6322                initialPosition[pawnRow+1][j] = WhiteCobra,
6323                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6324              for(i=1; i<pieceRows; i++) {
6325                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6326                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6327              }
6328         }
6329         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6330             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6331                initialPosition[0][j] = WhiteRook;
6332                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6333             }
6334         }
6335         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6336     }
6337     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6338     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6339
6340             j=BOARD_LEFT+1;
6341             initialPosition[1][j] = WhiteBishop;
6342             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6343             j=BOARD_RGHT-2;
6344             initialPosition[1][j] = WhiteRook;
6345             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6346     }
6347
6348     if( nrCastlingRights == -1) {
6349         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6350         /*       This sets default castling rights from none to normal corners   */
6351         /* Variants with other castling rights must set them themselves above    */
6352         nrCastlingRights = 6;
6353
6354         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6355         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6356         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6357         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6358         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6359         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6360      }
6361
6362      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6363      if(gameInfo.variant == VariantGreat) { // promotion commoners
6364         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6365         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6366         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6367         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6368      }
6369      if( gameInfo.variant == VariantSChess ) {
6370       initialPosition[1][0] = BlackMarshall;
6371       initialPosition[2][0] = BlackAngel;
6372       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6373       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6374       initialPosition[1][1] = initialPosition[2][1] =
6375       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6376      }
6377   if (appData.debugMode) {
6378     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6379   }
6380     if(shuffleOpenings) {
6381         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6382         startedFromSetupPosition = TRUE;
6383     }
6384     if(startedFromPositionFile) {
6385       /* [HGM] loadPos: use PositionFile for every new game */
6386       CopyBoard(initialPosition, filePosition);
6387       for(i=0; i<nrCastlingRights; i++)
6388           initialRights[i] = filePosition[CASTLING][i];
6389       startedFromSetupPosition = TRUE;
6390     }
6391
6392     CopyBoard(boards[0], initialPosition);
6393
6394     if(oldx != gameInfo.boardWidth ||
6395        oldy != gameInfo.boardHeight ||
6396        oldv != gameInfo.variant ||
6397        oldh != gameInfo.holdingsWidth
6398                                          )
6399             InitDrawingSizes(-2 ,0);
6400
6401     oldv = gameInfo.variant;
6402     if (redraw)
6403       DrawPosition(TRUE, boards[currentMove]);
6404 }
6405
6406 void
6407 SendBoard (ChessProgramState *cps, int moveNum)
6408 {
6409     char message[MSG_SIZ];
6410
6411     if (cps->useSetboard) {
6412       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6413       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6414       SendToProgram(message, cps);
6415       free(fen);
6416
6417     } else {
6418       ChessSquare *bp;
6419       int i, j, left=0, right=BOARD_WIDTH;
6420       /* Kludge to set black to move, avoiding the troublesome and now
6421        * deprecated "black" command.
6422        */
6423       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6424         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6425
6426       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6427
6428       SendToProgram("edit\n", cps);
6429       SendToProgram("#\n", cps);
6430       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6431         bp = &boards[moveNum][i][left];
6432         for (j = left; j < right; j++, bp++) {
6433           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6434           if ((int) *bp < (int) BlackPawn) {
6435             if(j == BOARD_RGHT+1)
6436                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6437             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6438             if(message[0] == '+' || message[0] == '~') {
6439               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6440                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6441                         AAA + j, ONE + i - '0');
6442             }
6443             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6444                 message[1] = BOARD_RGHT   - 1 - j + '1';
6445                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6446             }
6447             SendToProgram(message, cps);
6448           }
6449         }
6450       }
6451
6452       SendToProgram("c\n", cps);
6453       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6454         bp = &boards[moveNum][i][left];
6455         for (j = left; j < right; j++, bp++) {
6456           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6457           if (((int) *bp != (int) EmptySquare)
6458               && ((int) *bp >= (int) BlackPawn)) {
6459             if(j == BOARD_LEFT-2)
6460                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6461             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6462                     AAA + j, ONE + i - '0');
6463             if(message[0] == '+' || message[0] == '~') {
6464               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6465                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6466                         AAA + j, ONE + i - '0');
6467             }
6468             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6469                 message[1] = BOARD_RGHT   - 1 - j + '1';
6470                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6471             }
6472             SendToProgram(message, cps);
6473           }
6474         }
6475       }
6476
6477       SendToProgram(".\n", cps);
6478     }
6479     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6480 }
6481
6482 char exclusionHeader[MSG_SIZ];
6483 int exCnt, excludePtr;
6484 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6485 static Exclusion excluTab[200];
6486 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6487
6488 static void
6489 WriteMap (int s)
6490 {
6491     int j;
6492     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6493     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6494 }
6495
6496 static void
6497 ClearMap ()
6498 {
6499     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6500     excludePtr = 24; exCnt = 0;
6501     WriteMap(0);
6502 }
6503
6504 static void
6505 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6506 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6507     char buf[2*MOVE_LEN], *p;
6508     Exclusion *e = excluTab;
6509     int i;
6510     for(i=0; i<exCnt; i++)
6511         if(e[i].ff == fromX && e[i].fr == fromY &&
6512            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6513     if(i == exCnt) { // was not in exclude list; add it
6514         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6515         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6516             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6517             return; // abort
6518         }
6519         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6520         excludePtr++; e[i].mark = excludePtr++;
6521         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6522         exCnt++;
6523     }
6524     exclusionHeader[e[i].mark] = state;
6525 }
6526
6527 static int
6528 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6529 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6530     char buf[MSG_SIZ];
6531     int j, k;
6532     ChessMove moveType;
6533     if((signed char)promoChar == -1) { // kludge to indicate best move
6534         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6535             return 1; // if unparsable, abort
6536     }
6537     // update exclusion map (resolving toggle by consulting existing state)
6538     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6539     j = k%8; k >>= 3;
6540     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6541     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6542          excludeMap[k] |=   1<<j;
6543     else excludeMap[k] &= ~(1<<j);
6544     // update header
6545     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6546     // inform engine
6547     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6548     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6549     SendToBoth(buf);
6550     return (state == '+');
6551 }
6552
6553 static void
6554 ExcludeClick (int index)
6555 {
6556     int i, j;
6557     Exclusion *e = excluTab;
6558     if(index < 25) { // none, best or tail clicked
6559         if(index < 13) { // none: include all
6560             WriteMap(0); // clear map
6561             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6562             SendToBoth("include all\n"); // and inform engine
6563         } else if(index > 18) { // tail
6564             if(exclusionHeader[19] == '-') { // tail was excluded
6565                 SendToBoth("include all\n");
6566                 WriteMap(0); // clear map completely
6567                 // now re-exclude selected moves
6568                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6569                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6570             } else { // tail was included or in mixed state
6571                 SendToBoth("exclude all\n");
6572                 WriteMap(0xFF); // fill map completely
6573                 // now re-include selected moves
6574                 j = 0; // count them
6575                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6576                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6577                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6578             }
6579         } else { // best
6580             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6581         }
6582     } else {
6583         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6584             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6585             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6586             break;
6587         }
6588     }
6589 }
6590
6591 ChessSquare
6592 DefaultPromoChoice (int white)
6593 {
6594     ChessSquare result;
6595     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6596        gameInfo.variant == VariantMakruk)
6597         result = WhiteFerz; // no choice
6598     else if(gameInfo.variant == VariantASEAN)
6599         result = WhiteRook; // no choice
6600     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6601         result= WhiteKing; // in Suicide Q is the last thing we want
6602     else if(gameInfo.variant == VariantSpartan)
6603         result = white ? WhiteQueen : WhiteAngel;
6604     else result = WhiteQueen;
6605     if(!white) result = WHITE_TO_BLACK result;
6606     return result;
6607 }
6608
6609 static int autoQueen; // [HGM] oneclick
6610
6611 int
6612 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6613 {
6614     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6615     /* [HGM] add Shogi promotions */
6616     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6617     ChessSquare piece, partner;
6618     ChessMove moveType;
6619     Boolean premove;
6620
6621     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6622     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6623
6624     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6625       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6626         return FALSE;
6627
6628     piece = boards[currentMove][fromY][fromX];
6629     if(gameInfo.variant == VariantChu) {
6630         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6631         promotionZoneSize = BOARD_HEIGHT/3;
6632         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6633     } else if(gameInfo.variant == VariantShogi) {
6634         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6635         highestPromotingPiece = (int)WhiteAlfil;
6636     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6637         promotionZoneSize = 3;
6638     }
6639
6640     // Treat Lance as Pawn when it is not representing Amazon or Lance
6641     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6642         if(piece == WhiteLance) piece = WhitePawn; else
6643         if(piece == BlackLance) piece = BlackPawn;
6644     }
6645
6646     // next weed out all moves that do not touch the promotion zone at all
6647     if((int)piece >= BlackPawn) {
6648         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6649              return FALSE;
6650         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6651         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6652     } else {
6653         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6654            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6655         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6656              return FALSE;
6657     }
6658
6659     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6660
6661     // weed out mandatory Shogi promotions
6662     if(gameInfo.variant == VariantShogi) {
6663         if(piece >= BlackPawn) {
6664             if(toY == 0 && piece == BlackPawn ||
6665                toY == 0 && piece == BlackQueen ||
6666                toY <= 1 && piece == BlackKnight) {
6667                 *promoChoice = '+';
6668                 return FALSE;
6669             }
6670         } else {
6671             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6672                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6673                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6674                 *promoChoice = '+';
6675                 return FALSE;
6676             }
6677         }
6678     }
6679
6680     // weed out obviously illegal Pawn moves
6681     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6682         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6683         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6684         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6685         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6686         // note we are not allowed to test for valid (non-)capture, due to premove
6687     }
6688
6689     // we either have a choice what to promote to, or (in Shogi) whether to promote
6690     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6691        gameInfo.variant == VariantMakruk) {
6692         ChessSquare p=BlackFerz;  // no choice
6693         while(p < EmptySquare) {  //but make sure we use piece that exists
6694             *promoChoice = PieceToChar(p++);
6695             if(*promoChoice != '.') break;
6696         }
6697         return FALSE;
6698     }
6699     // no sense asking what we must promote to if it is going to explode...
6700     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6701         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6702         return FALSE;
6703     }
6704     // give caller the default choice even if we will not make it
6705     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6706     partner = piece; // pieces can promote if the pieceToCharTable says so
6707     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6708     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6709     if(        sweepSelect && gameInfo.variant != VariantGreat
6710                            && gameInfo.variant != VariantGrand
6711                            && gameInfo.variant != VariantSuper) return FALSE;
6712     if(autoQueen) return FALSE; // predetermined
6713
6714     // suppress promotion popup on illegal moves that are not premoves
6715     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6716               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6717     if(appData.testLegality && !premove) {
6718         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6719                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6720         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6721         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6722             return FALSE;
6723     }
6724
6725     return TRUE;
6726 }
6727
6728 int
6729 InPalace (int row, int column)
6730 {   /* [HGM] for Xiangqi */
6731     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6732          column < (BOARD_WIDTH + 4)/2 &&
6733          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6734     return FALSE;
6735 }
6736
6737 int
6738 PieceForSquare (int x, int y)
6739 {
6740   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6741      return -1;
6742   else
6743      return boards[currentMove][y][x];
6744 }
6745
6746 int
6747 OKToStartUserMove (int x, int y)
6748 {
6749     ChessSquare from_piece;
6750     int white_piece;
6751
6752     if (matchMode) return FALSE;
6753     if (gameMode == EditPosition) return TRUE;
6754
6755     if (x >= 0 && y >= 0)
6756       from_piece = boards[currentMove][y][x];
6757     else
6758       from_piece = EmptySquare;
6759
6760     if (from_piece == EmptySquare) return FALSE;
6761
6762     white_piece = (int)from_piece >= (int)WhitePawn &&
6763       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6764
6765     switch (gameMode) {
6766       case AnalyzeFile:
6767       case TwoMachinesPlay:
6768       case EndOfGame:
6769         return FALSE;
6770
6771       case IcsObserving:
6772       case IcsIdle:
6773         return FALSE;
6774
6775       case MachinePlaysWhite:
6776       case IcsPlayingBlack:
6777         if (appData.zippyPlay) return FALSE;
6778         if (white_piece) {
6779             DisplayMoveError(_("You are playing Black"));
6780             return FALSE;
6781         }
6782         break;
6783
6784       case MachinePlaysBlack:
6785       case IcsPlayingWhite:
6786         if (appData.zippyPlay) return FALSE;
6787         if (!white_piece) {
6788             DisplayMoveError(_("You are playing White"));
6789             return FALSE;
6790         }
6791         break;
6792
6793       case PlayFromGameFile:
6794             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6795       case EditGame:
6796         if (!white_piece && WhiteOnMove(currentMove)) {
6797             DisplayMoveError(_("It is White's turn"));
6798             return FALSE;
6799         }
6800         if (white_piece && !WhiteOnMove(currentMove)) {
6801             DisplayMoveError(_("It is Black's turn"));
6802             return FALSE;
6803         }
6804         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6805             /* Editing correspondence game history */
6806             /* Could disallow this or prompt for confirmation */
6807             cmailOldMove = -1;
6808         }
6809         break;
6810
6811       case BeginningOfGame:
6812         if (appData.icsActive) return FALSE;
6813         if (!appData.noChessProgram) {
6814             if (!white_piece) {
6815                 DisplayMoveError(_("You are playing White"));
6816                 return FALSE;
6817             }
6818         }
6819         break;
6820
6821       case Training:
6822         if (!white_piece && WhiteOnMove(currentMove)) {
6823             DisplayMoveError(_("It is White's turn"));
6824             return FALSE;
6825         }
6826         if (white_piece && !WhiteOnMove(currentMove)) {
6827             DisplayMoveError(_("It is Black's turn"));
6828             return FALSE;
6829         }
6830         break;
6831
6832       default:
6833       case IcsExamining:
6834         break;
6835     }
6836     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6837         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6838         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6839         && gameMode != AnalyzeFile && gameMode != Training) {
6840         DisplayMoveError(_("Displayed position is not current"));
6841         return FALSE;
6842     }
6843     return TRUE;
6844 }
6845
6846 Boolean
6847 OnlyMove (int *x, int *y, Boolean captures)
6848 {
6849     DisambiguateClosure cl;
6850     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6851     switch(gameMode) {
6852       case MachinePlaysBlack:
6853       case IcsPlayingWhite:
6854       case BeginningOfGame:
6855         if(!WhiteOnMove(currentMove)) return FALSE;
6856         break;
6857       case MachinePlaysWhite:
6858       case IcsPlayingBlack:
6859         if(WhiteOnMove(currentMove)) return FALSE;
6860         break;
6861       case EditGame:
6862         break;
6863       default:
6864         return FALSE;
6865     }
6866     cl.pieceIn = EmptySquare;
6867     cl.rfIn = *y;
6868     cl.ffIn = *x;
6869     cl.rtIn = -1;
6870     cl.ftIn = -1;
6871     cl.promoCharIn = NULLCHAR;
6872     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6873     if( cl.kind == NormalMove ||
6874         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6875         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6876         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6877       fromX = cl.ff;
6878       fromY = cl.rf;
6879       *x = cl.ft;
6880       *y = cl.rt;
6881       return TRUE;
6882     }
6883     if(cl.kind != ImpossibleMove) return FALSE;
6884     cl.pieceIn = EmptySquare;
6885     cl.rfIn = -1;
6886     cl.ffIn = -1;
6887     cl.rtIn = *y;
6888     cl.ftIn = *x;
6889     cl.promoCharIn = NULLCHAR;
6890     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6891     if( cl.kind == NormalMove ||
6892         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6893         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6894         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6895       fromX = cl.ff;
6896       fromY = cl.rf;
6897       *x = cl.ft;
6898       *y = cl.rt;
6899       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6900       return TRUE;
6901     }
6902     return FALSE;
6903 }
6904
6905 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6906 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6907 int lastLoadGameUseList = FALSE;
6908 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6909 ChessMove lastLoadGameStart = EndOfFile;
6910 int doubleClick;
6911 Boolean addToBookFlag;
6912
6913 void
6914 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6915 {
6916     ChessMove moveType;
6917     ChessSquare pup;
6918     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6919
6920     /* Check if the user is playing in turn.  This is complicated because we
6921        let the user "pick up" a piece before it is his turn.  So the piece he
6922        tried to pick up may have been captured by the time he puts it down!
6923        Therefore we use the color the user is supposed to be playing in this
6924        test, not the color of the piece that is currently on the starting
6925        square---except in EditGame mode, where the user is playing both
6926        sides; fortunately there the capture race can't happen.  (It can
6927        now happen in IcsExamining mode, but that's just too bad.  The user
6928        will get a somewhat confusing message in that case.)
6929        */
6930
6931     switch (gameMode) {
6932       case AnalyzeFile:
6933       case TwoMachinesPlay:
6934       case EndOfGame:
6935       case IcsObserving:
6936       case IcsIdle:
6937         /* We switched into a game mode where moves are not accepted,
6938            perhaps while the mouse button was down. */
6939         return;
6940
6941       case MachinePlaysWhite:
6942         /* User is moving for Black */
6943         if (WhiteOnMove(currentMove)) {
6944             DisplayMoveError(_("It is White's turn"));
6945             return;
6946         }
6947         break;
6948
6949       case MachinePlaysBlack:
6950         /* User is moving for White */
6951         if (!WhiteOnMove(currentMove)) {
6952             DisplayMoveError(_("It is Black's turn"));
6953             return;
6954         }
6955         break;
6956
6957       case PlayFromGameFile:
6958             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6959       case EditGame:
6960       case IcsExamining:
6961       case BeginningOfGame:
6962       case AnalyzeMode:
6963       case Training:
6964         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6965         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6966             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6967             /* User is moving for Black */
6968             if (WhiteOnMove(currentMove)) {
6969                 DisplayMoveError(_("It is White's turn"));
6970                 return;
6971             }
6972         } else {
6973             /* User is moving for White */
6974             if (!WhiteOnMove(currentMove)) {
6975                 DisplayMoveError(_("It is Black's turn"));
6976                 return;
6977             }
6978         }
6979         break;
6980
6981       case IcsPlayingBlack:
6982         /* User is moving for Black */
6983         if (WhiteOnMove(currentMove)) {
6984             if (!appData.premove) {
6985                 DisplayMoveError(_("It is White's turn"));
6986             } else if (toX >= 0 && toY >= 0) {
6987                 premoveToX = toX;
6988                 premoveToY = toY;
6989                 premoveFromX = fromX;
6990                 premoveFromY = fromY;
6991                 premovePromoChar = promoChar;
6992                 gotPremove = 1;
6993                 if (appData.debugMode)
6994                     fprintf(debugFP, "Got premove: fromX %d,"
6995                             "fromY %d, toX %d, toY %d\n",
6996                             fromX, fromY, toX, toY);
6997             }
6998             return;
6999         }
7000         break;
7001
7002       case IcsPlayingWhite:
7003         /* User is moving for White */
7004         if (!WhiteOnMove(currentMove)) {
7005             if (!appData.premove) {
7006                 DisplayMoveError(_("It is Black's turn"));
7007             } else if (toX >= 0 && toY >= 0) {
7008                 premoveToX = toX;
7009                 premoveToY = toY;
7010                 premoveFromX = fromX;
7011                 premoveFromY = fromY;
7012                 premovePromoChar = promoChar;
7013                 gotPremove = 1;
7014                 if (appData.debugMode)
7015                     fprintf(debugFP, "Got premove: fromX %d,"
7016                             "fromY %d, toX %d, toY %d\n",
7017                             fromX, fromY, toX, toY);
7018             }
7019             return;
7020         }
7021         break;
7022
7023       default:
7024         break;
7025
7026       case EditPosition:
7027         /* EditPosition, empty square, or different color piece;
7028            click-click move is possible */
7029         if (toX == -2 || toY == -2) {
7030             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         } else if (toX >= 0 && toY >= 0) {
7034             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7035                 ChessSquare q, p = boards[0][rf][ff];
7036                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7037                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7038                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7039                 if(PieceToChar(q) == '+') gatingPiece = p;
7040             }
7041             boards[0][toY][toX] = boards[0][fromY][fromX];
7042             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7043                 if(boards[0][fromY][0] != EmptySquare) {
7044                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7045                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7046                 }
7047             } else
7048             if(fromX == BOARD_RGHT+1) {
7049                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7050                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7051                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7052                 }
7053             } else
7054             boards[0][fromY][fromX] = gatingPiece;
7055             DrawPosition(FALSE, boards[currentMove]);
7056             return;
7057         }
7058         return;
7059     }
7060
7061     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7062     pup = boards[currentMove][toY][toX];
7063
7064     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7065     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7066          if( pup != EmptySquare ) return;
7067          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7068            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7069                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7070            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7071            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7072            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7073            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7074          fromY = DROP_RANK;
7075     }
7076
7077     /* [HGM] always test for legality, to get promotion info */
7078     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7079                                          fromY, fromX, toY, toX, promoChar);
7080
7081     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7082
7083     /* [HGM] but possibly ignore an IllegalMove result */
7084     if (appData.testLegality) {
7085         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7086             DisplayMoveError(_("Illegal move"));
7087             return;
7088         }
7089     }
7090
7091     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7092         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7093              ClearPremoveHighlights(); // was included
7094         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7095         return;
7096     }
7097
7098     if(addToBookFlag) { // adding moves to book
7099         char buf[MSG_SIZ], move[MSG_SIZ];
7100         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7101         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');
7102         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7103         AddBookMove(buf);
7104         addToBookFlag = FALSE;
7105         ClearHighlights();
7106         return;
7107     }
7108
7109     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7110 }
7111
7112 /* Common tail of UserMoveEvent and DropMenuEvent */
7113 int
7114 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7115 {
7116     char *bookHit = 0;
7117
7118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7119         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7121         if(WhiteOnMove(currentMove)) {
7122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7123         } else {
7124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7125         }
7126     }
7127
7128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7129        move type in caller when we know the move is a legal promotion */
7130     if(moveType == NormalMove && promoChar)
7131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7132
7133     /* [HGM] <popupFix> The following if has been moved here from
7134        UserMoveEvent(). Because it seemed to belong here (why not allow
7135        piece drops in training games?), and because it can only be
7136        performed after it is known to what we promote. */
7137     if (gameMode == Training) {
7138       /* compare the move played on the board to the next move in the
7139        * game. If they match, display the move and the opponent's response.
7140        * If they don't match, display an error message.
7141        */
7142       int saveAnimate;
7143       Board testBoard;
7144       CopyBoard(testBoard, boards[currentMove]);
7145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7146
7147       if (CompareBoards(testBoard, boards[currentMove+1])) {
7148         ForwardInner(currentMove+1);
7149
7150         /* Autoplay the opponent's response.
7151          * if appData.animate was TRUE when Training mode was entered,
7152          * the response will be animated.
7153          */
7154         saveAnimate = appData.animate;
7155         appData.animate = animateTraining;
7156         ForwardInner(currentMove+1);
7157         appData.animate = saveAnimate;
7158
7159         /* check for the end of the game */
7160         if (currentMove >= forwardMostMove) {
7161           gameMode = PlayFromGameFile;
7162           ModeHighlight();
7163           SetTrainingModeOff();
7164           DisplayInformation(_("End of game"));
7165         }
7166       } else {
7167         DisplayError(_("Incorrect move"), 0);
7168       }
7169       return 1;
7170     }
7171
7172   /* Ok, now we know that the move is good, so we can kill
7173      the previous line in Analysis Mode */
7174   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7175                                 && currentMove < forwardMostMove) {
7176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7177     else forwardMostMove = currentMove;
7178   }
7179
7180   ClearMap();
7181
7182   /* If we need the chess program but it's dead, restart it */
7183   ResurrectChessProgram();
7184
7185   /* A user move restarts a paused game*/
7186   if (pausing)
7187     PauseEvent();
7188
7189   thinkOutput[0] = NULLCHAR;
7190
7191   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7192
7193   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7194     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7195     return 1;
7196   }
7197
7198   if (gameMode == BeginningOfGame) {
7199     if (appData.noChessProgram) {
7200       gameMode = EditGame;
7201       SetGameInfo();
7202     } else {
7203       char buf[MSG_SIZ];
7204       gameMode = MachinePlaysBlack;
7205       StartClocks();
7206       SetGameInfo();
7207       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7208       DisplayTitle(buf);
7209       if (first.sendName) {
7210         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7211         SendToProgram(buf, &first);
7212       }
7213       StartClocks();
7214     }
7215     ModeHighlight();
7216   }
7217
7218   /* Relay move to ICS or chess engine */
7219   if (appData.icsActive) {
7220     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7221         gameMode == IcsExamining) {
7222       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7223         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7224         SendToICS("draw ");
7225         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7226       }
7227       // also send plain move, in case ICS does not understand atomic claims
7228       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7229       ics_user_moved = 1;
7230     }
7231   } else {
7232     if (first.sendTime && (gameMode == BeginningOfGame ||
7233                            gameMode == MachinePlaysWhite ||
7234                            gameMode == MachinePlaysBlack)) {
7235       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7236     }
7237     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7238          // [HGM] book: if program might be playing, let it use book
7239         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7240         first.maybeThinking = TRUE;
7241     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7242         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7243         SendBoard(&first, currentMove+1);
7244         if(second.analyzing) {
7245             if(!second.useSetboard) SendToProgram("undo\n", &second);
7246             SendBoard(&second, currentMove+1);
7247         }
7248     } else {
7249         SendMoveToProgram(forwardMostMove-1, &first);
7250         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7251     }
7252     if (currentMove == cmailOldMove + 1) {
7253       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7254     }
7255   }
7256
7257   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7258
7259   switch (gameMode) {
7260   case EditGame:
7261     if(appData.testLegality)
7262     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7263     case MT_NONE:
7264     case MT_CHECK:
7265       break;
7266     case MT_CHECKMATE:
7267     case MT_STAINMATE:
7268       if (WhiteOnMove(currentMove)) {
7269         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7270       } else {
7271         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7272       }
7273       break;
7274     case MT_STALEMATE:
7275       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7276       break;
7277     }
7278     break;
7279
7280   case MachinePlaysBlack:
7281   case MachinePlaysWhite:
7282     /* disable certain menu options while machine is thinking */
7283     SetMachineThinkingEnables();
7284     break;
7285
7286   default:
7287     break;
7288   }
7289
7290   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7291   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7292
7293   if(bookHit) { // [HGM] book: simulate book reply
7294         static char bookMove[MSG_SIZ]; // a bit generous?
7295
7296         programStats.nodes = programStats.depth = programStats.time =
7297         programStats.score = programStats.got_only_move = 0;
7298         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7299
7300         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7301         strcat(bookMove, bookHit);
7302         HandleMachineMove(bookMove, &first);
7303   }
7304   return 1;
7305 }
7306
7307 void
7308 MarkByFEN(char *fen)
7309 {
7310         int r, f;
7311         if(!appData.markers || !appData.highlightDragging) return;
7312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7313         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7314         while(*fen) {
7315             int s = 0;
7316             marker[r][f] = 0;
7317             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7318             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7319             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7320             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7321             if(*fen == 'T') marker[r][f++] = 0; else
7322             if(*fen == 'Y') marker[r][f++] = 1; else
7323             if(*fen == 'G') marker[r][f++] = 3; else
7324             if(*fen == 'B') marker[r][f++] = 4; else
7325             if(*fen == 'C') marker[r][f++] = 5; else
7326             if(*fen == 'M') marker[r][f++] = 6; else
7327             if(*fen == 'W') marker[r][f++] = 7; else
7328             if(*fen == 'D') marker[r][f++] = 8; else
7329             if(*fen == 'R') marker[r][f++] = 2; else {
7330                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7331               f += s; fen -= s>0;
7332             }
7333             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7334             if(r < 0) break;
7335             fen++;
7336         }
7337         DrawPosition(TRUE, NULL);
7338 }
7339
7340 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7341
7342 void
7343 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7344 {
7345     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7346     Markers *m = (Markers *) closure;
7347     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7348         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7349                          || kind == WhiteCapturesEnPassant
7350                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7351     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7352 }
7353
7354 static int hoverSavedValid;
7355
7356 void
7357 MarkTargetSquares (int clear)
7358 {
7359   int x, y, sum=0;
7360   if(clear) { // no reason to ever suppress clearing
7361     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7362     hoverSavedValid = 0;
7363     if(!sum) return; // nothing was cleared,no redraw needed
7364   } else {
7365     int capt = 0;
7366     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7367        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7368     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7369     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7370       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7371       if(capt)
7372       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7373     }
7374   }
7375   DrawPosition(FALSE, NULL);
7376 }
7377
7378 int
7379 Explode (Board board, int fromX, int fromY, int toX, int toY)
7380 {
7381     if(gameInfo.variant == VariantAtomic &&
7382        (board[toY][toX] != EmptySquare ||                     // capture?
7383         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7384                          board[fromY][fromX] == BlackPawn   )
7385       )) {
7386         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7387         return TRUE;
7388     }
7389     return FALSE;
7390 }
7391
7392 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7393
7394 int
7395 CanPromote (ChessSquare piece, int y)
7396 {
7397         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7398         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7399         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7400         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7401            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7402            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7403          gameInfo.variant == VariantMakruk) return FALSE;
7404         return (piece == BlackPawn && y <= zone ||
7405                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7406                 piece == BlackLance && y <= zone ||
7407                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7408 }
7409
7410 void
7411 HoverEvent (int xPix, int yPix, int x, int y)
7412 {
7413         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7414         int r, f;
7415         if(!first.highlight) return;
7416         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7417         if(x == oldX && y == oldY) return; // only do something if we enter new square
7418         oldFromX = fromX; oldFromY = fromY;
7419         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7420           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7421             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7422           hoverSavedValid = 1;
7423         } else if(oldX != x || oldY != y) {
7424           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7425           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7426           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7427             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7428           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7429             char buf[MSG_SIZ];
7430             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7431             SendToProgram(buf, &first);
7432           }
7433           oldX = x; oldY = y;
7434 //        SetHighlights(fromX, fromY, x, y);
7435         }
7436 }
7437
7438 void ReportClick(char *action, int x, int y)
7439 {
7440         char buf[MSG_SIZ]; // Inform engine of what user does
7441         int r, f;
7442         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7443           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7444             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7445         if(!first.highlight || gameMode == EditPosition) return;
7446         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7447         SendToProgram(buf, &first);
7448 }
7449
7450 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7451
7452 void
7453 LeftClick (ClickType clickType, int xPix, int yPix)
7454 {
7455     int x, y;
7456     Boolean saveAnimate;
7457     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7458     char promoChoice = NULLCHAR;
7459     ChessSquare piece;
7460     static TimeMark lastClickTime, prevClickTime;
7461
7462     x = EventToSquare(xPix, BOARD_WIDTH);
7463     y = EventToSquare(yPix, BOARD_HEIGHT);
7464     if (!flipView && y >= 0) {
7465         y = BOARD_HEIGHT - 1 - y;
7466     }
7467     if (flipView && x >= 0) {
7468         x = BOARD_WIDTH - 1 - x;
7469     }
7470
7471     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7472         static int dummy;
7473         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7474         right = TRUE;
7475         return;
7476     }
7477
7478     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7479
7480     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7481
7482     if (clickType == Press) ErrorPopDown();
7483     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7484
7485     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7486         defaultPromoChoice = promoSweep;
7487         promoSweep = EmptySquare;   // terminate sweep
7488         promoDefaultAltered = TRUE;
7489         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7490     }
7491
7492     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7493         if(clickType == Release) return; // ignore upclick of click-click destination
7494         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7495         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7496         if(gameInfo.holdingsWidth &&
7497                 (WhiteOnMove(currentMove)
7498                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7499                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7500             // click in right holdings, for determining promotion piece
7501             ChessSquare p = boards[currentMove][y][x];
7502             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7503             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7504             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7505                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7506                 fromX = fromY = -1;
7507                 return;
7508             }
7509         }
7510         DrawPosition(FALSE, boards[currentMove]);
7511         return;
7512     }
7513
7514     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7515     if(clickType == Press
7516             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7517               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7518               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7519         return;
7520
7521     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7522         // could be static click on premove from-square: abort premove
7523         gotPremove = 0;
7524         ClearPremoveHighlights();
7525     }
7526
7527     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7528         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7529
7530     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7531         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7532                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7533         defaultPromoChoice = DefaultPromoChoice(side);
7534     }
7535
7536     autoQueen = appData.alwaysPromoteToQueen;
7537
7538     if (fromX == -1) {
7539       int originalY = y;
7540       gatingPiece = EmptySquare;
7541       if (clickType != Press) {
7542         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7543             DragPieceEnd(xPix, yPix); dragging = 0;
7544             DrawPosition(FALSE, NULL);
7545         }
7546         return;
7547       }
7548       doubleClick = FALSE;
7549       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7550         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7551       }
7552       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7553       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7554          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7555          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7556             /* First square */
7557             if (OKToStartUserMove(fromX, fromY)) {
7558                 second = 0;
7559                 ReportClick("lift", x, y);
7560                 MarkTargetSquares(0);
7561                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7562                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7563                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7564                     promoSweep = defaultPromoChoice;
7565                     selectFlag = 0; lastX = xPix; lastY = yPix;
7566                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7567                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7568                 }
7569                 if (appData.highlightDragging) {
7570                     SetHighlights(fromX, fromY, -1, -1);
7571                 } else {
7572                     ClearHighlights();
7573                 }
7574             } else fromX = fromY = -1;
7575             return;
7576         }
7577     }
7578
7579     /* fromX != -1 */
7580     if (clickType == Press && gameMode != EditPosition) {
7581         ChessSquare fromP;
7582         ChessSquare toP;
7583         int frc;
7584
7585         // ignore off-board to clicks
7586         if(y < 0 || x < 0) return;
7587
7588         /* Check if clicking again on the same color piece */
7589         fromP = boards[currentMove][fromY][fromX];
7590         toP = boards[currentMove][y][x];
7591         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7592         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7593             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7594            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7595              WhitePawn <= toP && toP <= WhiteKing &&
7596              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7597              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7598             (BlackPawn <= fromP && fromP <= BlackKing &&
7599              BlackPawn <= toP && toP <= BlackKing &&
7600              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7601              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7602             /* Clicked again on same color piece -- changed his mind */
7603             second = (x == fromX && y == fromY);
7604             killX = killY = -1;
7605             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7606                 second = FALSE; // first double-click rather than scond click
7607                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7608             }
7609             promoDefaultAltered = FALSE;
7610             MarkTargetSquares(1);
7611            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7612             if (appData.highlightDragging) {
7613                 SetHighlights(x, y, -1, -1);
7614             } else {
7615                 ClearHighlights();
7616             }
7617             if (OKToStartUserMove(x, y)) {
7618                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7619                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7620                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7621                  gatingPiece = boards[currentMove][fromY][fromX];
7622                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7623                 fromX = x;
7624                 fromY = y; dragging = 1;
7625                 if(!second) ReportClick("lift", x, y);
7626                 MarkTargetSquares(0);
7627                 DragPieceBegin(xPix, yPix, FALSE);
7628                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7629                     promoSweep = defaultPromoChoice;
7630                     selectFlag = 0; lastX = xPix; lastY = yPix;
7631                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7632                 }
7633             }
7634            }
7635            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7636            second = FALSE;
7637         }
7638         // ignore clicks on holdings
7639         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7640     }
7641
7642     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7643         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7644         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7645         return;
7646     }
7647
7648     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7649         DragPieceEnd(xPix, yPix); dragging = 0;
7650         if(clearFlag) {
7651             // a deferred attempt to click-click move an empty square on top of a piece
7652             boards[currentMove][y][x] = EmptySquare;
7653             ClearHighlights();
7654             DrawPosition(FALSE, boards[currentMove]);
7655             fromX = fromY = -1; clearFlag = 0;
7656             return;
7657         }
7658         if (appData.animateDragging) {
7659             /* Undo animation damage if any */
7660             DrawPosition(FALSE, NULL);
7661         }
7662         if (second) {
7663             /* Second up/down in same square; just abort move */
7664             second = 0;
7665             fromX = fromY = -1;
7666             gatingPiece = EmptySquare;
7667             MarkTargetSquares(1);
7668             ClearHighlights();
7669             gotPremove = 0;
7670             ClearPremoveHighlights();
7671         } else {
7672             /* First upclick in same square; start click-click mode */
7673             SetHighlights(x, y, -1, -1);
7674         }
7675         return;
7676     }
7677
7678     clearFlag = 0;
7679
7680     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7681        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7682         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7683         DisplayMessage(_("only marked squares are legal"),"");
7684         DrawPosition(TRUE, NULL);
7685         return; // ignore to-click
7686     }
7687
7688     /* we now have a different from- and (possibly off-board) to-square */
7689     /* Completed move */
7690     if(!sweepSelecting) {
7691         toX = x;
7692         toY = y;
7693     }
7694
7695     piece = boards[currentMove][fromY][fromX];
7696
7697     saveAnimate = appData.animate;
7698     if (clickType == Press) {
7699         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7700         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7701             // must be Edit Position mode with empty-square selected
7702             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7703             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7704             return;
7705         }
7706         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7707             return;
7708         }
7709         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7710             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7711         } else
7712         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7713         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7714           if(appData.sweepSelect) {
7715             promoSweep = defaultPromoChoice;
7716             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7717             selectFlag = 0; lastX = xPix; lastY = yPix;
7718             Sweep(0); // Pawn that is going to promote: preview promotion piece
7719             sweepSelecting = 1;
7720             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7721             MarkTargetSquares(1);
7722           }
7723           return; // promo popup appears on up-click
7724         }
7725         /* Finish clickclick move */
7726         if (appData.animate || appData.highlightLastMove) {
7727             SetHighlights(fromX, fromY, toX, toY);
7728         } else {
7729             ClearHighlights();
7730         }
7731     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7732         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7733         if (appData.animate || appData.highlightLastMove) {
7734             SetHighlights(fromX, fromY, toX, toY);
7735         } else {
7736             ClearHighlights();
7737         }
7738     } else {
7739 #if 0
7740 // [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
7741         /* Finish drag move */
7742         if (appData.highlightLastMove) {
7743             SetHighlights(fromX, fromY, toX, toY);
7744         } else {
7745             ClearHighlights();
7746         }
7747 #endif
7748         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7749         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7750           dragging *= 2;            // flag button-less dragging if we are dragging
7751           MarkTargetSquares(1);
7752           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7753           else {
7754             kill2X = killX; kill2Y = killY;
7755             killX = x; killY = y;     //remeber this square as intermediate
7756             ReportClick("put", x, y); // and inform engine
7757             ReportClick("lift", x, y);
7758             MarkTargetSquares(0);
7759             return;
7760           }
7761         }
7762         DragPieceEnd(xPix, yPix); dragging = 0;
7763         /* Don't animate move and drag both */
7764         appData.animate = FALSE;
7765     }
7766
7767     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7768     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7769         ChessSquare piece = boards[currentMove][fromY][fromX];
7770         if(gameMode == EditPosition && piece != EmptySquare &&
7771            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7772             int n;
7773
7774             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7775                 n = PieceToNumber(piece - (int)BlackPawn);
7776                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7777                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7778                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7779             } else
7780             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7781                 n = PieceToNumber(piece);
7782                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7783                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7784                 boards[currentMove][n][BOARD_WIDTH-2]++;
7785             }
7786             boards[currentMove][fromY][fromX] = EmptySquare;
7787         }
7788         ClearHighlights();
7789         fromX = fromY = -1;
7790         MarkTargetSquares(1);
7791         DrawPosition(TRUE, boards[currentMove]);
7792         return;
7793     }
7794
7795     // off-board moves should not be highlighted
7796     if(x < 0 || y < 0) ClearHighlights();
7797     else ReportClick("put", x, y);
7798
7799     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7800
7801     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7802
7803     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7804         SetHighlights(fromX, fromY, toX, toY);
7805         MarkTargetSquares(1);
7806         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7807             // [HGM] super: promotion to captured piece selected from holdings
7808             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7809             promotionChoice = TRUE;
7810             // kludge follows to temporarily execute move on display, without promoting yet
7811             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7812             boards[currentMove][toY][toX] = p;
7813             DrawPosition(FALSE, boards[currentMove]);
7814             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7815             boards[currentMove][toY][toX] = q;
7816             DisplayMessage("Click in holdings to choose piece", "");
7817             return;
7818         }
7819         PromotionPopUp(promoChoice);
7820     } else {
7821         int oldMove = currentMove;
7822         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7823         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7824         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7825         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7826            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7827             DrawPosition(TRUE, boards[currentMove]);
7828         MarkTargetSquares(1);
7829         fromX = fromY = -1;
7830     }
7831     appData.animate = saveAnimate;
7832     if (appData.animate || appData.animateDragging) {
7833         /* Undo animation damage if needed */
7834         DrawPosition(FALSE, NULL);
7835     }
7836 }
7837
7838 int
7839 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7840 {   // front-end-free part taken out of PieceMenuPopup
7841     int whichMenu; int xSqr, ySqr;
7842
7843     if(seekGraphUp) { // [HGM] seekgraph
7844         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7845         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7846         return -2;
7847     }
7848
7849     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7850          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7851         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7852         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7853         if(action == Press)   {
7854             originalFlip = flipView;
7855             flipView = !flipView; // temporarily flip board to see game from partners perspective
7856             DrawPosition(TRUE, partnerBoard);
7857             DisplayMessage(partnerStatus, "");
7858             partnerUp = TRUE;
7859         } else if(action == Release) {
7860             flipView = originalFlip;
7861             DrawPosition(TRUE, boards[currentMove]);
7862             partnerUp = FALSE;
7863         }
7864         return -2;
7865     }
7866
7867     xSqr = EventToSquare(x, BOARD_WIDTH);
7868     ySqr = EventToSquare(y, BOARD_HEIGHT);
7869     if (action == Release) {
7870         if(pieceSweep != EmptySquare) {
7871             EditPositionMenuEvent(pieceSweep, toX, toY);
7872             pieceSweep = EmptySquare;
7873         } else UnLoadPV(); // [HGM] pv
7874     }
7875     if (action != Press) return -2; // return code to be ignored
7876     switch (gameMode) {
7877       case IcsExamining:
7878         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7879       case EditPosition:
7880         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7881         if (xSqr < 0 || ySqr < 0) return -1;
7882         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7883         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7884         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7885         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7886         NextPiece(0);
7887         return 2; // grab
7888       case IcsObserving:
7889         if(!appData.icsEngineAnalyze) return -1;
7890       case IcsPlayingWhite:
7891       case IcsPlayingBlack:
7892         if(!appData.zippyPlay) goto noZip;
7893       case AnalyzeMode:
7894       case AnalyzeFile:
7895       case MachinePlaysWhite:
7896       case MachinePlaysBlack:
7897       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7898         if (!appData.dropMenu) {
7899           LoadPV(x, y);
7900           return 2; // flag front-end to grab mouse events
7901         }
7902         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7903            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7904       case EditGame:
7905       noZip:
7906         if (xSqr < 0 || ySqr < 0) return -1;
7907         if (!appData.dropMenu || appData.testLegality &&
7908             gameInfo.variant != VariantBughouse &&
7909             gameInfo.variant != VariantCrazyhouse) return -1;
7910         whichMenu = 1; // drop menu
7911         break;
7912       default:
7913         return -1;
7914     }
7915
7916     if (((*fromX = xSqr) < 0) ||
7917         ((*fromY = ySqr) < 0)) {
7918         *fromX = *fromY = -1;
7919         return -1;
7920     }
7921     if (flipView)
7922       *fromX = BOARD_WIDTH - 1 - *fromX;
7923     else
7924       *fromY = BOARD_HEIGHT - 1 - *fromY;
7925
7926     return whichMenu;
7927 }
7928
7929 void
7930 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7931 {
7932 //    char * hint = lastHint;
7933     FrontEndProgramStats stats;
7934
7935     stats.which = cps == &first ? 0 : 1;
7936     stats.depth = cpstats->depth;
7937     stats.nodes = cpstats->nodes;
7938     stats.score = cpstats->score;
7939     stats.time = cpstats->time;
7940     stats.pv = cpstats->movelist;
7941     stats.hint = lastHint;
7942     stats.an_move_index = 0;
7943     stats.an_move_count = 0;
7944
7945     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7946         stats.hint = cpstats->move_name;
7947         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7948         stats.an_move_count = cpstats->nr_moves;
7949     }
7950
7951     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
7952
7953     SetProgramStats( &stats );
7954 }
7955
7956 void
7957 ClearEngineOutputPane (int which)
7958 {
7959     static FrontEndProgramStats dummyStats;
7960     dummyStats.which = which;
7961     dummyStats.pv = "#";
7962     SetProgramStats( &dummyStats );
7963 }
7964
7965 #define MAXPLAYERS 500
7966
7967 char *
7968 TourneyStandings (int display)
7969 {
7970     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7971     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7972     char result, *p, *names[MAXPLAYERS];
7973
7974     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7975         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7976     names[0] = p = strdup(appData.participants);
7977     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7978
7979     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7980
7981     while(result = appData.results[nr]) {
7982         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7983         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7984         wScore = bScore = 0;
7985         switch(result) {
7986           case '+': wScore = 2; break;
7987           case '-': bScore = 2; break;
7988           case '=': wScore = bScore = 1; break;
7989           case ' ':
7990           case '*': return strdup("busy"); // tourney not finished
7991         }
7992         score[w] += wScore;
7993         score[b] += bScore;
7994         games[w]++;
7995         games[b]++;
7996         nr++;
7997     }
7998     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7999     for(w=0; w<nPlayers; w++) {
8000         bScore = -1;
8001         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8002         ranking[w] = b; points[w] = bScore; score[b] = -2;
8003     }
8004     p = malloc(nPlayers*34+1);
8005     for(w=0; w<nPlayers && w<display; w++)
8006         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8007     free(names[0]);
8008     return p;
8009 }
8010
8011 void
8012 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8013 {       // count all piece types
8014         int p, f, r;
8015         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8016         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8017         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8018                 p = board[r][f];
8019                 pCnt[p]++;
8020                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8021                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8022                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8023                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8024                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8025                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8026         }
8027 }
8028
8029 int
8030 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8031 {
8032         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8033         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8034
8035         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8036         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8037         if(myPawns == 2 && nMine == 3) // KPP
8038             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8039         if(myPawns == 1 && nMine == 2) // KP
8040             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8041         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8042             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8043         if(myPawns) return FALSE;
8044         if(pCnt[WhiteRook+side])
8045             return pCnt[BlackRook-side] ||
8046                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8047                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8048                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8049         if(pCnt[WhiteCannon+side]) {
8050             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8051             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8052         }
8053         if(pCnt[WhiteKnight+side])
8054             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8055         return FALSE;
8056 }
8057
8058 int
8059 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8060 {
8061         VariantClass v = gameInfo.variant;
8062
8063         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8064         if(v == VariantShatranj) return TRUE; // always winnable through baring
8065         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8066         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8067
8068         if(v == VariantXiangqi) {
8069                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8070
8071                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8072                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8073                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8074                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8075                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8076                 if(stale) // we have at least one last-rank P plus perhaps C
8077                     return majors // KPKX
8078                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8079                 else // KCA*E*
8080                     return pCnt[WhiteFerz+side] // KCAK
8081                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8082                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8083                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8084
8085         } else if(v == VariantKnightmate) {
8086                 if(nMine == 1) return FALSE;
8087                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8088         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8089                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8090
8091                 if(nMine == 1) return FALSE; // bare King
8092                 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
8093                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8094                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8095                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8096                 if(pCnt[WhiteKnight+side])
8097                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8098                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8099                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8100                 if(nBishops)
8101                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8102                 if(pCnt[WhiteAlfil+side])
8103                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8104                 if(pCnt[WhiteWazir+side])
8105                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8106         }
8107
8108         return TRUE;
8109 }
8110
8111 int
8112 CompareWithRights (Board b1, Board b2)
8113 {
8114     int rights = 0;
8115     if(!CompareBoards(b1, b2)) return FALSE;
8116     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8117     /* compare castling rights */
8118     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8119            rights++; /* King lost rights, while rook still had them */
8120     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8121         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8122            rights++; /* but at least one rook lost them */
8123     }
8124     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8125            rights++;
8126     if( b1[CASTLING][5] != NoRights ) {
8127         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8128            rights++;
8129     }
8130     return rights == 0;
8131 }
8132
8133 int
8134 Adjudicate (ChessProgramState *cps)
8135 {       // [HGM] some adjudications useful with buggy engines
8136         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8137         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8138         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8139         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8140         int k, drop, count = 0; static int bare = 1;
8141         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8142         Boolean canAdjudicate = !appData.icsActive;
8143
8144         // most tests only when we understand the game, i.e. legality-checking on
8145             if( appData.testLegality )
8146             {   /* [HGM] Some more adjudications for obstinate engines */
8147                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8148                 static int moveCount = 6;
8149                 ChessMove result;
8150                 char *reason = NULL;
8151
8152                 /* Count what is on board. */
8153                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8154
8155                 /* Some material-based adjudications that have to be made before stalemate test */
8156                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8157                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8158                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8159                      if(canAdjudicate && appData.checkMates) {
8160                          if(engineOpponent)
8161                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8162                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8163                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8164                          return 1;
8165                      }
8166                 }
8167
8168                 /* Bare King in Shatranj (loses) or Losers (wins) */
8169                 if( nrW == 1 || nrB == 1) {
8170                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8171                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8172                      if(canAdjudicate && appData.checkMates) {
8173                          if(engineOpponent)
8174                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8175                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8176                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8177                          return 1;
8178                      }
8179                   } else
8180                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8181                   {    /* bare King */
8182                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8183                         if(canAdjudicate && appData.checkMates) {
8184                             /* but only adjudicate if adjudication enabled */
8185                             if(engineOpponent)
8186                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8187                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8188                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8189                             return 1;
8190                         }
8191                   }
8192                 } else bare = 1;
8193
8194
8195             // don't wait for engine to announce game end if we can judge ourselves
8196             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8197               case MT_CHECK:
8198                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8199                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8200                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8201                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8202                             checkCnt++;
8203                         if(checkCnt >= 2) {
8204                             reason = "Xboard adjudication: 3rd check";
8205                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8206                             break;
8207                         }
8208                     }
8209                 }
8210               case MT_NONE:
8211               default:
8212                 break;
8213               case MT_STEALMATE:
8214               case MT_STALEMATE:
8215               case MT_STAINMATE:
8216                 reason = "Xboard adjudication: Stalemate";
8217                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8218                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8219                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8220                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8221                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8222                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8223                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8224                                                                         EP_CHECKMATE : EP_WINS);
8225                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8226                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8227                 }
8228                 break;
8229               case MT_CHECKMATE:
8230                 reason = "Xboard adjudication: Checkmate";
8231                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8232                 if(gameInfo.variant == VariantShogi) {
8233                     if(forwardMostMove > backwardMostMove
8234                        && moveList[forwardMostMove-1][1] == '@'
8235                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8236                         reason = "XBoard adjudication: pawn-drop mate";
8237                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8238                     }
8239                 }
8240                 break;
8241             }
8242
8243                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8244                     case EP_STALEMATE:
8245                         result = GameIsDrawn; break;
8246                     case EP_CHECKMATE:
8247                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8248                     case EP_WINS:
8249                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8250                     default:
8251                         result = EndOfFile;
8252                 }
8253                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8254                     if(engineOpponent)
8255                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8256                     GameEnds( result, reason, GE_XBOARD );
8257                     return 1;
8258                 }
8259
8260                 /* Next absolutely insufficient mating material. */
8261                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8262                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8263                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8264
8265                      /* always flag draws, for judging claims */
8266                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8267
8268                      if(canAdjudicate && appData.materialDraws) {
8269                          /* but only adjudicate them if adjudication enabled */
8270                          if(engineOpponent) {
8271                            SendToProgram("force\n", engineOpponent); // suppress reply
8272                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8273                          }
8274                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8275                          return 1;
8276                      }
8277                 }
8278
8279                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8280                 if(gameInfo.variant == VariantXiangqi ?
8281                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8282                  : nrW + nrB == 4 &&
8283                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8284                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8285                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8286                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8287                    ) ) {
8288                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8289                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8290                           if(engineOpponent) {
8291                             SendToProgram("force\n", engineOpponent); // suppress reply
8292                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8293                           }
8294                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8295                           return 1;
8296                      }
8297                 } else moveCount = 6;
8298             }
8299
8300         // Repetition draws and 50-move rule can be applied independently of legality testing
8301
8302                 /* Check for rep-draws */
8303                 count = 0;
8304                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8305                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8306                 for(k = forwardMostMove-2;
8307                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8308                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8309                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8310                     k-=2)
8311                 {   int rights=0;
8312                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8313                         /* compare castling rights */
8314                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8315                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8316                                 rights++; /* King lost rights, while rook still had them */
8317                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8318                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8319                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8320                                    rights++; /* but at least one rook lost them */
8321                         }
8322                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8323                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8324                                 rights++;
8325                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8326                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8327                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8328                                    rights++;
8329                         }
8330                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8331                             && appData.drawRepeats > 1) {
8332                              /* adjudicate after user-specified nr of repeats */
8333                              int result = GameIsDrawn;
8334                              char *details = "XBoard adjudication: repetition draw";
8335                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8336                                 // [HGM] xiangqi: check for forbidden perpetuals
8337                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8338                                 for(m=forwardMostMove; m>k; m-=2) {
8339                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8340                                         ourPerpetual = 0; // the current mover did not always check
8341                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8342                                         hisPerpetual = 0; // the opponent did not always check
8343                                 }
8344                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8345                                                                         ourPerpetual, hisPerpetual);
8346                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8347                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8348                                     details = "Xboard adjudication: perpetual checking";
8349                                 } else
8350                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8351                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8352                                 } else
8353                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8354                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8355                                         result = BlackWins;
8356                                         details = "Xboard adjudication: repetition";
8357                                     }
8358                                 } else // it must be XQ
8359                                 // Now check for perpetual chases
8360                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8361                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8362                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8363                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8364                                         static char resdet[MSG_SIZ];
8365                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8366                                         details = resdet;
8367                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8368                                     } else
8369                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8370                                         break; // Abort repetition-checking loop.
8371                                 }
8372                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8373                              }
8374                              if(engineOpponent) {
8375                                SendToProgram("force\n", engineOpponent); // suppress reply
8376                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8377                              }
8378                              GameEnds( result, details, GE_XBOARD );
8379                              return 1;
8380                         }
8381                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8382                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8383                     }
8384                 }
8385
8386                 /* Now we test for 50-move draws. Determine ply count */
8387                 count = forwardMostMove;
8388                 /* look for last irreversble move */
8389                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8390                     count--;
8391                 /* if we hit starting position, add initial plies */
8392                 if( count == backwardMostMove )
8393                     count -= initialRulePlies;
8394                 count = forwardMostMove - count;
8395                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8396                         // adjust reversible move counter for checks in Xiangqi
8397                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8398                         if(i < backwardMostMove) i = backwardMostMove;
8399                         while(i <= forwardMostMove) {
8400                                 lastCheck = inCheck; // check evasion does not count
8401                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8402                                 if(inCheck || lastCheck) count--; // check does not count
8403                                 i++;
8404                         }
8405                 }
8406                 if( count >= 100)
8407                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8408                          /* this is used to judge if draw claims are legal */
8409                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8410                          if(engineOpponent) {
8411                            SendToProgram("force\n", engineOpponent); // suppress reply
8412                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8413                          }
8414                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8415                          return 1;
8416                 }
8417
8418                 /* if draw offer is pending, treat it as a draw claim
8419                  * when draw condition present, to allow engines a way to
8420                  * claim draws before making their move to avoid a race
8421                  * condition occurring after their move
8422                  */
8423                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8424                          char *p = NULL;
8425                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8426                              p = "Draw claim: 50-move rule";
8427                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8428                              p = "Draw claim: 3-fold repetition";
8429                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8430                              p = "Draw claim: insufficient mating material";
8431                          if( p != NULL && canAdjudicate) {
8432                              if(engineOpponent) {
8433                                SendToProgram("force\n", engineOpponent); // suppress reply
8434                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8435                              }
8436                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8437                              return 1;
8438                          }
8439                 }
8440
8441                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8442                     if(engineOpponent) {
8443                       SendToProgram("force\n", engineOpponent); // suppress reply
8444                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8445                     }
8446                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8447                     return 1;
8448                 }
8449         return 0;
8450 }
8451
8452 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8453 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8454 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8455
8456 static int
8457 BitbaseProbe ()
8458 {
8459     int pieces[10], squares[10], cnt=0, r, f, res;
8460     static int loaded;
8461     static PPROBE_EGBB probeBB;
8462     if(!appData.testLegality) return 10;
8463     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8464     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8465     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8466     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8467         ChessSquare piece = boards[forwardMostMove][r][f];
8468         int black = (piece >= BlackPawn);
8469         int type = piece - black*BlackPawn;
8470         if(piece == EmptySquare) continue;
8471         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8472         if(type == WhiteKing) type = WhiteQueen + 1;
8473         type = egbbCode[type];
8474         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8475         pieces[cnt] = type + black*6;
8476         if(++cnt > 5) return 11;
8477     }
8478     pieces[cnt] = squares[cnt] = 0;
8479     // probe EGBB
8480     if(loaded == 2) return 13; // loading failed before
8481     if(loaded == 0) {
8482         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8483         HMODULE lib;
8484         PLOAD_EGBB loadBB;
8485         loaded = 2; // prepare for failure
8486         if(!path) return 13; // no egbb installed
8487         strncpy(buf, path + 8, MSG_SIZ);
8488         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8489         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8490         lib = LoadLibrary(buf);
8491         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8492         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8493         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8494         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8495         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8496         loaded = 1; // success!
8497     }
8498     res = probeBB(forwardMostMove & 1, pieces, squares);
8499     return res > 0 ? 1 : res < 0 ? -1 : 0;
8500 }
8501
8502 char *
8503 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8504 {   // [HGM] book: this routine intercepts moves to simulate book replies
8505     char *bookHit = NULL;
8506
8507     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8508         char buf[MSG_SIZ];
8509         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8510         SendToProgram(buf, cps);
8511     }
8512     //first determine if the incoming move brings opponent into his book
8513     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8514         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8515     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8516     if(bookHit != NULL && !cps->bookSuspend) {
8517         // make sure opponent is not going to reply after receiving move to book position
8518         SendToProgram("force\n", cps);
8519         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8520     }
8521     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8522     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8523     // now arrange restart after book miss
8524     if(bookHit) {
8525         // after a book hit we never send 'go', and the code after the call to this routine
8526         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8527         char buf[MSG_SIZ], *move = bookHit;
8528         if(cps->useSAN) {
8529             int fromX, fromY, toX, toY;
8530             char promoChar;
8531             ChessMove moveType;
8532             move = buf + 30;
8533             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8534                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8535                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8536                                     PosFlags(forwardMostMove),
8537                                     fromY, fromX, toY, toX, promoChar, move);
8538             } else {
8539                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8540                 bookHit = NULL;
8541             }
8542         }
8543         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8544         SendToProgram(buf, cps);
8545         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8546     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8547         SendToProgram("go\n", cps);
8548         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8549     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8550         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8551             SendToProgram("go\n", cps);
8552         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8553     }
8554     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8555 }
8556
8557 int
8558 LoadError (char *errmess, ChessProgramState *cps)
8559 {   // unloads engine and switches back to -ncp mode if it was first
8560     if(cps->initDone) return FALSE;
8561     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8562     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8563     cps->pr = NoProc;
8564     if(cps == &first) {
8565         appData.noChessProgram = TRUE;
8566         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8567         gameMode = BeginningOfGame; ModeHighlight();
8568         SetNCPMode();
8569     }
8570     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8571     DisplayMessage("", ""); // erase waiting message
8572     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8573     return TRUE;
8574 }
8575
8576 char *savedMessage;
8577 ChessProgramState *savedState;
8578 void
8579 DeferredBookMove (void)
8580 {
8581         if(savedState->lastPing != savedState->lastPong)
8582                     ScheduleDelayedEvent(DeferredBookMove, 10);
8583         else
8584         HandleMachineMove(savedMessage, savedState);
8585 }
8586
8587 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8588 static ChessProgramState *stalledEngine;
8589 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8590
8591 void
8592 HandleMachineMove (char *message, ChessProgramState *cps)
8593 {
8594     static char firstLeg[20];
8595     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8596     char realname[MSG_SIZ];
8597     int fromX, fromY, toX, toY;
8598     ChessMove moveType;
8599     char promoChar, roar;
8600     char *p, *pv=buf1;
8601     int machineWhite, oldError;
8602     char *bookHit;
8603
8604     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8605         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8606         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8607             DisplayError(_("Invalid pairing from pairing engine"), 0);
8608             return;
8609         }
8610         pairingReceived = 1;
8611         NextMatchGame();
8612         return; // Skim the pairing messages here.
8613     }
8614
8615     oldError = cps->userError; cps->userError = 0;
8616
8617 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8618     /*
8619      * Kludge to ignore BEL characters
8620      */
8621     while (*message == '\007') message++;
8622
8623     /*
8624      * [HGM] engine debug message: ignore lines starting with '#' character
8625      */
8626     if(cps->debug && *message == '#') return;
8627
8628     /*
8629      * Look for book output
8630      */
8631     if (cps == &first && bookRequested) {
8632         if (message[0] == '\t' || message[0] == ' ') {
8633             /* Part of the book output is here; append it */
8634             strcat(bookOutput, message);
8635             strcat(bookOutput, "  \n");
8636             return;
8637         } else if (bookOutput[0] != NULLCHAR) {
8638             /* All of book output has arrived; display it */
8639             char *p = bookOutput;
8640             while (*p != NULLCHAR) {
8641                 if (*p == '\t') *p = ' ';
8642                 p++;
8643             }
8644             DisplayInformation(bookOutput);
8645             bookRequested = FALSE;
8646             /* Fall through to parse the current output */
8647         }
8648     }
8649
8650     /*
8651      * Look for machine move.
8652      */
8653     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8654         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8655     {
8656         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8657             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8658             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8659             stalledEngine = cps;
8660             if(appData.ponderNextMove) { // bring opponent out of ponder
8661                 if(gameMode == TwoMachinesPlay) {
8662                     if(cps->other->pause)
8663                         PauseEngine(cps->other);
8664                     else
8665                         SendToProgram("easy\n", cps->other);
8666                 }
8667             }
8668             StopClocks();
8669             return;
8670         }
8671
8672       if(cps->usePing) {
8673
8674         /* This method is only useful on engines that support ping */
8675         if(abortEngineThink) {
8676             if (appData.debugMode) {
8677                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8678             }
8679             SendToProgram("undo\n", cps);
8680             return;
8681         }
8682
8683         if (cps->lastPing != cps->lastPong) {
8684             /* Extra move from before last new; ignore */
8685             if (appData.debugMode) {
8686                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8687             }
8688           return;
8689         }
8690
8691       } else {
8692
8693         switch (gameMode) {
8694           case BeginningOfGame:
8695             /* Extra move from before last reset; ignore */
8696             if (appData.debugMode) {
8697                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8698             }
8699             return;
8700
8701           case EndOfGame:
8702           case IcsIdle:
8703           default:
8704             /* Extra move after we tried to stop.  The mode test is
8705                not a reliable way of detecting this problem, but it's
8706                the best we can do on engines that don't support ping.
8707             */
8708             if (appData.debugMode) {
8709                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8710                         cps->which, gameMode);
8711             }
8712             SendToProgram("undo\n", cps);
8713             return;
8714
8715           case MachinePlaysWhite:
8716           case IcsPlayingWhite:
8717             machineWhite = TRUE;
8718             break;
8719
8720           case MachinePlaysBlack:
8721           case IcsPlayingBlack:
8722             machineWhite = FALSE;
8723             break;
8724
8725           case TwoMachinesPlay:
8726             machineWhite = (cps->twoMachinesColor[0] == 'w');
8727             break;
8728         }
8729         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8730             if (appData.debugMode) {
8731                 fprintf(debugFP,
8732                         "Ignoring move out of turn by %s, gameMode %d"
8733                         ", forwardMost %d\n",
8734                         cps->which, gameMode, forwardMostMove);
8735             }
8736             return;
8737         }
8738       }
8739
8740         if(cps->alphaRank) AlphaRank(machineMove, 4);
8741
8742         // [HGM] lion: (some very limited) support for Alien protocol
8743         killX = killY = kill2X = kill2Y = -1;
8744         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8745             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8746             return;
8747         }
8748         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8749             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8750             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8751         }
8752         if(firstLeg[0]) { // there was a previous leg;
8753             // only support case where same piece makes two step
8754             char buf[20], *p = machineMove+1, *q = buf+1, f;
8755             safeStrCpy(buf, machineMove, 20);
8756             while(isdigit(*q)) q++; // find start of to-square
8757             safeStrCpy(machineMove, firstLeg, 20);
8758             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8759             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8760             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8761             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8762             firstLeg[0] = NULLCHAR;
8763         }
8764
8765         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8766                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8767             /* Machine move could not be parsed; ignore it. */
8768           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8769                     machineMove, _(cps->which));
8770             DisplayMoveError(buf1);
8771             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8772                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8773             if (gameMode == TwoMachinesPlay) {
8774               GameEnds(machineWhite ? BlackWins : WhiteWins,
8775                        buf1, GE_XBOARD);
8776             }
8777             return;
8778         }
8779
8780         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8781         /* So we have to redo legality test with true e.p. status here,  */
8782         /* to make sure an illegal e.p. capture does not slip through,   */
8783         /* to cause a forfeit on a justified illegal-move complaint      */
8784         /* of the opponent.                                              */
8785         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8786            ChessMove moveType;
8787            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8788                              fromY, fromX, toY, toX, promoChar);
8789             if(moveType == IllegalMove) {
8790               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8791                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8792                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8793                            buf1, GE_XBOARD);
8794                 return;
8795            } else if(!appData.fischerCastling)
8796            /* [HGM] Kludge to handle engines that send FRC-style castling
8797               when they shouldn't (like TSCP-Gothic) */
8798            switch(moveType) {
8799              case WhiteASideCastleFR:
8800              case BlackASideCastleFR:
8801                toX+=2;
8802                currentMoveString[2]++;
8803                break;
8804              case WhiteHSideCastleFR:
8805              case BlackHSideCastleFR:
8806                toX--;
8807                currentMoveString[2]--;
8808                break;
8809              default: ; // nothing to do, but suppresses warning of pedantic compilers
8810            }
8811         }
8812         hintRequested = FALSE;
8813         lastHint[0] = NULLCHAR;
8814         bookRequested = FALSE;
8815         /* Program may be pondering now */
8816         cps->maybeThinking = TRUE;
8817         if (cps->sendTime == 2) cps->sendTime = 1;
8818         if (cps->offeredDraw) cps->offeredDraw--;
8819
8820         /* [AS] Save move info*/
8821         pvInfoList[ forwardMostMove ].score = programStats.score;
8822         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8823         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8824
8825         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8826
8827         /* Test suites abort the 'game' after one move */
8828         if(*appData.finger) {
8829            static FILE *f;
8830            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8831            if(!f) f = fopen(appData.finger, "w");
8832            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8833            else { DisplayFatalError("Bad output file", errno, 0); return; }
8834            free(fen);
8835            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8836         }
8837         if(appData.epd) {
8838            if(solvingTime >= 0) {
8839               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8840               totalTime += solvingTime; first.matchWins++;
8841            } else {
8842               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8843               second.matchWins++;
8844            }
8845            OutputKibitz(2, buf1);
8846            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8847         }
8848
8849         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8850         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8851             int count = 0;
8852
8853             while( count < adjudicateLossPlies ) {
8854                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8855
8856                 if( count & 1 ) {
8857                     score = -score; /* Flip score for winning side */
8858                 }
8859
8860                 if( score > appData.adjudicateLossThreshold ) {
8861                     break;
8862                 }
8863
8864                 count++;
8865             }
8866
8867             if( count >= adjudicateLossPlies ) {
8868                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8869
8870                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8871                     "Xboard adjudication",
8872                     GE_XBOARD );
8873
8874                 return;
8875             }
8876         }
8877
8878         if(Adjudicate(cps)) {
8879             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8880             return; // [HGM] adjudicate: for all automatic game ends
8881         }
8882
8883 #if ZIPPY
8884         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8885             first.initDone) {
8886           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8887                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8888                 SendToICS("draw ");
8889                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8890           }
8891           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8892           ics_user_moved = 1;
8893           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8894                 char buf[3*MSG_SIZ];
8895
8896                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8897                         programStats.score / 100.,
8898                         programStats.depth,
8899                         programStats.time / 100.,
8900                         (unsigned int)programStats.nodes,
8901                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8902                         programStats.movelist);
8903                 SendToICS(buf);
8904           }
8905         }
8906 #endif
8907
8908         /* [AS] Clear stats for next move */
8909         ClearProgramStats();
8910         thinkOutput[0] = NULLCHAR;
8911         hiddenThinkOutputState = 0;
8912
8913         bookHit = NULL;
8914         if (gameMode == TwoMachinesPlay) {
8915             /* [HGM] relaying draw offers moved to after reception of move */
8916             /* and interpreting offer as claim if it brings draw condition */
8917             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8918                 SendToProgram("draw\n", cps->other);
8919             }
8920             if (cps->other->sendTime) {
8921                 SendTimeRemaining(cps->other,
8922                                   cps->other->twoMachinesColor[0] == 'w');
8923             }
8924             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8925             if (firstMove && !bookHit) {
8926                 firstMove = FALSE;
8927                 if (cps->other->useColors) {
8928                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8929                 }
8930                 SendToProgram("go\n", cps->other);
8931             }
8932             cps->other->maybeThinking = TRUE;
8933         }
8934
8935         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8936
8937         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8938
8939         if (!pausing && appData.ringBellAfterMoves) {
8940             if(!roar) RingBell();
8941         }
8942
8943         /*
8944          * Reenable menu items that were disabled while
8945          * machine was thinking
8946          */
8947         if (gameMode != TwoMachinesPlay)
8948             SetUserThinkingEnables();
8949
8950         // [HGM] book: after book hit opponent has received move and is now in force mode
8951         // force the book reply into it, and then fake that it outputted this move by jumping
8952         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8953         if(bookHit) {
8954                 static char bookMove[MSG_SIZ]; // a bit generous?
8955
8956                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8957                 strcat(bookMove, bookHit);
8958                 message = bookMove;
8959                 cps = cps->other;
8960                 programStats.nodes = programStats.depth = programStats.time =
8961                 programStats.score = programStats.got_only_move = 0;
8962                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8963
8964                 if(cps->lastPing != cps->lastPong) {
8965                     savedMessage = message; // args for deferred call
8966                     savedState = cps;
8967                     ScheduleDelayedEvent(DeferredBookMove, 10);
8968                     return;
8969                 }
8970                 goto FakeBookMove;
8971         }
8972
8973         return;
8974     }
8975
8976     /* Set special modes for chess engines.  Later something general
8977      *  could be added here; for now there is just one kludge feature,
8978      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8979      *  when "xboard" is given as an interactive command.
8980      */
8981     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8982         cps->useSigint = FALSE;
8983         cps->useSigterm = FALSE;
8984     }
8985     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8986       ParseFeatures(message+8, cps);
8987       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8988     }
8989
8990     if (!strncmp(message, "setup ", 6) && 
8991         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8992           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8993                                         ) { // [HGM] allow first engine to define opening position
8994       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8995       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8996       *buf = NULLCHAR;
8997       if(sscanf(message, "setup (%s", buf) == 1) {
8998         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8999         ASSIGN(appData.pieceToCharTable, buf);
9000       }
9001       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9002       if(dummy >= 3) {
9003         while(message[s] && message[s++] != ' ');
9004         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9005            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9006             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9007             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9008           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9009           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9010           startedFromSetupPosition = FALSE;
9011         }
9012       }
9013       if(startedFromSetupPosition) return;
9014       ParseFEN(boards[0], &dummy, message+s, FALSE);
9015       DrawPosition(TRUE, boards[0]);
9016       CopyBoard(initialPosition, boards[0]);
9017       startedFromSetupPosition = TRUE;
9018       return;
9019     }
9020     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9021       ChessSquare piece = WhitePawn;
9022       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9023       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
9024       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9025       piece += CharToPiece(ID & 255) - WhitePawn;
9026       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9027       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9028       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9029       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9030       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9031       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9032                                                && gameInfo.variant != VariantGreat
9033                                                && gameInfo.variant != VariantFairy    ) return;
9034       if(piece < EmptySquare) {
9035         pieceDefs = TRUE;
9036         ASSIGN(pieceDesc[piece], buf1);
9037         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9038       }
9039       return;
9040     }
9041     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9042      * want this, I was asked to put it in, and obliged.
9043      */
9044     if (!strncmp(message, "setboard ", 9)) {
9045         Board initial_position;
9046
9047         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9048
9049         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9050             DisplayError(_("Bad FEN received from engine"), 0);
9051             return ;
9052         } else {
9053            Reset(TRUE, FALSE);
9054            CopyBoard(boards[0], initial_position);
9055            initialRulePlies = FENrulePlies;
9056            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9057            else gameMode = MachinePlaysBlack;
9058            DrawPosition(FALSE, boards[currentMove]);
9059         }
9060         return;
9061     }
9062
9063     /*
9064      * Look for communication commands
9065      */
9066     if (!strncmp(message, "telluser ", 9)) {
9067         if(message[9] == '\\' && message[10] == '\\')
9068             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9069         PlayTellSound();
9070         DisplayNote(message + 9);
9071         return;
9072     }
9073     if (!strncmp(message, "tellusererror ", 14)) {
9074         cps->userError = 1;
9075         if(message[14] == '\\' && message[15] == '\\')
9076             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9077         PlayTellSound();
9078         DisplayError(message + 14, 0);
9079         return;
9080     }
9081     if (!strncmp(message, "tellopponent ", 13)) {
9082       if (appData.icsActive) {
9083         if (loggedOn) {
9084           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9085           SendToICS(buf1);
9086         }
9087       } else {
9088         DisplayNote(message + 13);
9089       }
9090       return;
9091     }
9092     if (!strncmp(message, "tellothers ", 11)) {
9093       if (appData.icsActive) {
9094         if (loggedOn) {
9095           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9096           SendToICS(buf1);
9097         }
9098       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9099       return;
9100     }
9101     if (!strncmp(message, "tellall ", 8)) {
9102       if (appData.icsActive) {
9103         if (loggedOn) {
9104           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9105           SendToICS(buf1);
9106         }
9107       } else {
9108         DisplayNote(message + 8);
9109       }
9110       return;
9111     }
9112     if (strncmp(message, "warning", 7) == 0) {
9113         /* Undocumented feature, use tellusererror in new code */
9114         DisplayError(message, 0);
9115         return;
9116     }
9117     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9118         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9119         strcat(realname, " query");
9120         AskQuestion(realname, buf2, buf1, cps->pr);
9121         return;
9122     }
9123     /* Commands from the engine directly to ICS.  We don't allow these to be
9124      *  sent until we are logged on. Crafty kibitzes have been known to
9125      *  interfere with the login process.
9126      */
9127     if (loggedOn) {
9128         if (!strncmp(message, "tellics ", 8)) {
9129             SendToICS(message + 8);
9130             SendToICS("\n");
9131             return;
9132         }
9133         if (!strncmp(message, "tellicsnoalias ", 15)) {
9134             SendToICS(ics_prefix);
9135             SendToICS(message + 15);
9136             SendToICS("\n");
9137             return;
9138         }
9139         /* The following are for backward compatibility only */
9140         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9141             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9142             SendToICS(ics_prefix);
9143             SendToICS(message);
9144             SendToICS("\n");
9145             return;
9146         }
9147     }
9148     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9149         if(initPing == cps->lastPong) {
9150             if(gameInfo.variant == VariantUnknown) {
9151                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9152                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9153                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9154             }
9155             initPing = -1;
9156         }
9157         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9158             abortEngineThink = FALSE;
9159             DisplayMessage("", "");
9160             ThawUI();
9161         }
9162         return;
9163     }
9164     if(!strncmp(message, "highlight ", 10)) {
9165         if(appData.testLegality && !*engineVariant && appData.markers) return;
9166         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9167         return;
9168     }
9169     if(!strncmp(message, "click ", 6)) {
9170         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9171         if(appData.testLegality || !appData.oneClick) return;
9172         sscanf(message+6, "%c%d%c", &f, &y, &c);
9173         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9174         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9175         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9176         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9177         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9178         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9179             LeftClick(Release, lastLeftX, lastLeftY);
9180         controlKey  = (c == ',');
9181         LeftClick(Press, x, y);
9182         LeftClick(Release, x, y);
9183         first.highlight = f;
9184         return;
9185     }
9186     /*
9187      * If the move is illegal, cancel it and redraw the board.
9188      * Also deal with other error cases.  Matching is rather loose
9189      * here to accommodate engines written before the spec.
9190      */
9191     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9192         strncmp(message, "Error", 5) == 0) {
9193         if (StrStr(message, "name") ||
9194             StrStr(message, "rating") || StrStr(message, "?") ||
9195             StrStr(message, "result") || StrStr(message, "board") ||
9196             StrStr(message, "bk") || StrStr(message, "computer") ||
9197             StrStr(message, "variant") || StrStr(message, "hint") ||
9198             StrStr(message, "random") || StrStr(message, "depth") ||
9199             StrStr(message, "accepted")) {
9200             return;
9201         }
9202         if (StrStr(message, "protover")) {
9203           /* Program is responding to input, so it's apparently done
9204              initializing, and this error message indicates it is
9205              protocol version 1.  So we don't need to wait any longer
9206              for it to initialize and send feature commands. */
9207           FeatureDone(cps, 1);
9208           cps->protocolVersion = 1;
9209           return;
9210         }
9211         cps->maybeThinking = FALSE;
9212
9213         if (StrStr(message, "draw")) {
9214             /* Program doesn't have "draw" command */
9215             cps->sendDrawOffers = 0;
9216             return;
9217         }
9218         if (cps->sendTime != 1 &&
9219             (StrStr(message, "time") || StrStr(message, "otim"))) {
9220           /* Program apparently doesn't have "time" or "otim" command */
9221           cps->sendTime = 0;
9222           return;
9223         }
9224         if (StrStr(message, "analyze")) {
9225             cps->analysisSupport = FALSE;
9226             cps->analyzing = FALSE;
9227 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9228             EditGameEvent(); // [HGM] try to preserve loaded game
9229             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9230             DisplayError(buf2, 0);
9231             return;
9232         }
9233         if (StrStr(message, "(no matching move)st")) {
9234           /* Special kludge for GNU Chess 4 only */
9235           cps->stKludge = TRUE;
9236           SendTimeControl(cps, movesPerSession, timeControl,
9237                           timeIncrement, appData.searchDepth,
9238                           searchTime);
9239           return;
9240         }
9241         if (StrStr(message, "(no matching move)sd")) {
9242           /* Special kludge for GNU Chess 4 only */
9243           cps->sdKludge = TRUE;
9244           SendTimeControl(cps, movesPerSession, timeControl,
9245                           timeIncrement, appData.searchDepth,
9246                           searchTime);
9247           return;
9248         }
9249         if (!StrStr(message, "llegal")) {
9250             return;
9251         }
9252         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9253             gameMode == IcsIdle) return;
9254         if (forwardMostMove <= backwardMostMove) return;
9255         if (pausing) PauseEvent();
9256       if(appData.forceIllegal) {
9257             // [HGM] illegal: machine refused move; force position after move into it
9258           SendToProgram("force\n", cps);
9259           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9260                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9261                 // when black is to move, while there might be nothing on a2 or black
9262                 // might already have the move. So send the board as if white has the move.
9263                 // But first we must change the stm of the engine, as it refused the last move
9264                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9265                 if(WhiteOnMove(forwardMostMove)) {
9266                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9267                     SendBoard(cps, forwardMostMove); // kludgeless board
9268                 } else {
9269                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9270                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9271                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9272                 }
9273           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9274             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9275                  gameMode == TwoMachinesPlay)
9276               SendToProgram("go\n", cps);
9277             return;
9278       } else
9279         if (gameMode == PlayFromGameFile) {
9280             /* Stop reading this game file */
9281             gameMode = EditGame;
9282             ModeHighlight();
9283         }
9284         /* [HGM] illegal-move claim should forfeit game when Xboard */
9285         /* only passes fully legal moves                            */
9286         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9287             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9288                                 "False illegal-move claim", GE_XBOARD );
9289             return; // do not take back move we tested as valid
9290         }
9291         currentMove = forwardMostMove-1;
9292         DisplayMove(currentMove-1); /* before DisplayMoveError */
9293         SwitchClocks(forwardMostMove-1); // [HGM] race
9294         DisplayBothClocks();
9295         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9296                 parseList[currentMove], _(cps->which));
9297         DisplayMoveError(buf1);
9298         DrawPosition(FALSE, boards[currentMove]);
9299
9300         SetUserThinkingEnables();
9301         return;
9302     }
9303     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9304         /* Program has a broken "time" command that
9305            outputs a string not ending in newline.
9306            Don't use it. */
9307         cps->sendTime = 0;
9308     }
9309     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9310         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9311             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9312     }
9313
9314     /*
9315      * If chess program startup fails, exit with an error message.
9316      * Attempts to recover here are futile. [HGM] Well, we try anyway
9317      */
9318     if ((StrStr(message, "unknown host") != NULL)
9319         || (StrStr(message, "No remote directory") != NULL)
9320         || (StrStr(message, "not found") != NULL)
9321         || (StrStr(message, "No such file") != NULL)
9322         || (StrStr(message, "can't alloc") != NULL)
9323         || (StrStr(message, "Permission denied") != NULL)) {
9324
9325         cps->maybeThinking = FALSE;
9326         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9327                 _(cps->which), cps->program, cps->host, message);
9328         RemoveInputSource(cps->isr);
9329         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9330             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9331             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9332         }
9333         return;
9334     }
9335
9336     /*
9337      * Look for hint output
9338      */
9339     if (sscanf(message, "Hint: %s", buf1) == 1) {
9340         if (cps == &first && hintRequested) {
9341             hintRequested = FALSE;
9342             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9343                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9344                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9345                                     PosFlags(forwardMostMove),
9346                                     fromY, fromX, toY, toX, promoChar, buf1);
9347                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9348                 DisplayInformation(buf2);
9349             } else {
9350                 /* Hint move could not be parsed!? */
9351               snprintf(buf2, sizeof(buf2),
9352                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9353                         buf1, _(cps->which));
9354                 DisplayError(buf2, 0);
9355             }
9356         } else {
9357           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9358         }
9359         return;
9360     }
9361
9362     /*
9363      * Ignore other messages if game is not in progress
9364      */
9365     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9366         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9367
9368     /*
9369      * look for win, lose, draw, or draw offer
9370      */
9371     if (strncmp(message, "1-0", 3) == 0) {
9372         char *p, *q, *r = "";
9373         p = strchr(message, '{');
9374         if (p) {
9375             q = strchr(p, '}');
9376             if (q) {
9377                 *q = NULLCHAR;
9378                 r = p + 1;
9379             }
9380         }
9381         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9382         return;
9383     } else if (strncmp(message, "0-1", 3) == 0) {
9384         char *p, *q, *r = "";
9385         p = strchr(message, '{');
9386         if (p) {
9387             q = strchr(p, '}');
9388             if (q) {
9389                 *q = NULLCHAR;
9390                 r = p + 1;
9391             }
9392         }
9393         /* Kludge for Arasan 4.1 bug */
9394         if (strcmp(r, "Black resigns") == 0) {
9395             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9396             return;
9397         }
9398         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9399         return;
9400     } else if (strncmp(message, "1/2", 3) == 0) {
9401         char *p, *q, *r = "";
9402         p = strchr(message, '{');
9403         if (p) {
9404             q = strchr(p, '}');
9405             if (q) {
9406                 *q = NULLCHAR;
9407                 r = p + 1;
9408             }
9409         }
9410
9411         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9412         return;
9413
9414     } else if (strncmp(message, "White resign", 12) == 0) {
9415         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9416         return;
9417     } else if (strncmp(message, "Black resign", 12) == 0) {
9418         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9419         return;
9420     } else if (strncmp(message, "White matches", 13) == 0 ||
9421                strncmp(message, "Black matches", 13) == 0   ) {
9422         /* [HGM] ignore GNUShogi noises */
9423         return;
9424     } else if (strncmp(message, "White", 5) == 0 &&
9425                message[5] != '(' &&
9426                StrStr(message, "Black") == NULL) {
9427         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9428         return;
9429     } else if (strncmp(message, "Black", 5) == 0 &&
9430                message[5] != '(') {
9431         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9432         return;
9433     } else if (strcmp(message, "resign") == 0 ||
9434                strcmp(message, "computer resigns") == 0) {
9435         switch (gameMode) {
9436           case MachinePlaysBlack:
9437           case IcsPlayingBlack:
9438             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9439             break;
9440           case MachinePlaysWhite:
9441           case IcsPlayingWhite:
9442             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9443             break;
9444           case TwoMachinesPlay:
9445             if (cps->twoMachinesColor[0] == 'w')
9446               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9447             else
9448               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9449             break;
9450           default:
9451             /* can't happen */
9452             break;
9453         }
9454         return;
9455     } else if (strncmp(message, "opponent mates", 14) == 0) {
9456         switch (gameMode) {
9457           case MachinePlaysBlack:
9458           case IcsPlayingBlack:
9459             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9460             break;
9461           case MachinePlaysWhite:
9462           case IcsPlayingWhite:
9463             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9464             break;
9465           case TwoMachinesPlay:
9466             if (cps->twoMachinesColor[0] == 'w')
9467               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9468             else
9469               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9470             break;
9471           default:
9472             /* can't happen */
9473             break;
9474         }
9475         return;
9476     } else if (strncmp(message, "computer mates", 14) == 0) {
9477         switch (gameMode) {
9478           case MachinePlaysBlack:
9479           case IcsPlayingBlack:
9480             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9481             break;
9482           case MachinePlaysWhite:
9483           case IcsPlayingWhite:
9484             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9485             break;
9486           case TwoMachinesPlay:
9487             if (cps->twoMachinesColor[0] == 'w')
9488               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9489             else
9490               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9491             break;
9492           default:
9493             /* can't happen */
9494             break;
9495         }
9496         return;
9497     } else if (strncmp(message, "checkmate", 9) == 0) {
9498         if (WhiteOnMove(forwardMostMove)) {
9499             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9500         } else {
9501             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9502         }
9503         return;
9504     } else if (strstr(message, "Draw") != NULL ||
9505                strstr(message, "game is a draw") != NULL) {
9506         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9507         return;
9508     } else if (strstr(message, "offer") != NULL &&
9509                strstr(message, "draw") != NULL) {
9510 #if ZIPPY
9511         if (appData.zippyPlay && first.initDone) {
9512             /* Relay offer to ICS */
9513             SendToICS(ics_prefix);
9514             SendToICS("draw\n");
9515         }
9516 #endif
9517         cps->offeredDraw = 2; /* valid until this engine moves twice */
9518         if (gameMode == TwoMachinesPlay) {
9519             if (cps->other->offeredDraw) {
9520                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9521             /* [HGM] in two-machine mode we delay relaying draw offer      */
9522             /* until after we also have move, to see if it is really claim */
9523             }
9524         } else if (gameMode == MachinePlaysWhite ||
9525                    gameMode == MachinePlaysBlack) {
9526           if (userOfferedDraw) {
9527             DisplayInformation(_("Machine accepts your draw offer"));
9528             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9529           } else {
9530             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9531           }
9532         }
9533     }
9534
9535
9536     /*
9537      * Look for thinking output
9538      */
9539     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9540           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9541                                 ) {
9542         int plylev, mvleft, mvtot, curscore, time;
9543         char mvname[MOVE_LEN];
9544         u64 nodes; // [DM]
9545         char plyext;
9546         int ignore = FALSE;
9547         int prefixHint = FALSE;
9548         mvname[0] = NULLCHAR;
9549
9550         switch (gameMode) {
9551           case MachinePlaysBlack:
9552           case IcsPlayingBlack:
9553             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9554             break;
9555           case MachinePlaysWhite:
9556           case IcsPlayingWhite:
9557             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9558             break;
9559           case AnalyzeMode:
9560           case AnalyzeFile:
9561             break;
9562           case IcsObserving: /* [DM] icsEngineAnalyze */
9563             if (!appData.icsEngineAnalyze) ignore = TRUE;
9564             break;
9565           case TwoMachinesPlay:
9566             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9567                 ignore = TRUE;
9568             }
9569             break;
9570           default:
9571             ignore = TRUE;
9572             break;
9573         }
9574
9575         if (!ignore) {
9576             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9577             buf1[0] = NULLCHAR;
9578             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9579                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9580                 char score_buf[MSG_SIZ];
9581
9582                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9583                     nodes += u64Const(0x100000000);
9584
9585                 if (plyext != ' ' && plyext != '\t') {
9586                     time *= 100;
9587                 }
9588
9589                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9590                 if( cps->scoreIsAbsolute &&
9591                     ( gameMode == MachinePlaysBlack ||
9592                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9593                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9594                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9595                      !WhiteOnMove(currentMove)
9596                     ) )
9597                 {
9598                     curscore = -curscore;
9599                 }
9600
9601                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9602
9603                 if(*bestMove) { // rememer time best EPD move was first found
9604                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9605                     ChessMove mt;
9606                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9607                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9608                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9609                 }
9610
9611                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9612                         char buf[MSG_SIZ];
9613                         FILE *f;
9614                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9615                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9616                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9617                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9618                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9619                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9620                                 fclose(f);
9621                         }
9622                         else
9623                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9624                           DisplayError(_("failed writing PV"), 0);
9625                 }
9626
9627                 tempStats.depth = plylev;
9628                 tempStats.nodes = nodes;
9629                 tempStats.time = time;
9630                 tempStats.score = curscore;
9631                 tempStats.got_only_move = 0;
9632
9633                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9634                         int ticklen;
9635
9636                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9637                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9638                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9639                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9640                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9641                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9642                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9643                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9644                 }
9645
9646                 /* Buffer overflow protection */
9647                 if (pv[0] != NULLCHAR) {
9648                     if (strlen(pv) >= sizeof(tempStats.movelist)
9649                         && appData.debugMode) {
9650                         fprintf(debugFP,
9651                                 "PV is too long; using the first %u bytes.\n",
9652                                 (unsigned) sizeof(tempStats.movelist) - 1);
9653                     }
9654
9655                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9656                 } else {
9657                     sprintf(tempStats.movelist, " no PV\n");
9658                 }
9659
9660                 if (tempStats.seen_stat) {
9661                     tempStats.ok_to_send = 1;
9662                 }
9663
9664                 if (strchr(tempStats.movelist, '(') != NULL) {
9665                     tempStats.line_is_book = 1;
9666                     tempStats.nr_moves = 0;
9667                     tempStats.moves_left = 0;
9668                 } else {
9669                     tempStats.line_is_book = 0;
9670                 }
9671
9672                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9673                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9674
9675                 SendProgramStatsToFrontend( cps, &tempStats );
9676
9677                 /*
9678                     [AS] Protect the thinkOutput buffer from overflow... this
9679                     is only useful if buf1 hasn't overflowed first!
9680                 */
9681                 if(curscore >= MATE_SCORE) 
9682                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9683                 else if(curscore <= -MATE_SCORE) 
9684                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9685                 else
9686                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9687                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9688                          plylev,
9689                          (gameMode == TwoMachinesPlay ?
9690                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9691                          score_buf,
9692                          prefixHint ? lastHint : "",
9693                          prefixHint ? " " : "" );
9694
9695                 if( buf1[0] != NULLCHAR ) {
9696                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9697
9698                     if( strlen(pv) > max_len ) {
9699                         if( appData.debugMode) {
9700                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9701                         }
9702                         pv[max_len+1] = '\0';
9703                     }
9704
9705                     strcat( thinkOutput, pv);
9706                 }
9707
9708                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9709                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9710                     DisplayMove(currentMove - 1);
9711                 }
9712                 return;
9713
9714             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9715                 /* crafty (9.25+) says "(only move) <move>"
9716                  * if there is only 1 legal move
9717                  */
9718                 sscanf(p, "(only move) %s", buf1);
9719                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9720                 sprintf(programStats.movelist, "%s (only move)", buf1);
9721                 programStats.depth = 1;
9722                 programStats.nr_moves = 1;
9723                 programStats.moves_left = 1;
9724                 programStats.nodes = 1;
9725                 programStats.time = 1;
9726                 programStats.got_only_move = 1;
9727
9728                 /* Not really, but we also use this member to
9729                    mean "line isn't going to change" (Crafty
9730                    isn't searching, so stats won't change) */
9731                 programStats.line_is_book = 1;
9732
9733                 SendProgramStatsToFrontend( cps, &programStats );
9734
9735                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9736                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9737                     DisplayMove(currentMove - 1);
9738                 }
9739                 return;
9740             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9741                               &time, &nodes, &plylev, &mvleft,
9742                               &mvtot, mvname) >= 5) {
9743                 /* The stat01: line is from Crafty (9.29+) in response
9744                    to the "." command */
9745                 programStats.seen_stat = 1;
9746                 cps->maybeThinking = TRUE;
9747
9748                 if (programStats.got_only_move || !appData.periodicUpdates)
9749                   return;
9750
9751                 programStats.depth = plylev;
9752                 programStats.time = time;
9753                 programStats.nodes = nodes;
9754                 programStats.moves_left = mvleft;
9755                 programStats.nr_moves = mvtot;
9756                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9757                 programStats.ok_to_send = 1;
9758                 programStats.movelist[0] = '\0';
9759
9760                 SendProgramStatsToFrontend( cps, &programStats );
9761
9762                 return;
9763
9764             } else if (strncmp(message,"++",2) == 0) {
9765                 /* Crafty 9.29+ outputs this */
9766                 programStats.got_fail = 2;
9767                 return;
9768
9769             } else if (strncmp(message,"--",2) == 0) {
9770                 /* Crafty 9.29+ outputs this */
9771                 programStats.got_fail = 1;
9772                 return;
9773
9774             } else if (thinkOutput[0] != NULLCHAR &&
9775                        strncmp(message, "    ", 4) == 0) {
9776                 unsigned message_len;
9777
9778                 p = message;
9779                 while (*p && *p == ' ') p++;
9780
9781                 message_len = strlen( p );
9782
9783                 /* [AS] Avoid buffer overflow */
9784                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9785                     strcat(thinkOutput, " ");
9786                     strcat(thinkOutput, p);
9787                 }
9788
9789                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9790                     strcat(programStats.movelist, " ");
9791                     strcat(programStats.movelist, p);
9792                 }
9793
9794                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9795                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9796                     DisplayMove(currentMove - 1);
9797                 }
9798                 return;
9799             }
9800         }
9801         else {
9802             buf1[0] = NULLCHAR;
9803
9804             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9805                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9806             {
9807                 ChessProgramStats cpstats;
9808
9809                 if (plyext != ' ' && plyext != '\t') {
9810                     time *= 100;
9811                 }
9812
9813                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9814                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9815                     curscore = -curscore;
9816                 }
9817
9818                 cpstats.depth = plylev;
9819                 cpstats.nodes = nodes;
9820                 cpstats.time = time;
9821                 cpstats.score = curscore;
9822                 cpstats.got_only_move = 0;
9823                 cpstats.movelist[0] = '\0';
9824
9825                 if (buf1[0] != NULLCHAR) {
9826                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9827                 }
9828
9829                 cpstats.ok_to_send = 0;
9830                 cpstats.line_is_book = 0;
9831                 cpstats.nr_moves = 0;
9832                 cpstats.moves_left = 0;
9833
9834                 SendProgramStatsToFrontend( cps, &cpstats );
9835             }
9836         }
9837     }
9838 }
9839
9840
9841 /* Parse a game score from the character string "game", and
9842    record it as the history of the current game.  The game
9843    score is NOT assumed to start from the standard position.
9844    The display is not updated in any way.
9845    */
9846 void
9847 ParseGameHistory (char *game)
9848 {
9849     ChessMove moveType;
9850     int fromX, fromY, toX, toY, boardIndex;
9851     char promoChar;
9852     char *p, *q;
9853     char buf[MSG_SIZ];
9854
9855     if (appData.debugMode)
9856       fprintf(debugFP, "Parsing game history: %s\n", game);
9857
9858     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9859     gameInfo.site = StrSave(appData.icsHost);
9860     gameInfo.date = PGNDate();
9861     gameInfo.round = StrSave("-");
9862
9863     /* Parse out names of players */
9864     while (*game == ' ') game++;
9865     p = buf;
9866     while (*game != ' ') *p++ = *game++;
9867     *p = NULLCHAR;
9868     gameInfo.white = StrSave(buf);
9869     while (*game == ' ') game++;
9870     p = buf;
9871     while (*game != ' ' && *game != '\n') *p++ = *game++;
9872     *p = NULLCHAR;
9873     gameInfo.black = StrSave(buf);
9874
9875     /* Parse moves */
9876     boardIndex = blackPlaysFirst ? 1 : 0;
9877     yynewstr(game);
9878     for (;;) {
9879         yyboardindex = boardIndex;
9880         moveType = (ChessMove) Myylex();
9881         switch (moveType) {
9882           case IllegalMove:             /* maybe suicide chess, etc. */
9883   if (appData.debugMode) {
9884     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9885     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9886     setbuf(debugFP, NULL);
9887   }
9888           case WhitePromotion:
9889           case BlackPromotion:
9890           case WhiteNonPromotion:
9891           case BlackNonPromotion:
9892           case NormalMove:
9893           case FirstLeg:
9894           case WhiteCapturesEnPassant:
9895           case BlackCapturesEnPassant:
9896           case WhiteKingSideCastle:
9897           case WhiteQueenSideCastle:
9898           case BlackKingSideCastle:
9899           case BlackQueenSideCastle:
9900           case WhiteKingSideCastleWild:
9901           case WhiteQueenSideCastleWild:
9902           case BlackKingSideCastleWild:
9903           case BlackQueenSideCastleWild:
9904           /* PUSH Fabien */
9905           case WhiteHSideCastleFR:
9906           case WhiteASideCastleFR:
9907           case BlackHSideCastleFR:
9908           case BlackASideCastleFR:
9909           /* POP Fabien */
9910             fromX = currentMoveString[0] - AAA;
9911             fromY = currentMoveString[1] - ONE;
9912             toX = currentMoveString[2] - AAA;
9913             toY = currentMoveString[3] - ONE;
9914             promoChar = currentMoveString[4];
9915             break;
9916           case WhiteDrop:
9917           case BlackDrop:
9918             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9919             fromX = moveType == WhiteDrop ?
9920               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9921             (int) CharToPiece(ToLower(currentMoveString[0]));
9922             fromY = DROP_RANK;
9923             toX = currentMoveString[2] - AAA;
9924             toY = currentMoveString[3] - ONE;
9925             promoChar = NULLCHAR;
9926             break;
9927           case AmbiguousMove:
9928             /* bug? */
9929             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9930   if (appData.debugMode) {
9931     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9932     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9933     setbuf(debugFP, NULL);
9934   }
9935             DisplayError(buf, 0);
9936             return;
9937           case ImpossibleMove:
9938             /* bug? */
9939             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9940   if (appData.debugMode) {
9941     fprintf(debugFP, "Impossible 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             DisplayError(buf, 0);
9946             return;
9947           case EndOfFile:
9948             if (boardIndex < backwardMostMove) {
9949                 /* Oops, gap.  How did that happen? */
9950                 DisplayError(_("Gap in move list"), 0);
9951                 return;
9952             }
9953             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9954             if (boardIndex > forwardMostMove) {
9955                 forwardMostMove = boardIndex;
9956             }
9957             return;
9958           case ElapsedTime:
9959             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9960                 strcat(parseList[boardIndex-1], " ");
9961                 strcat(parseList[boardIndex-1], yy_text);
9962             }
9963             continue;
9964           case Comment:
9965           case PGNTag:
9966           case NAG:
9967           default:
9968             /* ignore */
9969             continue;
9970           case WhiteWins:
9971           case BlackWins:
9972           case GameIsDrawn:
9973           case GameUnfinished:
9974             if (gameMode == IcsExamining) {
9975                 if (boardIndex < backwardMostMove) {
9976                     /* Oops, gap.  How did that happen? */
9977                     return;
9978                 }
9979                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9980                 return;
9981             }
9982             gameInfo.result = moveType;
9983             p = strchr(yy_text, '{');
9984             if (p == NULL) p = strchr(yy_text, '(');
9985             if (p == NULL) {
9986                 p = yy_text;
9987                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9988             } else {
9989                 q = strchr(p, *p == '{' ? '}' : ')');
9990                 if (q != NULL) *q = NULLCHAR;
9991                 p++;
9992             }
9993             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9994             gameInfo.resultDetails = StrSave(p);
9995             continue;
9996         }
9997         if (boardIndex >= forwardMostMove &&
9998             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9999             backwardMostMove = blackPlaysFirst ? 1 : 0;
10000             return;
10001         }
10002         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10003                                  fromY, fromX, toY, toX, promoChar,
10004                                  parseList[boardIndex]);
10005         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10006         /* currentMoveString is set as a side-effect of yylex */
10007         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10008         strcat(moveList[boardIndex], "\n");
10009         boardIndex++;
10010         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10011         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10012           case MT_NONE:
10013           case MT_STALEMATE:
10014           default:
10015             break;
10016           case MT_CHECK:
10017             if(!IS_SHOGI(gameInfo.variant))
10018                 strcat(parseList[boardIndex - 1], "+");
10019             break;
10020           case MT_CHECKMATE:
10021           case MT_STAINMATE:
10022             strcat(parseList[boardIndex - 1], "#");
10023             break;
10024         }
10025     }
10026 }
10027
10028
10029 /* Apply a move to the given board  */
10030 void
10031 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10032 {
10033   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10034   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10035
10036     /* [HGM] compute & store e.p. status and castling rights for new position */
10037     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10038
10039       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10040       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10041       board[EP_STATUS] = EP_NONE;
10042       board[EP_FILE] = board[EP_RANK] = 100;
10043
10044   if (fromY == DROP_RANK) {
10045         /* must be first */
10046         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10047             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10048             return;
10049         }
10050         piece = board[toY][toX] = (ChessSquare) fromX;
10051   } else {
10052 //      ChessSquare victim;
10053       int i;
10054
10055       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10056 //           victim = board[killY][killX],
10057            killed = board[killY][killX],
10058            board[killY][killX] = EmptySquare,
10059            board[EP_STATUS] = EP_CAPTURE;
10060            if( kill2X >= 0 && kill2Y >= 0)
10061              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10062       }
10063
10064       if( board[toY][toX] != EmptySquare ) {
10065            board[EP_STATUS] = EP_CAPTURE;
10066            if( (fromX != toX || fromY != toY) && // not igui!
10067                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10068                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10069                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10070            }
10071       }
10072
10073       pawn = board[fromY][fromX];
10074       if( pawn == WhiteLance || pawn == BlackLance ) {
10075            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10076                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10077                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10078            }
10079       }
10080       if( pawn == WhitePawn ) {
10081            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10082                board[EP_STATUS] = EP_PAWN_MOVE;
10083            if( toY-fromY>=2) {
10084                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10085                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10086                         gameInfo.variant != VariantBerolina || toX < fromX)
10087                       board[EP_STATUS] = toX | berolina;
10088                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10089                         gameInfo.variant != VariantBerolina || toX > fromX)
10090                       board[EP_STATUS] = toX;
10091            }
10092       } else
10093       if( pawn == BlackPawn ) {
10094            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10095                board[EP_STATUS] = EP_PAWN_MOVE;
10096            if( toY-fromY<= -2) {
10097                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10098                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10099                         gameInfo.variant != VariantBerolina || toX < fromX)
10100                       board[EP_STATUS] = toX | berolina;
10101                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10102                         gameInfo.variant != VariantBerolina || toX > fromX)
10103                       board[EP_STATUS] = toX;
10104            }
10105        }
10106
10107        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10108        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10109        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10110        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10111
10112        for(i=0; i<nrCastlingRights; i++) {
10113            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10114               board[CASTLING][i] == toX   && castlingRank[i] == toY
10115              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10116        }
10117
10118        if(gameInfo.variant == VariantSChess) { // update virginity
10119            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10120            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10121            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10122            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10123        }
10124
10125      if (fromX == toX && fromY == toY) return;
10126
10127      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10128      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10129      if(gameInfo.variant == VariantKnightmate)
10130          king += (int) WhiteUnicorn - (int) WhiteKing;
10131
10132     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10133        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10134         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10135         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10136         board[EP_STATUS] = EP_NONE; // capture was fake!
10137     } else
10138     /* Code added by Tord: */
10139     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10140     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10141         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10142       board[EP_STATUS] = EP_NONE; // capture was fake!
10143       board[fromY][fromX] = EmptySquare;
10144       board[toY][toX] = EmptySquare;
10145       if((toX > fromX) != (piece == WhiteRook)) {
10146         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10147       } else {
10148         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10149       }
10150     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10151                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10152       board[EP_STATUS] = EP_NONE;
10153       board[fromY][fromX] = EmptySquare;
10154       board[toY][toX] = EmptySquare;
10155       if((toX > fromX) != (piece == BlackRook)) {
10156         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10157       } else {
10158         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10159       }
10160     /* End of code added by Tord */
10161
10162     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10163         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10164         board[toY][toX] = piece;
10165     } else if (board[fromY][fromX] == king
10166         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10167         && toY == fromY && toX > fromX+1) {
10168         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10169         board[fromY][toX-1] = board[fromY][rookX];
10170         board[fromY][rookX] = EmptySquare;
10171         board[fromY][fromX] = EmptySquare;
10172         board[toY][toX] = king;
10173     } else if (board[fromY][fromX] == king
10174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10175                && toY == fromY && toX < fromX-1) {
10176         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10177         board[fromY][toX+1] = board[fromY][rookX];
10178         board[fromY][rookX] = EmptySquare;
10179         board[fromY][fromX] = EmptySquare;
10180         board[toY][toX] = king;
10181     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10182                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10183                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10184                ) {
10185         /* white pawn promotion */
10186         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10187         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10188             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10189         board[fromY][fromX] = EmptySquare;
10190     } else if ((fromY >= BOARD_HEIGHT>>1)
10191                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10192                && (toX != fromX)
10193                && gameInfo.variant != VariantXiangqi
10194                && gameInfo.variant != VariantBerolina
10195                && (pawn == WhitePawn)
10196                && (board[toY][toX] == EmptySquare)) {
10197         board[fromY][fromX] = EmptySquare;
10198         board[toY][toX] = piece;
10199         if(toY == epRank - 128 + 1)
10200             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10201         else
10202             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10203     } else if ((fromY == BOARD_HEIGHT-4)
10204                && (toX == fromX)
10205                && gameInfo.variant == VariantBerolina
10206                && (board[fromY][fromX] == WhitePawn)
10207                && (board[toY][toX] == EmptySquare)) {
10208         board[fromY][fromX] = EmptySquare;
10209         board[toY][toX] = WhitePawn;
10210         if(oldEP & EP_BEROLIN_A) {
10211                 captured = board[fromY][fromX-1];
10212                 board[fromY][fromX-1] = EmptySquare;
10213         }else{  captured = board[fromY][fromX+1];
10214                 board[fromY][fromX+1] = EmptySquare;
10215         }
10216     } else if (board[fromY][fromX] == king
10217         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10218                && toY == fromY && toX > fromX+1) {
10219         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10220         board[fromY][toX-1] = board[fromY][rookX];
10221         board[fromY][rookX] = EmptySquare;
10222         board[fromY][fromX] = EmptySquare;
10223         board[toY][toX] = king;
10224     } else if (board[fromY][fromX] == king
10225         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10226                && toY == fromY && toX < fromX-1) {
10227         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10228         board[fromY][toX+1] = board[fromY][rookX];
10229         board[fromY][rookX] = EmptySquare;
10230         board[fromY][fromX] = EmptySquare;
10231         board[toY][toX] = king;
10232     } else if (fromY == 7 && fromX == 3
10233                && board[fromY][fromX] == BlackKing
10234                && toY == 7 && toX == 5) {
10235         board[fromY][fromX] = EmptySquare;
10236         board[toY][toX] = BlackKing;
10237         board[fromY][7] = EmptySquare;
10238         board[toY][4] = BlackRook;
10239     } else if (fromY == 7 && fromX == 3
10240                && board[fromY][fromX] == BlackKing
10241                && toY == 7 && toX == 1) {
10242         board[fromY][fromX] = EmptySquare;
10243         board[toY][toX] = BlackKing;
10244         board[fromY][0] = EmptySquare;
10245         board[toY][2] = BlackRook;
10246     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10247                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10248                && toY < promoRank && promoChar
10249                ) {
10250         /* black pawn promotion */
10251         board[toY][toX] = CharToPiece(ToLower(promoChar));
10252         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10253             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10254         board[fromY][fromX] = EmptySquare;
10255     } else if ((fromY < BOARD_HEIGHT>>1)
10256                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10257                && (toX != fromX)
10258                && gameInfo.variant != VariantXiangqi
10259                && gameInfo.variant != VariantBerolina
10260                && (pawn == BlackPawn)
10261                && (board[toY][toX] == EmptySquare)) {
10262         board[fromY][fromX] = EmptySquare;
10263         board[toY][toX] = piece;
10264         if(toY == epRank - 128 - 1)
10265             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10266         else
10267             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10268     } else if ((fromY == 3)
10269                && (toX == fromX)
10270                && gameInfo.variant == VariantBerolina
10271                && (board[fromY][fromX] == BlackPawn)
10272                && (board[toY][toX] == EmptySquare)) {
10273         board[fromY][fromX] = EmptySquare;
10274         board[toY][toX] = BlackPawn;
10275         if(oldEP & EP_BEROLIN_A) {
10276                 captured = board[fromY][fromX-1];
10277                 board[fromY][fromX-1] = EmptySquare;
10278         }else{  captured = board[fromY][fromX+1];
10279                 board[fromY][fromX+1] = EmptySquare;
10280         }
10281     } else {
10282         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10283         board[fromY][fromX] = EmptySquare;
10284         board[toY][toX] = piece;
10285     }
10286   }
10287
10288     if (gameInfo.holdingsWidth != 0) {
10289
10290       /* !!A lot more code needs to be written to support holdings  */
10291       /* [HGM] OK, so I have written it. Holdings are stored in the */
10292       /* penultimate board files, so they are automaticlly stored   */
10293       /* in the game history.                                       */
10294       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10295                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10296         /* Delete from holdings, by decreasing count */
10297         /* and erasing image if necessary            */
10298         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10299         if(p < (int) BlackPawn) { /* white drop */
10300              p -= (int)WhitePawn;
10301                  p = PieceToNumber((ChessSquare)p);
10302              if(p >= gameInfo.holdingsSize) p = 0;
10303              if(--board[p][BOARD_WIDTH-2] <= 0)
10304                   board[p][BOARD_WIDTH-1] = EmptySquare;
10305              if((int)board[p][BOARD_WIDTH-2] < 0)
10306                         board[p][BOARD_WIDTH-2] = 0;
10307         } else {                  /* black drop */
10308              p -= (int)BlackPawn;
10309                  p = PieceToNumber((ChessSquare)p);
10310              if(p >= gameInfo.holdingsSize) p = 0;
10311              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10312                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10313              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10314                         board[BOARD_HEIGHT-1-p][1] = 0;
10315         }
10316       }
10317       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10318           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10319         /* [HGM] holdings: Add to holdings, if holdings exist */
10320         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10321                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10322                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10323         }
10324         p = (int) captured;
10325         if (p >= (int) BlackPawn) {
10326           p -= (int)BlackPawn;
10327           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10328                   /* Restore shogi-promoted piece to its original  first */
10329                   captured = (ChessSquare) (DEMOTED captured);
10330                   p = DEMOTED p;
10331           }
10332           p = PieceToNumber((ChessSquare)p);
10333           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10334           board[p][BOARD_WIDTH-2]++;
10335           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10336         } else {
10337           p -= (int)WhitePawn;
10338           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10339                   captured = (ChessSquare) (DEMOTED captured);
10340                   p = DEMOTED p;
10341           }
10342           p = PieceToNumber((ChessSquare)p);
10343           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10344           board[BOARD_HEIGHT-1-p][1]++;
10345           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10346         }
10347       }
10348     } else if (gameInfo.variant == VariantAtomic) {
10349       if (captured != EmptySquare) {
10350         int y, x;
10351         for (y = toY-1; y <= toY+1; y++) {
10352           for (x = toX-1; x <= toX+1; x++) {
10353             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10354                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10355               board[y][x] = EmptySquare;
10356             }
10357           }
10358         }
10359         board[toY][toX] = EmptySquare;
10360       }
10361     }
10362
10363     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10364         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10365     } else
10366     if(promoChar == '+') {
10367         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10368         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10369         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10370           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10371     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10372         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10373         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10374            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10375         board[toY][toX] = newPiece;
10376     }
10377     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10378                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10379         // [HGM] superchess: take promotion piece out of holdings
10380         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10381         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10382             if(!--board[k][BOARD_WIDTH-2])
10383                 board[k][BOARD_WIDTH-1] = EmptySquare;
10384         } else {
10385             if(!--board[BOARD_HEIGHT-1-k][1])
10386                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10387         }
10388     }
10389 }
10390
10391 /* Updates forwardMostMove */
10392 void
10393 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10394 {
10395     int x = toX, y = toY;
10396     char *s = parseList[forwardMostMove];
10397     ChessSquare p = boards[forwardMostMove][toY][toX];
10398 //    forwardMostMove++; // [HGM] bare: moved downstream
10399
10400     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10401     (void) CoordsToAlgebraic(boards[forwardMostMove],
10402                              PosFlags(forwardMostMove),
10403                              fromY, fromX, y, x, promoChar,
10404                              s);
10405     if(killX >= 0 && killY >= 0)
10406         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10407
10408     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10409         int timeLeft; static int lastLoadFlag=0; int king, piece;
10410         piece = boards[forwardMostMove][fromY][fromX];
10411         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10412         if(gameInfo.variant == VariantKnightmate)
10413             king += (int) WhiteUnicorn - (int) WhiteKing;
10414         if(forwardMostMove == 0) {
10415             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10416                 fprintf(serverMoves, "%s;", UserName());
10417             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10418                 fprintf(serverMoves, "%s;", second.tidy);
10419             fprintf(serverMoves, "%s;", first.tidy);
10420             if(gameMode == MachinePlaysWhite)
10421                 fprintf(serverMoves, "%s;", UserName());
10422             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10423                 fprintf(serverMoves, "%s;", second.tidy);
10424         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10425         lastLoadFlag = loadFlag;
10426         // print base move
10427         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10428         // print castling suffix
10429         if( toY == fromY && piece == king ) {
10430             if(toX-fromX > 1)
10431                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10432             if(fromX-toX >1)
10433                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10434         }
10435         // e.p. suffix
10436         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10437              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10438              boards[forwardMostMove][toY][toX] == EmptySquare
10439              && fromX != toX && fromY != toY)
10440                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10441         // promotion suffix
10442         if(promoChar != NULLCHAR) {
10443             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10444                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10445                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10446             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10447         }
10448         if(!loadFlag) {
10449                 char buf[MOVE_LEN*2], *p; int len;
10450             fprintf(serverMoves, "/%d/%d",
10451                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10452             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10453             else                      timeLeft = blackTimeRemaining/1000;
10454             fprintf(serverMoves, "/%d", timeLeft);
10455                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10456                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10457                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10458                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10459             fprintf(serverMoves, "/%s", buf);
10460         }
10461         fflush(serverMoves);
10462     }
10463
10464     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10465         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10466       return;
10467     }
10468     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10469     if (commentList[forwardMostMove+1] != NULL) {
10470         free(commentList[forwardMostMove+1]);
10471         commentList[forwardMostMove+1] = NULL;
10472     }
10473     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10474     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10475     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10476     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10477     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10478     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10479     adjustedClock = FALSE;
10480     gameInfo.result = GameUnfinished;
10481     if (gameInfo.resultDetails != NULL) {
10482         free(gameInfo.resultDetails);
10483         gameInfo.resultDetails = NULL;
10484     }
10485     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10486                               moveList[forwardMostMove - 1]);
10487     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10488       case MT_NONE:
10489       case MT_STALEMATE:
10490       default:
10491         break;
10492       case MT_CHECK:
10493         if(!IS_SHOGI(gameInfo.variant))
10494             strcat(parseList[forwardMostMove - 1], "+");
10495         break;
10496       case MT_CHECKMATE:
10497       case MT_STAINMATE:
10498         strcat(parseList[forwardMostMove - 1], "#");
10499         break;
10500     }
10501 }
10502
10503 /* Updates currentMove if not pausing */
10504 void
10505 ShowMove (int fromX, int fromY, int toX, int toY)
10506 {
10507     int instant = (gameMode == PlayFromGameFile) ?
10508         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10509     if(appData.noGUI) return;
10510     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10511         if (!instant) {
10512             if (forwardMostMove == currentMove + 1) {
10513                 AnimateMove(boards[forwardMostMove - 1],
10514                             fromX, fromY, toX, toY);
10515             }
10516         }
10517         currentMove = forwardMostMove;
10518     }
10519
10520     killX = killY = -1; // [HGM] lion: used up
10521
10522     if (instant) return;
10523
10524     DisplayMove(currentMove - 1);
10525     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10526             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10527                 SetHighlights(fromX, fromY, toX, toY);
10528             }
10529     }
10530     DrawPosition(FALSE, boards[currentMove]);
10531     DisplayBothClocks();
10532     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10533 }
10534
10535 void
10536 SendEgtPath (ChessProgramState *cps)
10537 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10538         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10539
10540         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10541
10542         while(*p) {
10543             char c, *q = name+1, *r, *s;
10544
10545             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10546             while(*p && *p != ',') *q++ = *p++;
10547             *q++ = ':'; *q = 0;
10548             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10549                 strcmp(name, ",nalimov:") == 0 ) {
10550                 // take nalimov path from the menu-changeable option first, if it is defined
10551               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10552                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10553             } else
10554             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10555                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10556                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10557                 s = r = StrStr(s, ":") + 1; // beginning of path info
10558                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10559                 c = *r; *r = 0;             // temporarily null-terminate path info
10560                     *--q = 0;               // strip of trailig ':' from name
10561                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10562                 *r = c;
10563                 SendToProgram(buf,cps);     // send egtbpath command for this format
10564             }
10565             if(*p == ',') p++; // read away comma to position for next format name
10566         }
10567 }
10568
10569 static int
10570 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10571 {
10572       int width = 8, height = 8, holdings = 0;             // most common sizes
10573       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10574       // correct the deviations default for each variant
10575       if( v == VariantXiangqi ) width = 9,  height = 10;
10576       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10577       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10578       if( v == VariantCapablanca || v == VariantCapaRandom ||
10579           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10580                                 width = 10;
10581       if( v == VariantCourier ) width = 12;
10582       if( v == VariantSuper )                            holdings = 8;
10583       if( v == VariantGreat )   width = 10,              holdings = 8;
10584       if( v == VariantSChess )                           holdings = 7;
10585       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10586       if( v == VariantChuChess) width = 10, height = 10;
10587       if( v == VariantChu )     width = 12, height = 12;
10588       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10589              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10590              holdingsSize >= 0 && holdingsSize != holdings;
10591 }
10592
10593 char variantError[MSG_SIZ];
10594
10595 char *
10596 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10597 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10598       char *p, *variant = VariantName(v);
10599       static char b[MSG_SIZ];
10600       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10601            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10602                                                holdingsSize, variant); // cook up sized variant name
10603            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10604            if(StrStr(list, b) == NULL) {
10605                // specific sized variant not known, check if general sizing allowed
10606                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10607                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10608                             boardWidth, boardHeight, holdingsSize, engine);
10609                    return NULL;
10610                }
10611                /* [HGM] here we really should compare with the maximum supported board size */
10612            }
10613       } else snprintf(b, MSG_SIZ,"%s", variant);
10614       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10615       p = StrStr(list, b);
10616       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10617       if(p == NULL) {
10618           // occurs not at all in list, or only as sub-string
10619           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10620           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10621               int l = strlen(variantError);
10622               char *q;
10623               while(p != list && p[-1] != ',') p--;
10624               q = strchr(p, ',');
10625               if(q) *q = NULLCHAR;
10626               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10627               if(q) *q= ',';
10628           }
10629           return NULL;
10630       }
10631       return b;
10632 }
10633
10634 void
10635 InitChessProgram (ChessProgramState *cps, int setup)
10636 /* setup needed to setup FRC opening position */
10637 {
10638     char buf[MSG_SIZ], *b;
10639     if (appData.noChessProgram) return;
10640     hintRequested = FALSE;
10641     bookRequested = FALSE;
10642
10643     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10644     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10645     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10646     if(cps->memSize) { /* [HGM] memory */
10647       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10648         SendToProgram(buf, cps);
10649     }
10650     SendEgtPath(cps); /* [HGM] EGT */
10651     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10652       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10653         SendToProgram(buf, cps);
10654     }
10655
10656     setboardSpoiledMachineBlack = FALSE;
10657     SendToProgram(cps->initString, cps);
10658     if (gameInfo.variant != VariantNormal &&
10659         gameInfo.variant != VariantLoadable
10660         /* [HGM] also send variant if board size non-standard */
10661         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10662
10663       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10664                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10665       if (b == NULL) {
10666         VariantClass v;
10667         char c, *q = cps->variants, *p = strchr(q, ',');
10668         if(p) *p = NULLCHAR;
10669         v = StringToVariant(q);
10670         DisplayError(variantError, 0);
10671         if(v != VariantUnknown && cps == &first) {
10672             int w, h, s;
10673             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10674                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10675             ASSIGN(appData.variant, q);
10676             Reset(TRUE, FALSE);
10677         }
10678         if(p) *p = ',';
10679         return;
10680       }
10681
10682       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10683       SendToProgram(buf, cps);
10684     }
10685     currentlyInitializedVariant = gameInfo.variant;
10686
10687     /* [HGM] send opening position in FRC to first engine */
10688     if(setup) {
10689           SendToProgram("force\n", cps);
10690           SendBoard(cps, 0);
10691           /* engine is now in force mode! Set flag to wake it up after first move. */
10692           setboardSpoiledMachineBlack = 1;
10693     }
10694
10695     if (cps->sendICS) {
10696       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10697       SendToProgram(buf, cps);
10698     }
10699     cps->maybeThinking = FALSE;
10700     cps->offeredDraw = 0;
10701     if (!appData.icsActive) {
10702         SendTimeControl(cps, movesPerSession, timeControl,
10703                         timeIncrement, appData.searchDepth,
10704                         searchTime);
10705     }
10706     if (appData.showThinking
10707         // [HGM] thinking: four options require thinking output to be sent
10708         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10709                                 ) {
10710         SendToProgram("post\n", cps);
10711     }
10712     SendToProgram("hard\n", cps);
10713     if (!appData.ponderNextMove) {
10714         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10715            it without being sure what state we are in first.  "hard"
10716            is not a toggle, so that one is OK.
10717          */
10718         SendToProgram("easy\n", cps);
10719     }
10720     if (cps->usePing) {
10721       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10722       SendToProgram(buf, cps);
10723     }
10724     cps->initDone = TRUE;
10725     ClearEngineOutputPane(cps == &second);
10726 }
10727
10728
10729 void
10730 ResendOptions (ChessProgramState *cps)
10731 { // send the stored value of the options
10732   int i;
10733   char buf[MSG_SIZ];
10734   Option *opt = cps->option;
10735   for(i=0; i<cps->nrOptions; i++, opt++) {
10736       switch(opt->type) {
10737         case Spin:
10738         case Slider:
10739         case CheckBox:
10740             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10741           break;
10742         case ComboBox:
10743           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10744           break;
10745         default:
10746             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10747           break;
10748         case Button:
10749         case SaveButton:
10750           continue;
10751       }
10752       SendToProgram(buf, cps);
10753   }
10754 }
10755
10756 void
10757 StartChessProgram (ChessProgramState *cps)
10758 {
10759     char buf[MSG_SIZ];
10760     int err;
10761
10762     if (appData.noChessProgram) return;
10763     cps->initDone = FALSE;
10764
10765     if (strcmp(cps->host, "localhost") == 0) {
10766         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10767     } else if (*appData.remoteShell == NULLCHAR) {
10768         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10769     } else {
10770         if (*appData.remoteUser == NULLCHAR) {
10771           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10772                     cps->program);
10773         } else {
10774           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10775                     cps->host, appData.remoteUser, cps->program);
10776         }
10777         err = StartChildProcess(buf, "", &cps->pr);
10778     }
10779
10780     if (err != 0) {
10781       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10782         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10783         if(cps != &first) return;
10784         appData.noChessProgram = TRUE;
10785         ThawUI();
10786         SetNCPMode();
10787 //      DisplayFatalError(buf, err, 1);
10788 //      cps->pr = NoProc;
10789 //      cps->isr = NULL;
10790         return;
10791     }
10792
10793     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10794     if (cps->protocolVersion > 1) {
10795       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10796       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10797         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10798         cps->comboCnt = 0;  //                and values of combo boxes
10799       }
10800       SendToProgram(buf, cps);
10801       if(cps->reload) ResendOptions(cps);
10802     } else {
10803       SendToProgram("xboard\n", cps);
10804     }
10805 }
10806
10807 void
10808 TwoMachinesEventIfReady P((void))
10809 {
10810   static int curMess = 0;
10811   if (first.lastPing != first.lastPong) {
10812     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10813     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10814     return;
10815   }
10816   if (second.lastPing != second.lastPong) {
10817     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10818     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10819     return;
10820   }
10821   DisplayMessage("", ""); curMess = 0;
10822   TwoMachinesEvent();
10823 }
10824
10825 char *
10826 MakeName (char *template)
10827 {
10828     time_t clock;
10829     struct tm *tm;
10830     static char buf[MSG_SIZ];
10831     char *p = buf;
10832     int i;
10833
10834     clock = time((time_t *)NULL);
10835     tm = localtime(&clock);
10836
10837     while(*p++ = *template++) if(p[-1] == '%') {
10838         switch(*template++) {
10839           case 0:   *p = 0; return buf;
10840           case 'Y': i = tm->tm_year+1900; break;
10841           case 'y': i = tm->tm_year-100; break;
10842           case 'M': i = tm->tm_mon+1; break;
10843           case 'd': i = tm->tm_mday; break;
10844           case 'h': i = tm->tm_hour; break;
10845           case 'm': i = tm->tm_min; break;
10846           case 's': i = tm->tm_sec; break;
10847           default:  i = 0;
10848         }
10849         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10850     }
10851     return buf;
10852 }
10853
10854 int
10855 CountPlayers (char *p)
10856 {
10857     int n = 0;
10858     while(p = strchr(p, '\n')) p++, n++; // count participants
10859     return n;
10860 }
10861
10862 FILE *
10863 WriteTourneyFile (char *results, FILE *f)
10864 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10865     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10866     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10867         // create a file with tournament description
10868         fprintf(f, "-participants {%s}\n", appData.participants);
10869         fprintf(f, "-seedBase %d\n", appData.seedBase);
10870         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10871         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10872         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10873         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10874         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10875         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10876         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10877         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10878         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10879         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10880         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10881         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10882         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10883         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10884         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10885         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10886         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10887         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10888         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10889         fprintf(f, "-smpCores %d\n", appData.smpCores);
10890         if(searchTime > 0)
10891                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10892         else {
10893                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10894                 fprintf(f, "-tc %s\n", appData.timeControl);
10895                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10896         }
10897         fprintf(f, "-results \"%s\"\n", results);
10898     }
10899     return f;
10900 }
10901
10902 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10903
10904 void
10905 Substitute (char *participants, int expunge)
10906 {
10907     int i, changed, changes=0, nPlayers=0;
10908     char *p, *q, *r, buf[MSG_SIZ];
10909     if(participants == NULL) return;
10910     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10911     r = p = participants; q = appData.participants;
10912     while(*p && *p == *q) {
10913         if(*p == '\n') r = p+1, nPlayers++;
10914         p++; q++;
10915     }
10916     if(*p) { // difference
10917         while(*p && *p++ != '\n');
10918         while(*q && *q++ != '\n');
10919       changed = nPlayers;
10920         changes = 1 + (strcmp(p, q) != 0);
10921     }
10922     if(changes == 1) { // a single engine mnemonic was changed
10923         q = r; while(*q) nPlayers += (*q++ == '\n');
10924         p = buf; while(*r && (*p = *r++) != '\n') p++;
10925         *p = NULLCHAR;
10926         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10927         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10928         if(mnemonic[i]) { // The substitute is valid
10929             FILE *f;
10930             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10931                 flock(fileno(f), LOCK_EX);
10932                 ParseArgsFromFile(f);
10933                 fseek(f, 0, SEEK_SET);
10934                 FREE(appData.participants); appData.participants = participants;
10935                 if(expunge) { // erase results of replaced engine
10936                     int len = strlen(appData.results), w, b, dummy;
10937                     for(i=0; i<len; i++) {
10938                         Pairing(i, nPlayers, &w, &b, &dummy);
10939                         if((w == changed || b == changed) && appData.results[i] == '*') {
10940                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10941                             fclose(f);
10942                             return;
10943                         }
10944                     }
10945                     for(i=0; i<len; i++) {
10946                         Pairing(i, nPlayers, &w, &b, &dummy);
10947                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10948                     }
10949                 }
10950                 WriteTourneyFile(appData.results, f);
10951                 fclose(f); // release lock
10952                 return;
10953             }
10954         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10955     }
10956     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10957     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10958     free(participants);
10959     return;
10960 }
10961
10962 int
10963 CheckPlayers (char *participants)
10964 {
10965         int i;
10966         char buf[MSG_SIZ], *p;
10967         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10968         while(p = strchr(participants, '\n')) {
10969             *p = NULLCHAR;
10970             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10971             if(!mnemonic[i]) {
10972                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10973                 *p = '\n';
10974                 DisplayError(buf, 0);
10975                 return 1;
10976             }
10977             *p = '\n';
10978             participants = p + 1;
10979         }
10980         return 0;
10981 }
10982
10983 int
10984 CreateTourney (char *name)
10985 {
10986         FILE *f;
10987         if(matchMode && strcmp(name, appData.tourneyFile)) {
10988              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10989         }
10990         if(name[0] == NULLCHAR) {
10991             if(appData.participants[0])
10992                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10993             return 0;
10994         }
10995         f = fopen(name, "r");
10996         if(f) { // file exists
10997             ASSIGN(appData.tourneyFile, name);
10998             ParseArgsFromFile(f); // parse it
10999         } else {
11000             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11001             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11002                 DisplayError(_("Not enough participants"), 0);
11003                 return 0;
11004             }
11005             if(CheckPlayers(appData.participants)) return 0;
11006             ASSIGN(appData.tourneyFile, name);
11007             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11008             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11009         }
11010         fclose(f);
11011         appData.noChessProgram = FALSE;
11012         appData.clockMode = TRUE;
11013         SetGNUMode();
11014         return 1;
11015 }
11016
11017 int
11018 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11019 {
11020     char buf[MSG_SIZ], *p, *q;
11021     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11022     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11023     skip = !all && group[0]; // if group requested, we start in skip mode
11024     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11025         p = names; q = buf; header = 0;
11026         while(*p && *p != '\n') *q++ = *p++;
11027         *q = 0;
11028         if(*p == '\n') p++;
11029         if(buf[0] == '#') {
11030             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11031             depth++; // we must be entering a new group
11032             if(all) continue; // suppress printing group headers when complete list requested
11033             header = 1;
11034             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11035         }
11036         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11037         if(engineList[i]) free(engineList[i]);
11038         engineList[i] = strdup(buf);
11039         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11040         if(engineMnemonic[i]) free(engineMnemonic[i]);
11041         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11042             strcat(buf, " (");
11043             sscanf(q + 8, "%s", buf + strlen(buf));
11044             strcat(buf, ")");
11045         }
11046         engineMnemonic[i] = strdup(buf);
11047         i++;
11048     }
11049     engineList[i] = engineMnemonic[i] = NULL;
11050     return i;
11051 }
11052
11053 // following implemented as macro to avoid type limitations
11054 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11055
11056 void
11057 SwapEngines (int n)
11058 {   // swap settings for first engine and other engine (so far only some selected options)
11059     int h;
11060     char *p;
11061     if(n == 0) return;
11062     SWAP(directory, p)
11063     SWAP(chessProgram, p)
11064     SWAP(isUCI, h)
11065     SWAP(hasOwnBookUCI, h)
11066     SWAP(protocolVersion, h)
11067     SWAP(reuse, h)
11068     SWAP(scoreIsAbsolute, h)
11069     SWAP(timeOdds, h)
11070     SWAP(logo, p)
11071     SWAP(pgnName, p)
11072     SWAP(pvSAN, h)
11073     SWAP(engOptions, p)
11074     SWAP(engInitString, p)
11075     SWAP(computerString, p)
11076     SWAP(features, p)
11077     SWAP(fenOverride, p)
11078     SWAP(NPS, h)
11079     SWAP(accumulateTC, h)
11080     SWAP(drawDepth, h)
11081     SWAP(host, p)
11082     SWAP(pseudo, h)
11083 }
11084
11085 int
11086 GetEngineLine (char *s, int n)
11087 {
11088     int i;
11089     char buf[MSG_SIZ];
11090     extern char *icsNames;
11091     if(!s || !*s) return 0;
11092     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11093     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11094     if(!mnemonic[i]) return 0;
11095     if(n == 11) return 1; // just testing if there was a match
11096     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11097     if(n == 1) SwapEngines(n);
11098     ParseArgsFromString(buf);
11099     if(n == 1) SwapEngines(n);
11100     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11101         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11102         ParseArgsFromString(buf);
11103     }
11104     return 1;
11105 }
11106
11107 int
11108 SetPlayer (int player, char *p)
11109 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11110     int i;
11111     char buf[MSG_SIZ], *engineName;
11112     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11113     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11114     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11115     if(mnemonic[i]) {
11116         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11117         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11118         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11119         ParseArgsFromString(buf);
11120     } else { // no engine with this nickname is installed!
11121         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11122         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11123         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11124         ModeHighlight();
11125         DisplayError(buf, 0);
11126         return 0;
11127     }
11128     free(engineName);
11129     return i;
11130 }
11131
11132 char *recentEngines;
11133
11134 void
11135 RecentEngineEvent (int nr)
11136 {
11137     int n;
11138 //    SwapEngines(1); // bump first to second
11139 //    ReplaceEngine(&second, 1); // and load it there
11140     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11141     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11142     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11143         ReplaceEngine(&first, 0);
11144         FloatToFront(&appData.recentEngineList, command[n]);
11145     }
11146 }
11147
11148 int
11149 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11150 {   // determine players from game number
11151     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11152
11153     if(appData.tourneyType == 0) {
11154         roundsPerCycle = (nPlayers - 1) | 1;
11155         pairingsPerRound = nPlayers / 2;
11156     } else if(appData.tourneyType > 0) {
11157         roundsPerCycle = nPlayers - appData.tourneyType;
11158         pairingsPerRound = appData.tourneyType;
11159     }
11160     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11161     gamesPerCycle = gamesPerRound * roundsPerCycle;
11162     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11163     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11164     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11165     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11166     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11167     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11168
11169     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11170     if(appData.roundSync) *syncInterval = gamesPerRound;
11171
11172     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11173
11174     if(appData.tourneyType == 0) {
11175         if(curPairing == (nPlayers-1)/2 ) {
11176             *whitePlayer = curRound;
11177             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11178         } else {
11179             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11180             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11181             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11182             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11183         }
11184     } else if(appData.tourneyType > 1) {
11185         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11186         *whitePlayer = curRound + appData.tourneyType;
11187     } else if(appData.tourneyType > 0) {
11188         *whitePlayer = curPairing;
11189         *blackPlayer = curRound + appData.tourneyType;
11190     }
11191
11192     // take care of white/black alternation per round.
11193     // For cycles and games this is already taken care of by default, derived from matchGame!
11194     return curRound & 1;
11195 }
11196
11197 int
11198 NextTourneyGame (int nr, int *swapColors)
11199 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11200     char *p, *q;
11201     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11202     FILE *tf;
11203     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11204     tf = fopen(appData.tourneyFile, "r");
11205     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11206     ParseArgsFromFile(tf); fclose(tf);
11207     InitTimeControls(); // TC might be altered from tourney file
11208
11209     nPlayers = CountPlayers(appData.participants); // count participants
11210     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11211     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11212
11213     if(syncInterval) {
11214         p = q = appData.results;
11215         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11216         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11217             DisplayMessage(_("Waiting for other game(s)"),"");
11218             waitingForGame = TRUE;
11219             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11220             return 0;
11221         }
11222         waitingForGame = FALSE;
11223     }
11224
11225     if(appData.tourneyType < 0) {
11226         if(nr>=0 && !pairingReceived) {
11227             char buf[1<<16];
11228             if(pairing.pr == NoProc) {
11229                 if(!appData.pairingEngine[0]) {
11230                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11231                     return 0;
11232                 }
11233                 StartChessProgram(&pairing); // starts the pairing engine
11234             }
11235             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11236             SendToProgram(buf, &pairing);
11237             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11238             SendToProgram(buf, &pairing);
11239             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11240         }
11241         pairingReceived = 0;                              // ... so we continue here
11242         *swapColors = 0;
11243         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11244         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11245         matchGame = 1; roundNr = nr / syncInterval + 1;
11246     }
11247
11248     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11249
11250     // redefine engines, engine dir, etc.
11251     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11252     if(first.pr == NoProc) {
11253       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11254       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11255     }
11256     if(second.pr == NoProc) {
11257       SwapEngines(1);
11258       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11259       SwapEngines(1);         // and make that valid for second engine by swapping
11260       InitEngine(&second, 1);
11261     }
11262     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11263     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11264     return OK;
11265 }
11266
11267 void
11268 NextMatchGame ()
11269 {   // performs game initialization that does not invoke engines, and then tries to start the game
11270     int res, firstWhite, swapColors = 0;
11271     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11272     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
11273         char buf[MSG_SIZ];
11274         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11275         if(strcmp(buf, currentDebugFile)) { // name has changed
11276             FILE *f = fopen(buf, "w");
11277             if(f) { // if opening the new file failed, just keep using the old one
11278                 ASSIGN(currentDebugFile, buf);
11279                 fclose(debugFP);
11280                 debugFP = f;
11281             }
11282             if(appData.serverFileName) {
11283                 if(serverFP) fclose(serverFP);
11284                 serverFP = fopen(appData.serverFileName, "w");
11285                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11286                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11287             }
11288         }
11289     }
11290     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11291     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11292     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11293     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11294     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11295     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11296     Reset(FALSE, first.pr != NoProc);
11297     res = LoadGameOrPosition(matchGame); // setup game
11298     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11299     if(!res) return; // abort when bad game/pos file
11300     TwoMachinesEvent();
11301 }
11302
11303 void
11304 UserAdjudicationEvent (int result)
11305 {
11306     ChessMove gameResult = GameIsDrawn;
11307
11308     if( result > 0 ) {
11309         gameResult = WhiteWins;
11310     }
11311     else if( result < 0 ) {
11312         gameResult = BlackWins;
11313     }
11314
11315     if( gameMode == TwoMachinesPlay ) {
11316         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11317     }
11318 }
11319
11320
11321 // [HGM] save: calculate checksum of game to make games easily identifiable
11322 int
11323 StringCheckSum (char *s)
11324 {
11325         int i = 0;
11326         if(s==NULL) return 0;
11327         while(*s) i = i*259 + *s++;
11328         return i;
11329 }
11330
11331 int
11332 GameCheckSum ()
11333 {
11334         int i, sum=0;
11335         for(i=backwardMostMove; i<forwardMostMove; i++) {
11336                 sum += pvInfoList[i].depth;
11337                 sum += StringCheckSum(parseList[i]);
11338                 sum += StringCheckSum(commentList[i]);
11339                 sum *= 261;
11340         }
11341         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11342         return sum + StringCheckSum(commentList[i]);
11343 } // end of save patch
11344
11345 void
11346 GameEnds (ChessMove result, char *resultDetails, int whosays)
11347 {
11348     GameMode nextGameMode;
11349     int isIcsGame;
11350     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11351
11352     if(endingGame) return; /* [HGM] crash: forbid recursion */
11353     endingGame = 1;
11354     if(twoBoards) { // [HGM] dual: switch back to one board
11355         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11356         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11357     }
11358     if (appData.debugMode) {
11359       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11360               result, resultDetails ? resultDetails : "(null)", whosays);
11361     }
11362
11363     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11364
11365     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11366
11367     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11368         /* If we are playing on ICS, the server decides when the
11369            game is over, but the engine can offer to draw, claim
11370            a draw, or resign.
11371          */
11372 #if ZIPPY
11373         if (appData.zippyPlay && first.initDone) {
11374             if (result == GameIsDrawn) {
11375                 /* In case draw still needs to be claimed */
11376                 SendToICS(ics_prefix);
11377                 SendToICS("draw\n");
11378             } else if (StrCaseStr(resultDetails, "resign")) {
11379                 SendToICS(ics_prefix);
11380                 SendToICS("resign\n");
11381             }
11382         }
11383 #endif
11384         endingGame = 0; /* [HGM] crash */
11385         return;
11386     }
11387
11388     /* If we're loading the game from a file, stop */
11389     if (whosays == GE_FILE) {
11390       (void) StopLoadGameTimer();
11391       gameFileFP = NULL;
11392     }
11393
11394     /* Cancel draw offers */
11395     first.offeredDraw = second.offeredDraw = 0;
11396
11397     /* If this is an ICS game, only ICS can really say it's done;
11398        if not, anyone can. */
11399     isIcsGame = (gameMode == IcsPlayingWhite ||
11400                  gameMode == IcsPlayingBlack ||
11401                  gameMode == IcsObserving    ||
11402                  gameMode == IcsExamining);
11403
11404     if (!isIcsGame || whosays == GE_ICS) {
11405         /* OK -- not an ICS game, or ICS said it was done */
11406         StopClocks();
11407         if (!isIcsGame && !appData.noChessProgram)
11408           SetUserThinkingEnables();
11409
11410         /* [HGM] if a machine claims the game end we verify this claim */
11411         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11412             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11413                 char claimer;
11414                 ChessMove trueResult = (ChessMove) -1;
11415
11416                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11417                                             first.twoMachinesColor[0] :
11418                                             second.twoMachinesColor[0] ;
11419
11420                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11421                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11422                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11423                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11424                 } else
11425                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11426                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11427                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11428                 } else
11429                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11430                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11431                 }
11432
11433                 // now verify win claims, but not in drop games, as we don't understand those yet
11434                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11435                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11436                     (result == WhiteWins && claimer == 'w' ||
11437                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11438                       if (appData.debugMode) {
11439                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11440                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11441                       }
11442                       if(result != trueResult) {
11443                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11444                               result = claimer == 'w' ? BlackWins : WhiteWins;
11445                               resultDetails = buf;
11446                       }
11447                 } else
11448                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11449                     && (forwardMostMove <= backwardMostMove ||
11450                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11451                         (claimer=='b')==(forwardMostMove&1))
11452                                                                                   ) {
11453                       /* [HGM] verify: draws that were not flagged are false claims */
11454                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11455                       result = claimer == 'w' ? BlackWins : WhiteWins;
11456                       resultDetails = buf;
11457                 }
11458                 /* (Claiming a loss is accepted no questions asked!) */
11459             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11460                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11461                 result = GameUnfinished;
11462                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11463             }
11464             /* [HGM] bare: don't allow bare King to win */
11465             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11466                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11467                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11468                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11469                && result != GameIsDrawn)
11470             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11471                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11472                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11473                         if(p >= 0 && p <= (int)WhiteKing) k++;
11474                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11475                 }
11476                 if (appData.debugMode) {
11477                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11478                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11479                 }
11480                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11481                         result = GameIsDrawn;
11482                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11483                         resultDetails = buf;
11484                 }
11485             }
11486         }
11487
11488
11489         if(serverMoves != NULL && !loadFlag) { char c = '=';
11490             if(result==WhiteWins) c = '+';
11491             if(result==BlackWins) c = '-';
11492             if(resultDetails != NULL)
11493                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11494         }
11495         if (resultDetails != NULL) {
11496             gameInfo.result = result;
11497             gameInfo.resultDetails = StrSave(resultDetails);
11498
11499             /* display last move only if game was not loaded from file */
11500             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11501                 DisplayMove(currentMove - 1);
11502
11503             if (forwardMostMove != 0) {
11504                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11505                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11506                                                                 ) {
11507                     if (*appData.saveGameFile != NULLCHAR) {
11508                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11509                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11510                         else
11511                         SaveGameToFile(appData.saveGameFile, TRUE);
11512                     } else if (appData.autoSaveGames) {
11513                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11514                     }
11515                     if (*appData.savePositionFile != NULLCHAR) {
11516                         SavePositionToFile(appData.savePositionFile);
11517                     }
11518                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11519                 }
11520             }
11521
11522             /* Tell program how game ended in case it is learning */
11523             /* [HGM] Moved this to after saving the PGN, just in case */
11524             /* engine died and we got here through time loss. In that */
11525             /* case we will get a fatal error writing the pipe, which */
11526             /* would otherwise lose us the PGN.                       */
11527             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11528             /* output during GameEnds should never be fatal anymore   */
11529             if (gameMode == MachinePlaysWhite ||
11530                 gameMode == MachinePlaysBlack ||
11531                 gameMode == TwoMachinesPlay ||
11532                 gameMode == IcsPlayingWhite ||
11533                 gameMode == IcsPlayingBlack ||
11534                 gameMode == BeginningOfGame) {
11535                 char buf[MSG_SIZ];
11536                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11537                         resultDetails);
11538                 if (first.pr != NoProc) {
11539                     SendToProgram(buf, &first);
11540                 }
11541                 if (second.pr != NoProc &&
11542                     gameMode == TwoMachinesPlay) {
11543                     SendToProgram(buf, &second);
11544                 }
11545             }
11546         }
11547
11548         if (appData.icsActive) {
11549             if (appData.quietPlay &&
11550                 (gameMode == IcsPlayingWhite ||
11551                  gameMode == IcsPlayingBlack)) {
11552                 SendToICS(ics_prefix);
11553                 SendToICS("set shout 1\n");
11554             }
11555             nextGameMode = IcsIdle;
11556             ics_user_moved = FALSE;
11557             /* clean up premove.  It's ugly when the game has ended and the
11558              * premove highlights are still on the board.
11559              */
11560             if (gotPremove) {
11561               gotPremove = FALSE;
11562               ClearPremoveHighlights();
11563               DrawPosition(FALSE, boards[currentMove]);
11564             }
11565             if (whosays == GE_ICS) {
11566                 switch (result) {
11567                 case WhiteWins:
11568                     if (gameMode == IcsPlayingWhite)
11569                         PlayIcsWinSound();
11570                     else if(gameMode == IcsPlayingBlack)
11571                         PlayIcsLossSound();
11572                     break;
11573                 case BlackWins:
11574                     if (gameMode == IcsPlayingBlack)
11575                         PlayIcsWinSound();
11576                     else if(gameMode == IcsPlayingWhite)
11577                         PlayIcsLossSound();
11578                     break;
11579                 case GameIsDrawn:
11580                     PlayIcsDrawSound();
11581                     break;
11582                 default:
11583                     PlayIcsUnfinishedSound();
11584                 }
11585             }
11586             if(appData.quitNext) { ExitEvent(0); return; }
11587         } else if (gameMode == EditGame ||
11588                    gameMode == PlayFromGameFile ||
11589                    gameMode == AnalyzeMode ||
11590                    gameMode == AnalyzeFile) {
11591             nextGameMode = gameMode;
11592         } else {
11593             nextGameMode = EndOfGame;
11594         }
11595         pausing = FALSE;
11596         ModeHighlight();
11597     } else {
11598         nextGameMode = gameMode;
11599     }
11600
11601     if (appData.noChessProgram) {
11602         gameMode = nextGameMode;
11603         ModeHighlight();
11604         endingGame = 0; /* [HGM] crash */
11605         return;
11606     }
11607
11608     if (first.reuse) {
11609         /* Put first chess program into idle state */
11610         if (first.pr != NoProc &&
11611             (gameMode == MachinePlaysWhite ||
11612              gameMode == MachinePlaysBlack ||
11613              gameMode == TwoMachinesPlay ||
11614              gameMode == IcsPlayingWhite ||
11615              gameMode == IcsPlayingBlack ||
11616              gameMode == BeginningOfGame)) {
11617             SendToProgram("force\n", &first);
11618             if (first.usePing) {
11619               char buf[MSG_SIZ];
11620               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11621               SendToProgram(buf, &first);
11622             }
11623         }
11624     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11625         /* Kill off first chess program */
11626         if (first.isr != NULL)
11627           RemoveInputSource(first.isr);
11628         first.isr = NULL;
11629
11630         if (first.pr != NoProc) {
11631             ExitAnalyzeMode();
11632             DoSleep( appData.delayBeforeQuit );
11633             SendToProgram("quit\n", &first);
11634             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11635             first.reload = TRUE;
11636         }
11637         first.pr = NoProc;
11638     }
11639     if (second.reuse) {
11640         /* Put second chess program into idle state */
11641         if (second.pr != NoProc &&
11642             gameMode == TwoMachinesPlay) {
11643             SendToProgram("force\n", &second);
11644             if (second.usePing) {
11645               char buf[MSG_SIZ];
11646               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11647               SendToProgram(buf, &second);
11648             }
11649         }
11650     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11651         /* Kill off second chess program */
11652         if (second.isr != NULL)
11653           RemoveInputSource(second.isr);
11654         second.isr = NULL;
11655
11656         if (second.pr != NoProc) {
11657             DoSleep( appData.delayBeforeQuit );
11658             SendToProgram("quit\n", &second);
11659             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11660             second.reload = TRUE;
11661         }
11662         second.pr = NoProc;
11663     }
11664
11665     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11666         char resChar = '=';
11667         switch (result) {
11668         case WhiteWins:
11669           resChar = '+';
11670           if (first.twoMachinesColor[0] == 'w') {
11671             first.matchWins++;
11672           } else {
11673             second.matchWins++;
11674           }
11675           break;
11676         case BlackWins:
11677           resChar = '-';
11678           if (first.twoMachinesColor[0] == 'b') {
11679             first.matchWins++;
11680           } else {
11681             second.matchWins++;
11682           }
11683           break;
11684         case GameUnfinished:
11685           resChar = ' ';
11686         default:
11687           break;
11688         }
11689
11690         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11691         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11692             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11693             ReserveGame(nextGame, resChar); // sets nextGame
11694             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11695             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11696         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11697
11698         if (nextGame <= appData.matchGames && !abortMatch) {
11699             gameMode = nextGameMode;
11700             matchGame = nextGame; // this will be overruled in tourney mode!
11701             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11702             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11703             endingGame = 0; /* [HGM] crash */
11704             return;
11705         } else {
11706             gameMode = nextGameMode;
11707             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11708                      first.tidy, second.tidy,
11709                      first.matchWins, second.matchWins,
11710                      appData.matchGames - (first.matchWins + second.matchWins));
11711             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11712             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11713             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11714             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11715                 first.twoMachinesColor = "black\n";
11716                 second.twoMachinesColor = "white\n";
11717             } else {
11718                 first.twoMachinesColor = "white\n";
11719                 second.twoMachinesColor = "black\n";
11720             }
11721         }
11722     }
11723     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11724         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11725       ExitAnalyzeMode();
11726     gameMode = nextGameMode;
11727     ModeHighlight();
11728     endingGame = 0;  /* [HGM] crash */
11729     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11730         if(matchMode == TRUE) { // match through command line: exit with or without popup
11731             if(ranking) {
11732                 ToNrEvent(forwardMostMove);
11733                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11734                 else ExitEvent(0);
11735             } else DisplayFatalError(buf, 0, 0);
11736         } else { // match through menu; just stop, with or without popup
11737             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11738             ModeHighlight();
11739             if(ranking){
11740                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11741             } else DisplayNote(buf);
11742       }
11743       if(ranking) free(ranking);
11744     }
11745 }
11746
11747 /* Assumes program was just initialized (initString sent).
11748    Leaves program in force mode. */
11749 void
11750 FeedMovesToProgram (ChessProgramState *cps, int upto)
11751 {
11752     int i;
11753
11754     if (appData.debugMode)
11755       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11756               startedFromSetupPosition ? "position and " : "",
11757               backwardMostMove, upto, cps->which);
11758     if(currentlyInitializedVariant != gameInfo.variant) {
11759       char buf[MSG_SIZ];
11760         // [HGM] variantswitch: make engine aware of new variant
11761         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11762                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11763                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11764         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11765         SendToProgram(buf, cps);
11766         currentlyInitializedVariant = gameInfo.variant;
11767     }
11768     SendToProgram("force\n", cps);
11769     if (startedFromSetupPosition) {
11770         SendBoard(cps, backwardMostMove);
11771     if (appData.debugMode) {
11772         fprintf(debugFP, "feedMoves\n");
11773     }
11774     }
11775     for (i = backwardMostMove; i < upto; i++) {
11776         SendMoveToProgram(i, cps);
11777     }
11778 }
11779
11780
11781 int
11782 ResurrectChessProgram ()
11783 {
11784      /* The chess program may have exited.
11785         If so, restart it and feed it all the moves made so far. */
11786     static int doInit = 0;
11787
11788     if (appData.noChessProgram) return 1;
11789
11790     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11791         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11792         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11793         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11794     } else {
11795         if (first.pr != NoProc) return 1;
11796         StartChessProgram(&first);
11797     }
11798     InitChessProgram(&first, FALSE);
11799     FeedMovesToProgram(&first, currentMove);
11800
11801     if (!first.sendTime) {
11802         /* can't tell gnuchess what its clock should read,
11803            so we bow to its notion. */
11804         ResetClocks();
11805         timeRemaining[0][currentMove] = whiteTimeRemaining;
11806         timeRemaining[1][currentMove] = blackTimeRemaining;
11807     }
11808
11809     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11810                 appData.icsEngineAnalyze) && first.analysisSupport) {
11811       SendToProgram("analyze\n", &first);
11812       first.analyzing = TRUE;
11813     }
11814     return 1;
11815 }
11816
11817 /*
11818  * Button procedures
11819  */
11820 void
11821 Reset (int redraw, int init)
11822 {
11823     int i;
11824
11825     if (appData.debugMode) {
11826         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11827                 redraw, init, gameMode);
11828     }
11829     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11830     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11831     CleanupTail(); // [HGM] vari: delete any stored variations
11832     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11833     pausing = pauseExamInvalid = FALSE;
11834     startedFromSetupPosition = blackPlaysFirst = FALSE;
11835     firstMove = TRUE;
11836     whiteFlag = blackFlag = FALSE;
11837     userOfferedDraw = FALSE;
11838     hintRequested = bookRequested = FALSE;
11839     first.maybeThinking = FALSE;
11840     second.maybeThinking = FALSE;
11841     first.bookSuspend = FALSE; // [HGM] book
11842     second.bookSuspend = FALSE;
11843     thinkOutput[0] = NULLCHAR;
11844     lastHint[0] = NULLCHAR;
11845     ClearGameInfo(&gameInfo);
11846     gameInfo.variant = StringToVariant(appData.variant);
11847     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11848     ics_user_moved = ics_clock_paused = FALSE;
11849     ics_getting_history = H_FALSE;
11850     ics_gamenum = -1;
11851     white_holding[0] = black_holding[0] = NULLCHAR;
11852     ClearProgramStats();
11853     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11854
11855     ResetFrontEnd();
11856     ClearHighlights();
11857     flipView = appData.flipView;
11858     ClearPremoveHighlights();
11859     gotPremove = FALSE;
11860     alarmSounded = FALSE;
11861     killX = killY = -1; // [HGM] lion
11862
11863     GameEnds(EndOfFile, NULL, GE_PLAYER);
11864     if(appData.serverMovesName != NULL) {
11865         /* [HGM] prepare to make moves file for broadcasting */
11866         clock_t t = clock();
11867         if(serverMoves != NULL) fclose(serverMoves);
11868         serverMoves = fopen(appData.serverMovesName, "r");
11869         if(serverMoves != NULL) {
11870             fclose(serverMoves);
11871             /* delay 15 sec before overwriting, so all clients can see end */
11872             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11873         }
11874         serverMoves = fopen(appData.serverMovesName, "w");
11875     }
11876
11877     ExitAnalyzeMode();
11878     gameMode = BeginningOfGame;
11879     ModeHighlight();
11880     if(appData.icsActive) gameInfo.variant = VariantNormal;
11881     currentMove = forwardMostMove = backwardMostMove = 0;
11882     MarkTargetSquares(1);
11883     InitPosition(redraw);
11884     for (i = 0; i < MAX_MOVES; i++) {
11885         if (commentList[i] != NULL) {
11886             free(commentList[i]);
11887             commentList[i] = NULL;
11888         }
11889     }
11890     ResetClocks();
11891     timeRemaining[0][0] = whiteTimeRemaining;
11892     timeRemaining[1][0] = blackTimeRemaining;
11893
11894     if (first.pr == NoProc) {
11895         StartChessProgram(&first);
11896     }
11897     if (init) {
11898             InitChessProgram(&first, startedFromSetupPosition);
11899     }
11900     DisplayTitle("");
11901     DisplayMessage("", "");
11902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11903     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11904     ClearMap();        // [HGM] exclude: invalidate map
11905 }
11906
11907 void
11908 AutoPlayGameLoop ()
11909 {
11910     for (;;) {
11911         if (!AutoPlayOneMove())
11912           return;
11913         if (matchMode || appData.timeDelay == 0)
11914           continue;
11915         if (appData.timeDelay < 0)
11916           return;
11917         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11918         break;
11919     }
11920 }
11921
11922 void
11923 AnalyzeNextGame()
11924 {
11925     ReloadGame(1); // next game
11926 }
11927
11928 int
11929 AutoPlayOneMove ()
11930 {
11931     int fromX, fromY, toX, toY;
11932
11933     if (appData.debugMode) {
11934       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11935     }
11936
11937     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11938       return FALSE;
11939
11940     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11941       pvInfoList[currentMove].depth = programStats.depth;
11942       pvInfoList[currentMove].score = programStats.score;
11943       pvInfoList[currentMove].time  = 0;
11944       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11945       else { // append analysis of final position as comment
11946         char buf[MSG_SIZ];
11947         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11948         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11949       }
11950       programStats.depth = 0;
11951     }
11952
11953     if (currentMove >= forwardMostMove) {
11954       if(gameMode == AnalyzeFile) {
11955           if(appData.loadGameIndex == -1) {
11956             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11957           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11958           } else {
11959           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11960         }
11961       }
11962 //      gameMode = EndOfGame;
11963 //      ModeHighlight();
11964
11965       /* [AS] Clear current move marker at the end of a game */
11966       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11967
11968       return FALSE;
11969     }
11970
11971     toX = moveList[currentMove][2] - AAA;
11972     toY = moveList[currentMove][3] - ONE;
11973
11974     if (moveList[currentMove][1] == '@') {
11975         if (appData.highlightLastMove) {
11976             SetHighlights(-1, -1, toX, toY);
11977         }
11978     } else {
11979         int viaX = moveList[currentMove][5] - AAA;
11980         int viaY = moveList[currentMove][6] - ONE;
11981         fromX = moveList[currentMove][0] - AAA;
11982         fromY = moveList[currentMove][1] - ONE;
11983
11984         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11985
11986         if(moveList[currentMove][4] == ';') { // multi-leg
11987             ChessSquare piece = boards[currentMove][viaY][viaX];
11988             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11989             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11990             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11991             boards[currentMove][viaY][viaX] = piece;
11992         } else
11993         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11994
11995         if (appData.highlightLastMove) {
11996             SetHighlights(fromX, fromY, toX, toY);
11997         }
11998     }
11999     DisplayMove(currentMove);
12000     SendMoveToProgram(currentMove++, &first);
12001     DisplayBothClocks();
12002     DrawPosition(FALSE, boards[currentMove]);
12003     // [HGM] PV info: always display, routine tests if empty
12004     DisplayComment(currentMove - 1, commentList[currentMove]);
12005     return TRUE;
12006 }
12007
12008
12009 int
12010 LoadGameOneMove (ChessMove readAhead)
12011 {
12012     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12013     char promoChar = NULLCHAR;
12014     ChessMove moveType;
12015     char move[MSG_SIZ];
12016     char *p, *q;
12017
12018     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12019         gameMode != AnalyzeMode && gameMode != Training) {
12020         gameFileFP = NULL;
12021         return FALSE;
12022     }
12023
12024     yyboardindex = forwardMostMove;
12025     if (readAhead != EndOfFile) {
12026       moveType = readAhead;
12027     } else {
12028       if (gameFileFP == NULL)
12029           return FALSE;
12030       moveType = (ChessMove) Myylex();
12031     }
12032
12033     done = FALSE;
12034     switch (moveType) {
12035       case Comment:
12036         if (appData.debugMode)
12037           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12038         p = yy_text;
12039
12040         /* append the comment but don't display it */
12041         AppendComment(currentMove, p, FALSE);
12042         return TRUE;
12043
12044       case WhiteCapturesEnPassant:
12045       case BlackCapturesEnPassant:
12046       case WhitePromotion:
12047       case BlackPromotion:
12048       case WhiteNonPromotion:
12049       case BlackNonPromotion:
12050       case NormalMove:
12051       case FirstLeg:
12052       case WhiteKingSideCastle:
12053       case WhiteQueenSideCastle:
12054       case BlackKingSideCastle:
12055       case BlackQueenSideCastle:
12056       case WhiteKingSideCastleWild:
12057       case WhiteQueenSideCastleWild:
12058       case BlackKingSideCastleWild:
12059       case BlackQueenSideCastleWild:
12060       /* PUSH Fabien */
12061       case WhiteHSideCastleFR:
12062       case WhiteASideCastleFR:
12063       case BlackHSideCastleFR:
12064       case BlackASideCastleFR:
12065       /* POP Fabien */
12066         if (appData.debugMode)
12067           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12068         fromX = currentMoveString[0] - AAA;
12069         fromY = currentMoveString[1] - ONE;
12070         toX = currentMoveString[2] - AAA;
12071         toY = currentMoveString[3] - ONE;
12072         promoChar = currentMoveString[4];
12073         if(promoChar == ';') promoChar = NULLCHAR;
12074         break;
12075
12076       case WhiteDrop:
12077       case BlackDrop:
12078         if (appData.debugMode)
12079           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12080         fromX = moveType == WhiteDrop ?
12081           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12082         (int) CharToPiece(ToLower(currentMoveString[0]));
12083         fromY = DROP_RANK;
12084         toX = currentMoveString[2] - AAA;
12085         toY = currentMoveString[3] - ONE;
12086         break;
12087
12088       case WhiteWins:
12089       case BlackWins:
12090       case GameIsDrawn:
12091       case GameUnfinished:
12092         if (appData.debugMode)
12093           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12094         p = strchr(yy_text, '{');
12095         if (p == NULL) p = strchr(yy_text, '(');
12096         if (p == NULL) {
12097             p = yy_text;
12098             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12099         } else {
12100             q = strchr(p, *p == '{' ? '}' : ')');
12101             if (q != NULL) *q = NULLCHAR;
12102             p++;
12103         }
12104         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12105         GameEnds(moveType, p, GE_FILE);
12106         done = TRUE;
12107         if (cmailMsgLoaded) {
12108             ClearHighlights();
12109             flipView = WhiteOnMove(currentMove);
12110             if (moveType == GameUnfinished) flipView = !flipView;
12111             if (appData.debugMode)
12112               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12113         }
12114         break;
12115
12116       case EndOfFile:
12117         if (appData.debugMode)
12118           fprintf(debugFP, "Parser hit end of file\n");
12119         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12120           case MT_NONE:
12121           case MT_CHECK:
12122             break;
12123           case MT_CHECKMATE:
12124           case MT_STAINMATE:
12125             if (WhiteOnMove(currentMove)) {
12126                 GameEnds(BlackWins, "Black mates", GE_FILE);
12127             } else {
12128                 GameEnds(WhiteWins, "White mates", GE_FILE);
12129             }
12130             break;
12131           case MT_STALEMATE:
12132             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12133             break;
12134         }
12135         done = TRUE;
12136         break;
12137
12138       case MoveNumberOne:
12139         if (lastLoadGameStart == GNUChessGame) {
12140             /* GNUChessGames have numbers, but they aren't move numbers */
12141             if (appData.debugMode)
12142               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12143                       yy_text, (int) moveType);
12144             return LoadGameOneMove(EndOfFile); /* tail recursion */
12145         }
12146         /* else fall thru */
12147
12148       case XBoardGame:
12149       case GNUChessGame:
12150       case PGNTag:
12151         /* Reached start of next game in file */
12152         if (appData.debugMode)
12153           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12154         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12155           case MT_NONE:
12156           case MT_CHECK:
12157             break;
12158           case MT_CHECKMATE:
12159           case MT_STAINMATE:
12160             if (WhiteOnMove(currentMove)) {
12161                 GameEnds(BlackWins, "Black mates", GE_FILE);
12162             } else {
12163                 GameEnds(WhiteWins, "White mates", GE_FILE);
12164             }
12165             break;
12166           case MT_STALEMATE:
12167             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12168             break;
12169         }
12170         done = TRUE;
12171         break;
12172
12173       case PositionDiagram:     /* should not happen; ignore */
12174       case ElapsedTime:         /* ignore */
12175       case NAG:                 /* ignore */
12176         if (appData.debugMode)
12177           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12178                   yy_text, (int) moveType);
12179         return LoadGameOneMove(EndOfFile); /* tail recursion */
12180
12181       case IllegalMove:
12182         if (appData.testLegality) {
12183             if (appData.debugMode)
12184               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12185             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12186                     (forwardMostMove / 2) + 1,
12187                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12188             DisplayError(move, 0);
12189             done = TRUE;
12190         } else {
12191             if (appData.debugMode)
12192               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12193                       yy_text, currentMoveString);
12194             if(currentMoveString[1] == '@') {
12195                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12196                 fromY = DROP_RANK;
12197             } else {
12198                 fromX = currentMoveString[0] - AAA;
12199                 fromY = currentMoveString[1] - ONE;
12200             }
12201             toX = currentMoveString[2] - AAA;
12202             toY = currentMoveString[3] - ONE;
12203             promoChar = currentMoveString[4];
12204         }
12205         break;
12206
12207       case AmbiguousMove:
12208         if (appData.debugMode)
12209           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12210         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12211                 (forwardMostMove / 2) + 1,
12212                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12213         DisplayError(move, 0);
12214         done = TRUE;
12215         break;
12216
12217       default:
12218       case ImpossibleMove:
12219         if (appData.debugMode)
12220           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12221         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12222                 (forwardMostMove / 2) + 1,
12223                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12224         DisplayError(move, 0);
12225         done = TRUE;
12226         break;
12227     }
12228
12229     if (done) {
12230         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12231             DrawPosition(FALSE, boards[currentMove]);
12232             DisplayBothClocks();
12233             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12234               DisplayComment(currentMove - 1, commentList[currentMove]);
12235         }
12236         (void) StopLoadGameTimer();
12237         gameFileFP = NULL;
12238         cmailOldMove = forwardMostMove;
12239         return FALSE;
12240     } else {
12241         /* currentMoveString is set as a side-effect of yylex */
12242
12243         thinkOutput[0] = NULLCHAR;
12244         MakeMove(fromX, fromY, toX, toY, promoChar);
12245         killX = killY = -1; // [HGM] lion: used up
12246         currentMove = forwardMostMove;
12247         return TRUE;
12248     }
12249 }
12250
12251 /* Load the nth game from the given file */
12252 int
12253 LoadGameFromFile (char *filename, int n, char *title, int useList)
12254 {
12255     FILE *f;
12256     char buf[MSG_SIZ];
12257
12258     if (strcmp(filename, "-") == 0) {
12259         f = stdin;
12260         title = "stdin";
12261     } else {
12262         f = fopen(filename, "rb");
12263         if (f == NULL) {
12264           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12265             DisplayError(buf, errno);
12266             return FALSE;
12267         }
12268     }
12269     if (fseek(f, 0, 0) == -1) {
12270         /* f is not seekable; probably a pipe */
12271         useList = FALSE;
12272     }
12273     if (useList && n == 0) {
12274         int error = GameListBuild(f);
12275         if (error) {
12276             DisplayError(_("Cannot build game list"), error);
12277         } else if (!ListEmpty(&gameList) &&
12278                    ((ListGame *) gameList.tailPred)->number > 1) {
12279             GameListPopUp(f, title);
12280             return TRUE;
12281         }
12282         GameListDestroy();
12283         n = 1;
12284     }
12285     if (n == 0) n = 1;
12286     return LoadGame(f, n, title, FALSE);
12287 }
12288
12289
12290 void
12291 MakeRegisteredMove ()
12292 {
12293     int fromX, fromY, toX, toY;
12294     char promoChar;
12295     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12296         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12297           case CMAIL_MOVE:
12298           case CMAIL_DRAW:
12299             if (appData.debugMode)
12300               fprintf(debugFP, "Restoring %s for game %d\n",
12301                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12302
12303             thinkOutput[0] = NULLCHAR;
12304             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12305             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12306             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12307             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12308             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12309             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12310             MakeMove(fromX, fromY, toX, toY, promoChar);
12311             ShowMove(fromX, fromY, toX, toY);
12312
12313             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12314               case MT_NONE:
12315               case MT_CHECK:
12316                 break;
12317
12318               case MT_CHECKMATE:
12319               case MT_STAINMATE:
12320                 if (WhiteOnMove(currentMove)) {
12321                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12322                 } else {
12323                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12324                 }
12325                 break;
12326
12327               case MT_STALEMATE:
12328                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12329                 break;
12330             }
12331
12332             break;
12333
12334           case CMAIL_RESIGN:
12335             if (WhiteOnMove(currentMove)) {
12336                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12337             } else {
12338                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12339             }
12340             break;
12341
12342           case CMAIL_ACCEPT:
12343             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12344             break;
12345
12346           default:
12347             break;
12348         }
12349     }
12350
12351     return;
12352 }
12353
12354 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12355 int
12356 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12357 {
12358     int retVal;
12359
12360     if (gameNumber > nCmailGames) {
12361         DisplayError(_("No more games in this message"), 0);
12362         return FALSE;
12363     }
12364     if (f == lastLoadGameFP) {
12365         int offset = gameNumber - lastLoadGameNumber;
12366         if (offset == 0) {
12367             cmailMsg[0] = NULLCHAR;
12368             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12369                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12370                 nCmailMovesRegistered--;
12371             }
12372             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12373             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12374                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12375             }
12376         } else {
12377             if (! RegisterMove()) return FALSE;
12378         }
12379     }
12380
12381     retVal = LoadGame(f, gameNumber, title, useList);
12382
12383     /* Make move registered during previous look at this game, if any */
12384     MakeRegisteredMove();
12385
12386     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12387         commentList[currentMove]
12388           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12389         DisplayComment(currentMove - 1, commentList[currentMove]);
12390     }
12391
12392     return retVal;
12393 }
12394
12395 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12396 int
12397 ReloadGame (int offset)
12398 {
12399     int gameNumber = lastLoadGameNumber + offset;
12400     if (lastLoadGameFP == NULL) {
12401         DisplayError(_("No game has been loaded yet"), 0);
12402         return FALSE;
12403     }
12404     if (gameNumber <= 0) {
12405         DisplayError(_("Can't back up any further"), 0);
12406         return FALSE;
12407     }
12408     if (cmailMsgLoaded) {
12409         return CmailLoadGame(lastLoadGameFP, gameNumber,
12410                              lastLoadGameTitle, lastLoadGameUseList);
12411     } else {
12412         return LoadGame(lastLoadGameFP, gameNumber,
12413                         lastLoadGameTitle, lastLoadGameUseList);
12414     }
12415 }
12416
12417 int keys[EmptySquare+1];
12418
12419 int
12420 PositionMatches (Board b1, Board b2)
12421 {
12422     int r, f, sum=0;
12423     switch(appData.searchMode) {
12424         case 1: return CompareWithRights(b1, b2);
12425         case 2:
12426             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12427                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12428             }
12429             return TRUE;
12430         case 3:
12431             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12432               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12433                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12434             }
12435             return sum==0;
12436         case 4:
12437             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12438                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12439             }
12440             return sum==0;
12441     }
12442     return TRUE;
12443 }
12444
12445 #define Q_PROMO  4
12446 #define Q_EP     3
12447 #define Q_BCASTL 2
12448 #define Q_WCASTL 1
12449
12450 int pieceList[256], quickBoard[256];
12451 ChessSquare pieceType[256] = { EmptySquare };
12452 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12453 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12454 int soughtTotal, turn;
12455 Boolean epOK, flipSearch;
12456
12457 typedef struct {
12458     unsigned char piece, to;
12459 } Move;
12460
12461 #define DSIZE (250000)
12462
12463 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12464 Move *moveDatabase = initialSpace;
12465 unsigned int movePtr, dataSize = DSIZE;
12466
12467 int
12468 MakePieceList (Board board, int *counts)
12469 {
12470     int r, f, n=Q_PROMO, total=0;
12471     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12472     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12473         int sq = f + (r<<4);
12474         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12475             quickBoard[sq] = ++n;
12476             pieceList[n] = sq;
12477             pieceType[n] = board[r][f];
12478             counts[board[r][f]]++;
12479             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12480             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12481             total++;
12482         }
12483     }
12484     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12485     return total;
12486 }
12487
12488 void
12489 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12490 {
12491     int sq = fromX + (fromY<<4);
12492     int piece = quickBoard[sq], rook;
12493     quickBoard[sq] = 0;
12494     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12495     if(piece == pieceList[1] && fromY == toY) {
12496       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12497         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12498         moveDatabase[movePtr++].piece = Q_WCASTL;
12499         quickBoard[sq] = piece;
12500         piece = quickBoard[from]; quickBoard[from] = 0;
12501         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12502       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12503         quickBoard[sq] = 0; // remove Rook
12504         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12505         moveDatabase[movePtr++].piece = Q_WCASTL;
12506         quickBoard[sq] = pieceList[1]; // put King
12507         piece = rook;
12508         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12509       }
12510     } else
12511     if(piece == pieceList[2] && fromY == toY) {
12512       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12513         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12514         moveDatabase[movePtr++].piece = Q_BCASTL;
12515         quickBoard[sq] = piece;
12516         piece = quickBoard[from]; quickBoard[from] = 0;
12517         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12518       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12519         quickBoard[sq] = 0; // remove Rook
12520         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12521         moveDatabase[movePtr++].piece = Q_BCASTL;
12522         quickBoard[sq] = pieceList[2]; // put King
12523         piece = rook;
12524         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12525       }
12526     } else
12527     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12528         quickBoard[(fromY<<4)+toX] = 0;
12529         moveDatabase[movePtr].piece = Q_EP;
12530         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12531         moveDatabase[movePtr].to = sq;
12532     } else
12533     if(promoPiece != pieceType[piece]) {
12534         moveDatabase[movePtr++].piece = Q_PROMO;
12535         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12536     }
12537     moveDatabase[movePtr].piece = piece;
12538     quickBoard[sq] = piece;
12539     movePtr++;
12540 }
12541
12542 int
12543 PackGame (Board board)
12544 {
12545     Move *newSpace = NULL;
12546     moveDatabase[movePtr].piece = 0; // terminate previous game
12547     if(movePtr > dataSize) {
12548         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12549         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12550         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12551         if(newSpace) {
12552             int i;
12553             Move *p = moveDatabase, *q = newSpace;
12554             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12555             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12556             moveDatabase = newSpace;
12557         } else { // calloc failed, we must be out of memory. Too bad...
12558             dataSize = 0; // prevent calloc events for all subsequent games
12559             return 0;     // and signal this one isn't cached
12560         }
12561     }
12562     movePtr++;
12563     MakePieceList(board, counts);
12564     return movePtr;
12565 }
12566
12567 int
12568 QuickCompare (Board board, int *minCounts, int *maxCounts)
12569 {   // compare according to search mode
12570     int r, f;
12571     switch(appData.searchMode)
12572     {
12573       case 1: // exact position match
12574         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12575         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12576             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12577         }
12578         break;
12579       case 2: // can have extra material on empty squares
12580         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12581             if(board[r][f] == EmptySquare) continue;
12582             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12583         }
12584         break;
12585       case 3: // material with exact Pawn structure
12586         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12587             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12588             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12589         } // fall through to material comparison
12590       case 4: // exact material
12591         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12592         break;
12593       case 6: // material range with given imbalance
12594         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12595         // fall through to range comparison
12596       case 5: // material range
12597         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12598     }
12599     return TRUE;
12600 }
12601
12602 int
12603 QuickScan (Board board, Move *move)
12604 {   // reconstruct game,and compare all positions in it
12605     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12606     do {
12607         int piece = move->piece;
12608         int to = move->to, from = pieceList[piece];
12609         if(found < 0) { // if already found just scan to game end for final piece count
12610           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12611            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12612            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12613                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12614             ) {
12615             static int lastCounts[EmptySquare+1];
12616             int i;
12617             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12618             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12619           } else stretch = 0;
12620           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12621           if(found >= 0 && !appData.minPieces) return found;
12622         }
12623         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12624           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12625           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12626             piece = (++move)->piece;
12627             from = pieceList[piece];
12628             counts[pieceType[piece]]--;
12629             pieceType[piece] = (ChessSquare) move->to;
12630             counts[move->to]++;
12631           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12632             counts[pieceType[quickBoard[to]]]--;
12633             quickBoard[to] = 0; total--;
12634             move++;
12635             continue;
12636           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12637             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12638             from  = pieceList[piece]; // so this must be King
12639             quickBoard[from] = 0;
12640             pieceList[piece] = to;
12641             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12642             quickBoard[from] = 0; // rook
12643             quickBoard[to] = piece;
12644             to = move->to; piece = move->piece;
12645             goto aftercastle;
12646           }
12647         }
12648         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12649         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12650         quickBoard[from] = 0;
12651       aftercastle:
12652         quickBoard[to] = piece;
12653         pieceList[piece] = to;
12654         cnt++; turn ^= 3;
12655         move++;
12656     } while(1);
12657 }
12658
12659 void
12660 InitSearch ()
12661 {
12662     int r, f;
12663     flipSearch = FALSE;
12664     CopyBoard(soughtBoard, boards[currentMove]);
12665     soughtTotal = MakePieceList(soughtBoard, maxSought);
12666     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12667     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12668     CopyBoard(reverseBoard, boards[currentMove]);
12669     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12670         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12671         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12672         reverseBoard[r][f] = piece;
12673     }
12674     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12675     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12676     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12677                  || (boards[currentMove][CASTLING][2] == NoRights ||
12678                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12679                  && (boards[currentMove][CASTLING][5] == NoRights ||
12680                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12681       ) {
12682         flipSearch = TRUE;
12683         CopyBoard(flipBoard, soughtBoard);
12684         CopyBoard(rotateBoard, reverseBoard);
12685         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12686             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12687             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12688         }
12689     }
12690     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12691     if(appData.searchMode >= 5) {
12692         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12693         MakePieceList(soughtBoard, minSought);
12694         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12695     }
12696     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12697         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12698 }
12699
12700 GameInfo dummyInfo;
12701 static int creatingBook;
12702
12703 int
12704 GameContainsPosition (FILE *f, ListGame *lg)
12705 {
12706     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12707     int fromX, fromY, toX, toY;
12708     char promoChar;
12709     static int initDone=FALSE;
12710
12711     // weed out games based on numerical tag comparison
12712     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12713     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12714     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12715     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12716     if(!initDone) {
12717         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12718         initDone = TRUE;
12719     }
12720     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12721     else CopyBoard(boards[scratch], initialPosition); // default start position
12722     if(lg->moves) {
12723         turn = btm + 1;
12724         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12725         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12726     }
12727     if(btm) plyNr++;
12728     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12729     fseek(f, lg->offset, 0);
12730     yynewfile(f);
12731     while(1) {
12732         yyboardindex = scratch;
12733         quickFlag = plyNr+1;
12734         next = Myylex();
12735         quickFlag = 0;
12736         switch(next) {
12737             case PGNTag:
12738                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12739             default:
12740                 continue;
12741
12742             case XBoardGame:
12743             case GNUChessGame:
12744                 if(plyNr) return -1; // after we have seen moves, this is for new game
12745               continue;
12746
12747             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12748             case ImpossibleMove:
12749             case WhiteWins: // game ends here with these four
12750             case BlackWins:
12751             case GameIsDrawn:
12752             case GameUnfinished:
12753                 return -1;
12754
12755             case IllegalMove:
12756                 if(appData.testLegality) return -1;
12757             case WhiteCapturesEnPassant:
12758             case BlackCapturesEnPassant:
12759             case WhitePromotion:
12760             case BlackPromotion:
12761             case WhiteNonPromotion:
12762             case BlackNonPromotion:
12763             case NormalMove:
12764             case FirstLeg:
12765             case WhiteKingSideCastle:
12766             case WhiteQueenSideCastle:
12767             case BlackKingSideCastle:
12768             case BlackQueenSideCastle:
12769             case WhiteKingSideCastleWild:
12770             case WhiteQueenSideCastleWild:
12771             case BlackKingSideCastleWild:
12772             case BlackQueenSideCastleWild:
12773             case WhiteHSideCastleFR:
12774             case WhiteASideCastleFR:
12775             case BlackHSideCastleFR:
12776             case BlackASideCastleFR:
12777                 fromX = currentMoveString[0] - AAA;
12778                 fromY = currentMoveString[1] - ONE;
12779                 toX = currentMoveString[2] - AAA;
12780                 toY = currentMoveString[3] - ONE;
12781                 promoChar = currentMoveString[4];
12782                 break;
12783             case WhiteDrop:
12784             case BlackDrop:
12785                 fromX = next == WhiteDrop ?
12786                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12787                   (int) CharToPiece(ToLower(currentMoveString[0]));
12788                 fromY = DROP_RANK;
12789                 toX = currentMoveString[2] - AAA;
12790                 toY = currentMoveString[3] - ONE;
12791                 promoChar = 0;
12792                 break;
12793         }
12794         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12795         plyNr++;
12796         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12797         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12798         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12799         if(appData.findMirror) {
12800             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12801             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12802         }
12803     }
12804 }
12805
12806 /* Load the nth game from open file f */
12807 int
12808 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12809 {
12810     ChessMove cm;
12811     char buf[MSG_SIZ];
12812     int gn = gameNumber;
12813     ListGame *lg = NULL;
12814     int numPGNTags = 0;
12815     int err, pos = -1;
12816     GameMode oldGameMode;
12817     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12818     char oldName[MSG_SIZ];
12819
12820     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12821
12822     if (appData.debugMode)
12823         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12824
12825     if (gameMode == Training )
12826         SetTrainingModeOff();
12827
12828     oldGameMode = gameMode;
12829     if (gameMode != BeginningOfGame) {
12830       Reset(FALSE, TRUE);
12831     }
12832     killX = killY = -1; // [HGM] lion: in case we did not Reset
12833
12834     gameFileFP = f;
12835     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12836         fclose(lastLoadGameFP);
12837     }
12838
12839     if (useList) {
12840         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12841
12842         if (lg) {
12843             fseek(f, lg->offset, 0);
12844             GameListHighlight(gameNumber);
12845             pos = lg->position;
12846             gn = 1;
12847         }
12848         else {
12849             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12850               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12851             else
12852             DisplayError(_("Game number out of range"), 0);
12853             return FALSE;
12854         }
12855     } else {
12856         GameListDestroy();
12857         if (fseek(f, 0, 0) == -1) {
12858             if (f == lastLoadGameFP ?
12859                 gameNumber == lastLoadGameNumber + 1 :
12860                 gameNumber == 1) {
12861                 gn = 1;
12862             } else {
12863                 DisplayError(_("Can't seek on game file"), 0);
12864                 return FALSE;
12865             }
12866         }
12867     }
12868     lastLoadGameFP = f;
12869     lastLoadGameNumber = gameNumber;
12870     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12871     lastLoadGameUseList = useList;
12872
12873     yynewfile(f);
12874
12875     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12876       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12877                 lg->gameInfo.black);
12878             DisplayTitle(buf);
12879     } else if (*title != NULLCHAR) {
12880         if (gameNumber > 1) {
12881           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12882             DisplayTitle(buf);
12883         } else {
12884             DisplayTitle(title);
12885         }
12886     }
12887
12888     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12889         gameMode = PlayFromGameFile;
12890         ModeHighlight();
12891     }
12892
12893     currentMove = forwardMostMove = backwardMostMove = 0;
12894     CopyBoard(boards[0], initialPosition);
12895     StopClocks();
12896
12897     /*
12898      * Skip the first gn-1 games in the file.
12899      * Also skip over anything that precedes an identifiable
12900      * start of game marker, to avoid being confused by
12901      * garbage at the start of the file.  Currently
12902      * recognized start of game markers are the move number "1",
12903      * the pattern "gnuchess .* game", the pattern
12904      * "^[#;%] [^ ]* game file", and a PGN tag block.
12905      * A game that starts with one of the latter two patterns
12906      * will also have a move number 1, possibly
12907      * following a position diagram.
12908      * 5-4-02: Let's try being more lenient and allowing a game to
12909      * start with an unnumbered move.  Does that break anything?
12910      */
12911     cm = lastLoadGameStart = EndOfFile;
12912     while (gn > 0) {
12913         yyboardindex = forwardMostMove;
12914         cm = (ChessMove) Myylex();
12915         switch (cm) {
12916           case EndOfFile:
12917             if (cmailMsgLoaded) {
12918                 nCmailGames = CMAIL_MAX_GAMES - gn;
12919             } else {
12920                 Reset(TRUE, TRUE);
12921                 DisplayError(_("Game not found in file"), 0);
12922             }
12923             return FALSE;
12924
12925           case GNUChessGame:
12926           case XBoardGame:
12927             gn--;
12928             lastLoadGameStart = cm;
12929             break;
12930
12931           case MoveNumberOne:
12932             switch (lastLoadGameStart) {
12933               case GNUChessGame:
12934               case XBoardGame:
12935               case PGNTag:
12936                 break;
12937               case MoveNumberOne:
12938               case EndOfFile:
12939                 gn--;           /* count this game */
12940                 lastLoadGameStart = cm;
12941                 break;
12942               default:
12943                 /* impossible */
12944                 break;
12945             }
12946             break;
12947
12948           case PGNTag:
12949             switch (lastLoadGameStart) {
12950               case GNUChessGame:
12951               case PGNTag:
12952               case MoveNumberOne:
12953               case EndOfFile:
12954                 gn--;           /* count this game */
12955                 lastLoadGameStart = cm;
12956                 break;
12957               case XBoardGame:
12958                 lastLoadGameStart = cm; /* game counted already */
12959                 break;
12960               default:
12961                 /* impossible */
12962                 break;
12963             }
12964             if (gn > 0) {
12965                 do {
12966                     yyboardindex = forwardMostMove;
12967                     cm = (ChessMove) Myylex();
12968                 } while (cm == PGNTag || cm == Comment);
12969             }
12970             break;
12971
12972           case WhiteWins:
12973           case BlackWins:
12974           case GameIsDrawn:
12975             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12976                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12977                     != CMAIL_OLD_RESULT) {
12978                     nCmailResults ++ ;
12979                     cmailResult[  CMAIL_MAX_GAMES
12980                                 - gn - 1] = CMAIL_OLD_RESULT;
12981                 }
12982             }
12983             break;
12984
12985           case NormalMove:
12986           case FirstLeg:
12987             /* Only a NormalMove can be at the start of a game
12988              * without a position diagram. */
12989             if (lastLoadGameStart == EndOfFile ) {
12990               gn--;
12991               lastLoadGameStart = MoveNumberOne;
12992             }
12993             break;
12994
12995           default:
12996             break;
12997         }
12998     }
12999
13000     if (appData.debugMode)
13001       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13002
13003     if (cm == XBoardGame) {
13004         /* Skip any header junk before position diagram and/or move 1 */
13005         for (;;) {
13006             yyboardindex = forwardMostMove;
13007             cm = (ChessMove) Myylex();
13008
13009             if (cm == EndOfFile ||
13010                 cm == GNUChessGame || cm == XBoardGame) {
13011                 /* Empty game; pretend end-of-file and handle later */
13012                 cm = EndOfFile;
13013                 break;
13014             }
13015
13016             if (cm == MoveNumberOne || cm == PositionDiagram ||
13017                 cm == PGNTag || cm == Comment)
13018               break;
13019         }
13020     } else if (cm == GNUChessGame) {
13021         if (gameInfo.event != NULL) {
13022             free(gameInfo.event);
13023         }
13024         gameInfo.event = StrSave(yy_text);
13025     }
13026
13027     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13028     while (cm == PGNTag) {
13029         if (appData.debugMode)
13030           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13031         err = ParsePGNTag(yy_text, &gameInfo);
13032         if (!err) numPGNTags++;
13033
13034         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13035         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13036             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13037             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13038             InitPosition(TRUE);
13039             oldVariant = gameInfo.variant;
13040             if (appData.debugMode)
13041               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13042         }
13043
13044
13045         if (gameInfo.fen != NULL) {
13046           Board initial_position;
13047           startedFromSetupPosition = TRUE;
13048           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13049             Reset(TRUE, TRUE);
13050             DisplayError(_("Bad FEN position in file"), 0);
13051             return FALSE;
13052           }
13053           CopyBoard(boards[0], initial_position);
13054           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13055             CopyBoard(initialPosition, initial_position);
13056           if (blackPlaysFirst) {
13057             currentMove = forwardMostMove = backwardMostMove = 1;
13058             CopyBoard(boards[1], initial_position);
13059             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13060             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13061             timeRemaining[0][1] = whiteTimeRemaining;
13062             timeRemaining[1][1] = blackTimeRemaining;
13063             if (commentList[0] != NULL) {
13064               commentList[1] = commentList[0];
13065               commentList[0] = NULL;
13066             }
13067           } else {
13068             currentMove = forwardMostMove = backwardMostMove = 0;
13069           }
13070           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13071           {   int i;
13072               initialRulePlies = FENrulePlies;
13073               for( i=0; i< nrCastlingRights; i++ )
13074                   initialRights[i] = initial_position[CASTLING][i];
13075           }
13076           yyboardindex = forwardMostMove;
13077           free(gameInfo.fen);
13078           gameInfo.fen = NULL;
13079         }
13080
13081         yyboardindex = forwardMostMove;
13082         cm = (ChessMove) Myylex();
13083
13084         /* Handle comments interspersed among the tags */
13085         while (cm == Comment) {
13086             char *p;
13087             if (appData.debugMode)
13088               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13089             p = yy_text;
13090             AppendComment(currentMove, p, FALSE);
13091             yyboardindex = forwardMostMove;
13092             cm = (ChessMove) Myylex();
13093         }
13094     }
13095
13096     /* don't rely on existence of Event tag since if game was
13097      * pasted from clipboard the Event tag may not exist
13098      */
13099     if (numPGNTags > 0){
13100         char *tags;
13101         if (gameInfo.variant == VariantNormal) {
13102           VariantClass v = StringToVariant(gameInfo.event);
13103           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13104           if(v < VariantShogi) gameInfo.variant = v;
13105         }
13106         if (!matchMode) {
13107           if( appData.autoDisplayTags ) {
13108             tags = PGNTags(&gameInfo);
13109             TagsPopUp(tags, CmailMsg());
13110             free(tags);
13111           }
13112         }
13113     } else {
13114         /* Make something up, but don't display it now */
13115         SetGameInfo();
13116         TagsPopDown();
13117     }
13118
13119     if (cm == PositionDiagram) {
13120         int i, j;
13121         char *p;
13122         Board initial_position;
13123
13124         if (appData.debugMode)
13125           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13126
13127         if (!startedFromSetupPosition) {
13128             p = yy_text;
13129             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13130               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13131                 switch (*p) {
13132                   case '{':
13133                   case '[':
13134                   case '-':
13135                   case ' ':
13136                   case '\t':
13137                   case '\n':
13138                   case '\r':
13139                     break;
13140                   default:
13141                     initial_position[i][j++] = CharToPiece(*p);
13142                     break;
13143                 }
13144             while (*p == ' ' || *p == '\t' ||
13145                    *p == '\n' || *p == '\r') p++;
13146
13147             if (strncmp(p, "black", strlen("black"))==0)
13148               blackPlaysFirst = TRUE;
13149             else
13150               blackPlaysFirst = FALSE;
13151             startedFromSetupPosition = TRUE;
13152
13153             CopyBoard(boards[0], initial_position);
13154             if (blackPlaysFirst) {
13155                 currentMove = forwardMostMove = backwardMostMove = 1;
13156                 CopyBoard(boards[1], initial_position);
13157                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13158                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13159                 timeRemaining[0][1] = whiteTimeRemaining;
13160                 timeRemaining[1][1] = blackTimeRemaining;
13161                 if (commentList[0] != NULL) {
13162                     commentList[1] = commentList[0];
13163                     commentList[0] = NULL;
13164                 }
13165             } else {
13166                 currentMove = forwardMostMove = backwardMostMove = 0;
13167             }
13168         }
13169         yyboardindex = forwardMostMove;
13170         cm = (ChessMove) Myylex();
13171     }
13172
13173   if(!creatingBook) {
13174     if (first.pr == NoProc) {
13175         StartChessProgram(&first);
13176     }
13177     InitChessProgram(&first, FALSE);
13178     if(gameInfo.variant == VariantUnknown && *oldName) {
13179         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13180         gameInfo.variant = v;
13181     }
13182     SendToProgram("force\n", &first);
13183     if (startedFromSetupPosition) {
13184         SendBoard(&first, forwardMostMove);
13185     if (appData.debugMode) {
13186         fprintf(debugFP, "Load Game\n");
13187     }
13188         DisplayBothClocks();
13189     }
13190   }
13191
13192     /* [HGM] server: flag to write setup moves in broadcast file as one */
13193     loadFlag = appData.suppressLoadMoves;
13194
13195     while (cm == Comment) {
13196         char *p;
13197         if (appData.debugMode)
13198           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13199         p = yy_text;
13200         AppendComment(currentMove, p, FALSE);
13201         yyboardindex = forwardMostMove;
13202         cm = (ChessMove) Myylex();
13203     }
13204
13205     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13206         cm == WhiteWins || cm == BlackWins ||
13207         cm == GameIsDrawn || cm == GameUnfinished) {
13208         DisplayMessage("", _("No moves in game"));
13209         if (cmailMsgLoaded) {
13210             if (appData.debugMode)
13211               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13212             ClearHighlights();
13213             flipView = FALSE;
13214         }
13215         DrawPosition(FALSE, boards[currentMove]);
13216         DisplayBothClocks();
13217         gameMode = EditGame;
13218         ModeHighlight();
13219         gameFileFP = NULL;
13220         cmailOldMove = 0;
13221         return TRUE;
13222     }
13223
13224     // [HGM] PV info: routine tests if comment empty
13225     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13226         DisplayComment(currentMove - 1, commentList[currentMove]);
13227     }
13228     if (!matchMode && appData.timeDelay != 0)
13229       DrawPosition(FALSE, boards[currentMove]);
13230
13231     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13232       programStats.ok_to_send = 1;
13233     }
13234
13235     /* if the first token after the PGN tags is a move
13236      * and not move number 1, retrieve it from the parser
13237      */
13238     if (cm != MoveNumberOne)
13239         LoadGameOneMove(cm);
13240
13241     /* load the remaining moves from the file */
13242     while (LoadGameOneMove(EndOfFile)) {
13243       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13244       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13245     }
13246
13247     /* rewind to the start of the game */
13248     currentMove = backwardMostMove;
13249
13250     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13251
13252     if (oldGameMode == AnalyzeFile) {
13253       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13254       AnalyzeFileEvent();
13255     } else
13256     if (oldGameMode == AnalyzeMode) {
13257       AnalyzeFileEvent();
13258     }
13259
13260     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13261         long int w, b; // [HGM] adjourn: restore saved clock times
13262         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13263         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13264             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13265             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13266         }
13267     }
13268
13269     if(creatingBook) return TRUE;
13270     if (!matchMode && pos > 0) {
13271         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13272     } else
13273     if (matchMode || appData.timeDelay == 0) {
13274       ToEndEvent();
13275     } else if (appData.timeDelay > 0) {
13276       AutoPlayGameLoop();
13277     }
13278
13279     if (appData.debugMode)
13280         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13281
13282     loadFlag = 0; /* [HGM] true game starts */
13283     return TRUE;
13284 }
13285
13286 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13287 int
13288 ReloadPosition (int offset)
13289 {
13290     int positionNumber = lastLoadPositionNumber + offset;
13291     if (lastLoadPositionFP == NULL) {
13292         DisplayError(_("No position has been loaded yet"), 0);
13293         return FALSE;
13294     }
13295     if (positionNumber <= 0) {
13296         DisplayError(_("Can't back up any further"), 0);
13297         return FALSE;
13298     }
13299     return LoadPosition(lastLoadPositionFP, positionNumber,
13300                         lastLoadPositionTitle);
13301 }
13302
13303 /* Load the nth position from the given file */
13304 int
13305 LoadPositionFromFile (char *filename, int n, char *title)
13306 {
13307     FILE *f;
13308     char buf[MSG_SIZ];
13309
13310     if (strcmp(filename, "-") == 0) {
13311         return LoadPosition(stdin, n, "stdin");
13312     } else {
13313         f = fopen(filename, "rb");
13314         if (f == NULL) {
13315             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13316             DisplayError(buf, errno);
13317             return FALSE;
13318         } else {
13319             return LoadPosition(f, n, title);
13320         }
13321     }
13322 }
13323
13324 /* Load the nth position from the given open file, and close it */
13325 int
13326 LoadPosition (FILE *f, int positionNumber, char *title)
13327 {
13328     char *p, line[MSG_SIZ];
13329     Board initial_position;
13330     int i, j, fenMode, pn;
13331
13332     if (gameMode == Training )
13333         SetTrainingModeOff();
13334
13335     if (gameMode != BeginningOfGame) {
13336         Reset(FALSE, TRUE);
13337     }
13338     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13339         fclose(lastLoadPositionFP);
13340     }
13341     if (positionNumber == 0) positionNumber = 1;
13342     lastLoadPositionFP = f;
13343     lastLoadPositionNumber = positionNumber;
13344     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13345     if (first.pr == NoProc && !appData.noChessProgram) {
13346       StartChessProgram(&first);
13347       InitChessProgram(&first, FALSE);
13348     }
13349     pn = positionNumber;
13350     if (positionNumber < 0) {
13351         /* Negative position number means to seek to that byte offset */
13352         if (fseek(f, -positionNumber, 0) == -1) {
13353             DisplayError(_("Can't seek on position file"), 0);
13354             return FALSE;
13355         };
13356         pn = 1;
13357     } else {
13358         if (fseek(f, 0, 0) == -1) {
13359             if (f == lastLoadPositionFP ?
13360                 positionNumber == lastLoadPositionNumber + 1 :
13361                 positionNumber == 1) {
13362                 pn = 1;
13363             } else {
13364                 DisplayError(_("Can't seek on position file"), 0);
13365                 return FALSE;
13366             }
13367         }
13368     }
13369     /* See if this file is FEN or old-style xboard */
13370     if (fgets(line, MSG_SIZ, f) == NULL) {
13371         DisplayError(_("Position not found in file"), 0);
13372         return FALSE;
13373     }
13374     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13375     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13376
13377     if (pn >= 2) {
13378         if (fenMode || line[0] == '#') pn--;
13379         while (pn > 0) {
13380             /* skip positions before number pn */
13381             if (fgets(line, MSG_SIZ, f) == NULL) {
13382                 Reset(TRUE, TRUE);
13383                 DisplayError(_("Position not found in file"), 0);
13384                 return FALSE;
13385             }
13386             if (fenMode || line[0] == '#') pn--;
13387         }
13388     }
13389
13390     if (fenMode) {
13391         char *p;
13392         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13393             DisplayError(_("Bad FEN position in file"), 0);
13394             return FALSE;
13395         }
13396         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13397             sscanf(p+3, "%s", bestMove);
13398         } else *bestMove = NULLCHAR;
13399     } else {
13400         (void) fgets(line, MSG_SIZ, f);
13401         (void) fgets(line, MSG_SIZ, f);
13402
13403         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13404             (void) fgets(line, MSG_SIZ, f);
13405             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13406                 if (*p == ' ')
13407                   continue;
13408                 initial_position[i][j++] = CharToPiece(*p);
13409             }
13410         }
13411
13412         blackPlaysFirst = FALSE;
13413         if (!feof(f)) {
13414             (void) fgets(line, MSG_SIZ, f);
13415             if (strncmp(line, "black", strlen("black"))==0)
13416               blackPlaysFirst = TRUE;
13417         }
13418     }
13419     startedFromSetupPosition = TRUE;
13420
13421     CopyBoard(boards[0], initial_position);
13422     if (blackPlaysFirst) {
13423         currentMove = forwardMostMove = backwardMostMove = 1;
13424         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13425         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13426         CopyBoard(boards[1], initial_position);
13427         DisplayMessage("", _("Black to play"));
13428     } else {
13429         currentMove = forwardMostMove = backwardMostMove = 0;
13430         DisplayMessage("", _("White to play"));
13431     }
13432     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13433     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13434         SendToProgram("force\n", &first);
13435         SendBoard(&first, forwardMostMove);
13436     }
13437     if (appData.debugMode) {
13438 int i, j;
13439   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13440   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13441         fprintf(debugFP, "Load Position\n");
13442     }
13443
13444     if (positionNumber > 1) {
13445       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13446         DisplayTitle(line);
13447     } else {
13448         DisplayTitle(title);
13449     }
13450     gameMode = EditGame;
13451     ModeHighlight();
13452     ResetClocks();
13453     timeRemaining[0][1] = whiteTimeRemaining;
13454     timeRemaining[1][1] = blackTimeRemaining;
13455     DrawPosition(FALSE, boards[currentMove]);
13456
13457     return TRUE;
13458 }
13459
13460
13461 void
13462 CopyPlayerNameIntoFileName (char **dest, char *src)
13463 {
13464     while (*src != NULLCHAR && *src != ',') {
13465         if (*src == ' ') {
13466             *(*dest)++ = '_';
13467             src++;
13468         } else {
13469             *(*dest)++ = *src++;
13470         }
13471     }
13472 }
13473
13474 char *
13475 DefaultFileName (char *ext)
13476 {
13477     static char def[MSG_SIZ];
13478     char *p;
13479
13480     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13481         p = def;
13482         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13483         *p++ = '-';
13484         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13485         *p++ = '.';
13486         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13487     } else {
13488         def[0] = NULLCHAR;
13489     }
13490     return def;
13491 }
13492
13493 /* Save the current game to the given file */
13494 int
13495 SaveGameToFile (char *filename, int append)
13496 {
13497     FILE *f;
13498     char buf[MSG_SIZ];
13499     int result, i, t,tot=0;
13500
13501     if (strcmp(filename, "-") == 0) {
13502         return SaveGame(stdout, 0, NULL);
13503     } else {
13504         for(i=0; i<10; i++) { // upto 10 tries
13505              f = fopen(filename, append ? "a" : "w");
13506              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13507              if(f || errno != 13) break;
13508              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13509              tot += t;
13510         }
13511         if (f == NULL) {
13512             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13513             DisplayError(buf, errno);
13514             return FALSE;
13515         } else {
13516             safeStrCpy(buf, lastMsg, MSG_SIZ);
13517             DisplayMessage(_("Waiting for access to save file"), "");
13518             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13519             DisplayMessage(_("Saving game"), "");
13520             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13521             result = SaveGame(f, 0, NULL);
13522             DisplayMessage(buf, "");
13523             return result;
13524         }
13525     }
13526 }
13527
13528 char *
13529 SavePart (char *str)
13530 {
13531     static char buf[MSG_SIZ];
13532     char *p;
13533
13534     p = strchr(str, ' ');
13535     if (p == NULL) return str;
13536     strncpy(buf, str, p - str);
13537     buf[p - str] = NULLCHAR;
13538     return buf;
13539 }
13540
13541 #define PGN_MAX_LINE 75
13542
13543 #define PGN_SIDE_WHITE  0
13544 #define PGN_SIDE_BLACK  1
13545
13546 static int
13547 FindFirstMoveOutOfBook (int side)
13548 {
13549     int result = -1;
13550
13551     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13552         int index = backwardMostMove;
13553         int has_book_hit = 0;
13554
13555         if( (index % 2) != side ) {
13556             index++;
13557         }
13558
13559         while( index < forwardMostMove ) {
13560             /* Check to see if engine is in book */
13561             int depth = pvInfoList[index].depth;
13562             int score = pvInfoList[index].score;
13563             int in_book = 0;
13564
13565             if( depth <= 2 ) {
13566                 in_book = 1;
13567             }
13568             else if( score == 0 && depth == 63 ) {
13569                 in_book = 1; /* Zappa */
13570             }
13571             else if( score == 2 && depth == 99 ) {
13572                 in_book = 1; /* Abrok */
13573             }
13574
13575             has_book_hit += in_book;
13576
13577             if( ! in_book ) {
13578                 result = index;
13579
13580                 break;
13581             }
13582
13583             index += 2;
13584         }
13585     }
13586
13587     return result;
13588 }
13589
13590 void
13591 GetOutOfBookInfo (char * buf)
13592 {
13593     int oob[2];
13594     int i;
13595     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13596
13597     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13598     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13599
13600     *buf = '\0';
13601
13602     if( oob[0] >= 0 || oob[1] >= 0 ) {
13603         for( i=0; i<2; i++ ) {
13604             int idx = oob[i];
13605
13606             if( idx >= 0 ) {
13607                 if( i > 0 && oob[0] >= 0 ) {
13608                     strcat( buf, "   " );
13609                 }
13610
13611                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13612                 sprintf( buf+strlen(buf), "%s%.2f",
13613                     pvInfoList[idx].score >= 0 ? "+" : "",
13614                     pvInfoList[idx].score / 100.0 );
13615             }
13616         }
13617     }
13618 }
13619
13620 /* Save game in PGN style */
13621 static void
13622 SaveGamePGN2 (FILE *f)
13623 {
13624     int i, offset, linelen, newblock;
13625 //    char *movetext;
13626     char numtext[32];
13627     int movelen, numlen, blank;
13628     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13629
13630     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13631
13632     PrintPGNTags(f, &gameInfo);
13633
13634     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13635
13636     if (backwardMostMove > 0 || startedFromSetupPosition) {
13637         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13638         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13639         fprintf(f, "\n{--------------\n");
13640         PrintPosition(f, backwardMostMove);
13641         fprintf(f, "--------------}\n");
13642         free(fen);
13643     }
13644     else {
13645         /* [AS] Out of book annotation */
13646         if( appData.saveOutOfBookInfo ) {
13647             char buf[64];
13648
13649             GetOutOfBookInfo( buf );
13650
13651             if( buf[0] != '\0' ) {
13652                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13653             }
13654         }
13655
13656         fprintf(f, "\n");
13657     }
13658
13659     i = backwardMostMove;
13660     linelen = 0;
13661     newblock = TRUE;
13662
13663     while (i < forwardMostMove) {
13664         /* Print comments preceding this move */
13665         if (commentList[i] != NULL) {
13666             if (linelen > 0) fprintf(f, "\n");
13667             fprintf(f, "%s", commentList[i]);
13668             linelen = 0;
13669             newblock = TRUE;
13670         }
13671
13672         /* Format move number */
13673         if ((i % 2) == 0)
13674           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13675         else
13676           if (newblock)
13677             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13678           else
13679             numtext[0] = NULLCHAR;
13680
13681         numlen = strlen(numtext);
13682         newblock = FALSE;
13683
13684         /* Print move number */
13685         blank = linelen > 0 && numlen > 0;
13686         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13687             fprintf(f, "\n");
13688             linelen = 0;
13689             blank = 0;
13690         }
13691         if (blank) {
13692             fprintf(f, " ");
13693             linelen++;
13694         }
13695         fprintf(f, "%s", numtext);
13696         linelen += numlen;
13697
13698         /* Get move */
13699         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13700         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13701
13702         /* Print move */
13703         blank = linelen > 0 && movelen > 0;
13704         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13705             fprintf(f, "\n");
13706             linelen = 0;
13707             blank = 0;
13708         }
13709         if (blank) {
13710             fprintf(f, " ");
13711             linelen++;
13712         }
13713         fprintf(f, "%s", move_buffer);
13714         linelen += movelen;
13715
13716         /* [AS] Add PV info if present */
13717         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13718             /* [HGM] add time */
13719             char buf[MSG_SIZ]; int seconds;
13720
13721             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13722
13723             if( seconds <= 0)
13724               buf[0] = 0;
13725             else
13726               if( seconds < 30 )
13727                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13728               else
13729                 {
13730                   seconds = (seconds + 4)/10; // round to full seconds
13731                   if( seconds < 60 )
13732                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13733                   else
13734                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13735                 }
13736
13737             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13738                       pvInfoList[i].score >= 0 ? "+" : "",
13739                       pvInfoList[i].score / 100.0,
13740                       pvInfoList[i].depth,
13741                       buf );
13742
13743             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13744
13745             /* Print score/depth */
13746             blank = linelen > 0 && movelen > 0;
13747             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13748                 fprintf(f, "\n");
13749                 linelen = 0;
13750                 blank = 0;
13751             }
13752             if (blank) {
13753                 fprintf(f, " ");
13754                 linelen++;
13755             }
13756             fprintf(f, "%s", move_buffer);
13757             linelen += movelen;
13758         }
13759
13760         i++;
13761     }
13762
13763     /* Start a new line */
13764     if (linelen > 0) fprintf(f, "\n");
13765
13766     /* Print comments after last move */
13767     if (commentList[i] != NULL) {
13768         fprintf(f, "%s\n", commentList[i]);
13769     }
13770
13771     /* Print result */
13772     if (gameInfo.resultDetails != NULL &&
13773         gameInfo.resultDetails[0] != NULLCHAR) {
13774         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13775         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13776            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13777             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13778         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13779     } else {
13780         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13781     }
13782 }
13783
13784 /* Save game in PGN style and close the file */
13785 int
13786 SaveGamePGN (FILE *f)
13787 {
13788     SaveGamePGN2(f);
13789     fclose(f);
13790     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13791     return TRUE;
13792 }
13793
13794 /* Save game in old style and close the file */
13795 int
13796 SaveGameOldStyle (FILE *f)
13797 {
13798     int i, offset;
13799     time_t tm;
13800
13801     tm = time((time_t *) NULL);
13802
13803     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13804     PrintOpponents(f);
13805
13806     if (backwardMostMove > 0 || startedFromSetupPosition) {
13807         fprintf(f, "\n[--------------\n");
13808         PrintPosition(f, backwardMostMove);
13809         fprintf(f, "--------------]\n");
13810     } else {
13811         fprintf(f, "\n");
13812     }
13813
13814     i = backwardMostMove;
13815     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13816
13817     while (i < forwardMostMove) {
13818         if (commentList[i] != NULL) {
13819             fprintf(f, "[%s]\n", commentList[i]);
13820         }
13821
13822         if ((i % 2) == 1) {
13823             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13824             i++;
13825         } else {
13826             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13827             i++;
13828             if (commentList[i] != NULL) {
13829                 fprintf(f, "\n");
13830                 continue;
13831             }
13832             if (i >= forwardMostMove) {
13833                 fprintf(f, "\n");
13834                 break;
13835             }
13836             fprintf(f, "%s\n", parseList[i]);
13837             i++;
13838         }
13839     }
13840
13841     if (commentList[i] != NULL) {
13842         fprintf(f, "[%s]\n", commentList[i]);
13843     }
13844
13845     /* This isn't really the old style, but it's close enough */
13846     if (gameInfo.resultDetails != NULL &&
13847         gameInfo.resultDetails[0] != NULLCHAR) {
13848         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13849                 gameInfo.resultDetails);
13850     } else {
13851         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13852     }
13853
13854     fclose(f);
13855     return TRUE;
13856 }
13857
13858 /* Save the current game to open file f and close the file */
13859 int
13860 SaveGame (FILE *f, int dummy, char *dummy2)
13861 {
13862     if (gameMode == EditPosition) EditPositionDone(TRUE);
13863     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13864     if (appData.oldSaveStyle)
13865       return SaveGameOldStyle(f);
13866     else
13867       return SaveGamePGN(f);
13868 }
13869
13870 /* Save the current position to the given file */
13871 int
13872 SavePositionToFile (char *filename)
13873 {
13874     FILE *f;
13875     char buf[MSG_SIZ];
13876
13877     if (strcmp(filename, "-") == 0) {
13878         return SavePosition(stdout, 0, NULL);
13879     } else {
13880         f = fopen(filename, "a");
13881         if (f == NULL) {
13882             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13883             DisplayError(buf, errno);
13884             return FALSE;
13885         } else {
13886             safeStrCpy(buf, lastMsg, MSG_SIZ);
13887             DisplayMessage(_("Waiting for access to save file"), "");
13888             flock(fileno(f), LOCK_EX); // [HGM] lock
13889             DisplayMessage(_("Saving position"), "");
13890             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13891             SavePosition(f, 0, NULL);
13892             DisplayMessage(buf, "");
13893             return TRUE;
13894         }
13895     }
13896 }
13897
13898 /* Save the current position to the given open file and close the file */
13899 int
13900 SavePosition (FILE *f, int dummy, char *dummy2)
13901 {
13902     time_t tm;
13903     char *fen;
13904
13905     if (gameMode == EditPosition) EditPositionDone(TRUE);
13906     if (appData.oldSaveStyle) {
13907         tm = time((time_t *) NULL);
13908
13909         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13910         PrintOpponents(f);
13911         fprintf(f, "[--------------\n");
13912         PrintPosition(f, currentMove);
13913         fprintf(f, "--------------]\n");
13914     } else {
13915         fen = PositionToFEN(currentMove, NULL, 1);
13916         fprintf(f, "%s\n", fen);
13917         free(fen);
13918     }
13919     fclose(f);
13920     return TRUE;
13921 }
13922
13923 void
13924 ReloadCmailMsgEvent (int unregister)
13925 {
13926 #if !WIN32
13927     static char *inFilename = NULL;
13928     static char *outFilename;
13929     int i;
13930     struct stat inbuf, outbuf;
13931     int status;
13932
13933     /* Any registered moves are unregistered if unregister is set, */
13934     /* i.e. invoked by the signal handler */
13935     if (unregister) {
13936         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13937             cmailMoveRegistered[i] = FALSE;
13938             if (cmailCommentList[i] != NULL) {
13939                 free(cmailCommentList[i]);
13940                 cmailCommentList[i] = NULL;
13941             }
13942         }
13943         nCmailMovesRegistered = 0;
13944     }
13945
13946     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13947         cmailResult[i] = CMAIL_NOT_RESULT;
13948     }
13949     nCmailResults = 0;
13950
13951     if (inFilename == NULL) {
13952         /* Because the filenames are static they only get malloced once  */
13953         /* and they never get freed                                      */
13954         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13955         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13956
13957         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13958         sprintf(outFilename, "%s.out", appData.cmailGameName);
13959     }
13960
13961     status = stat(outFilename, &outbuf);
13962     if (status < 0) {
13963         cmailMailedMove = FALSE;
13964     } else {
13965         status = stat(inFilename, &inbuf);
13966         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13967     }
13968
13969     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13970        counts the games, notes how each one terminated, etc.
13971
13972        It would be nice to remove this kludge and instead gather all
13973        the information while building the game list.  (And to keep it
13974        in the game list nodes instead of having a bunch of fixed-size
13975        parallel arrays.)  Note this will require getting each game's
13976        termination from the PGN tags, as the game list builder does
13977        not process the game moves.  --mann
13978        */
13979     cmailMsgLoaded = TRUE;
13980     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13981
13982     /* Load first game in the file or popup game menu */
13983     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13984
13985 #endif /* !WIN32 */
13986     return;
13987 }
13988
13989 int
13990 RegisterMove ()
13991 {
13992     FILE *f;
13993     char string[MSG_SIZ];
13994
13995     if (   cmailMailedMove
13996         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13997         return TRUE;            /* Allow free viewing  */
13998     }
13999
14000     /* Unregister move to ensure that we don't leave RegisterMove        */
14001     /* with the move registered when the conditions for registering no   */
14002     /* longer hold                                                       */
14003     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14004         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14005         nCmailMovesRegistered --;
14006
14007         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14008           {
14009               free(cmailCommentList[lastLoadGameNumber - 1]);
14010               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14011           }
14012     }
14013
14014     if (cmailOldMove == -1) {
14015         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14016         return FALSE;
14017     }
14018
14019     if (currentMove > cmailOldMove + 1) {
14020         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14021         return FALSE;
14022     }
14023
14024     if (currentMove < cmailOldMove) {
14025         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14026         return FALSE;
14027     }
14028
14029     if (forwardMostMove > currentMove) {
14030         /* Silently truncate extra moves */
14031         TruncateGame();
14032     }
14033
14034     if (   (currentMove == cmailOldMove + 1)
14035         || (   (currentMove == cmailOldMove)
14036             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14037                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14038         if (gameInfo.result != GameUnfinished) {
14039             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14040         }
14041
14042         if (commentList[currentMove] != NULL) {
14043             cmailCommentList[lastLoadGameNumber - 1]
14044               = StrSave(commentList[currentMove]);
14045         }
14046         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14047
14048         if (appData.debugMode)
14049           fprintf(debugFP, "Saving %s for game %d\n",
14050                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14051
14052         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14053
14054         f = fopen(string, "w");
14055         if (appData.oldSaveStyle) {
14056             SaveGameOldStyle(f); /* also closes the file */
14057
14058             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14059             f = fopen(string, "w");
14060             SavePosition(f, 0, NULL); /* also closes the file */
14061         } else {
14062             fprintf(f, "{--------------\n");
14063             PrintPosition(f, currentMove);
14064             fprintf(f, "--------------}\n\n");
14065
14066             SaveGame(f, 0, NULL); /* also closes the file*/
14067         }
14068
14069         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14070         nCmailMovesRegistered ++;
14071     } else if (nCmailGames == 1) {
14072         DisplayError(_("You have not made a move yet"), 0);
14073         return FALSE;
14074     }
14075
14076     return TRUE;
14077 }
14078
14079 void
14080 MailMoveEvent ()
14081 {
14082 #if !WIN32
14083     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14084     FILE *commandOutput;
14085     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14086     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14087     int nBuffers;
14088     int i;
14089     int archived;
14090     char *arcDir;
14091
14092     if (! cmailMsgLoaded) {
14093         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14094         return;
14095     }
14096
14097     if (nCmailGames == nCmailResults) {
14098         DisplayError(_("No unfinished games"), 0);
14099         return;
14100     }
14101
14102 #if CMAIL_PROHIBIT_REMAIL
14103     if (cmailMailedMove) {
14104       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);
14105         DisplayError(msg, 0);
14106         return;
14107     }
14108 #endif
14109
14110     if (! (cmailMailedMove || RegisterMove())) return;
14111
14112     if (   cmailMailedMove
14113         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14114       snprintf(string, MSG_SIZ, partCommandString,
14115                appData.debugMode ? " -v" : "", appData.cmailGameName);
14116         commandOutput = popen(string, "r");
14117
14118         if (commandOutput == NULL) {
14119             DisplayError(_("Failed to invoke cmail"), 0);
14120         } else {
14121             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14122                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14123             }
14124             if (nBuffers > 1) {
14125                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14126                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14127                 nBytes = MSG_SIZ - 1;
14128             } else {
14129                 (void) memcpy(msg, buffer, nBytes);
14130             }
14131             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14132
14133             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14134                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14135
14136                 archived = TRUE;
14137                 for (i = 0; i < nCmailGames; i ++) {
14138                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14139                         archived = FALSE;
14140                     }
14141                 }
14142                 if (   archived
14143                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14144                         != NULL)) {
14145                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14146                            arcDir,
14147                            appData.cmailGameName,
14148                            gameInfo.date);
14149                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14150                     cmailMsgLoaded = FALSE;
14151                 }
14152             }
14153
14154             DisplayInformation(msg);
14155             pclose(commandOutput);
14156         }
14157     } else {
14158         if ((*cmailMsg) != '\0') {
14159             DisplayInformation(cmailMsg);
14160         }
14161     }
14162
14163     return;
14164 #endif /* !WIN32 */
14165 }
14166
14167 char *
14168 CmailMsg ()
14169 {
14170 #if WIN32
14171     return NULL;
14172 #else
14173     int  prependComma = 0;
14174     char number[5];
14175     char string[MSG_SIZ];       /* Space for game-list */
14176     int  i;
14177
14178     if (!cmailMsgLoaded) return "";
14179
14180     if (cmailMailedMove) {
14181       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14182     } else {
14183         /* Create a list of games left */
14184       snprintf(string, MSG_SIZ, "[");
14185         for (i = 0; i < nCmailGames; i ++) {
14186             if (! (   cmailMoveRegistered[i]
14187                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14188                 if (prependComma) {
14189                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14190                 } else {
14191                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14192                     prependComma = 1;
14193                 }
14194
14195                 strcat(string, number);
14196             }
14197         }
14198         strcat(string, "]");
14199
14200         if (nCmailMovesRegistered + nCmailResults == 0) {
14201             switch (nCmailGames) {
14202               case 1:
14203                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14204                 break;
14205
14206               case 2:
14207                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14208                 break;
14209
14210               default:
14211                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14212                          nCmailGames);
14213                 break;
14214             }
14215         } else {
14216             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14217               case 1:
14218                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14219                          string);
14220                 break;
14221
14222               case 0:
14223                 if (nCmailResults == nCmailGames) {
14224                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14225                 } else {
14226                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14227                 }
14228                 break;
14229
14230               default:
14231                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14232                          string);
14233             }
14234         }
14235     }
14236     return cmailMsg;
14237 #endif /* WIN32 */
14238 }
14239
14240 void
14241 ResetGameEvent ()
14242 {
14243     if (gameMode == Training)
14244       SetTrainingModeOff();
14245
14246     Reset(TRUE, TRUE);
14247     cmailMsgLoaded = FALSE;
14248     if (appData.icsActive) {
14249       SendToICS(ics_prefix);
14250       SendToICS("refresh\n");
14251     }
14252 }
14253
14254 void
14255 ExitEvent (int status)
14256 {
14257     exiting++;
14258     if (exiting > 2) {
14259       /* Give up on clean exit */
14260       exit(status);
14261     }
14262     if (exiting > 1) {
14263       /* Keep trying for clean exit */
14264       return;
14265     }
14266
14267     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14268     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14269
14270     if (telnetISR != NULL) {
14271       RemoveInputSource(telnetISR);
14272     }
14273     if (icsPR != NoProc) {
14274       DestroyChildProcess(icsPR, TRUE);
14275     }
14276
14277     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14278     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14279
14280     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14281     /* make sure this other one finishes before killing it!                  */
14282     if(endingGame) { int count = 0;
14283         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14284         while(endingGame && count++ < 10) DoSleep(1);
14285         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14286     }
14287
14288     /* Kill off chess programs */
14289     if (first.pr != NoProc) {
14290         ExitAnalyzeMode();
14291
14292         DoSleep( appData.delayBeforeQuit );
14293         SendToProgram("quit\n", &first);
14294         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14295     }
14296     if (second.pr != NoProc) {
14297         DoSleep( appData.delayBeforeQuit );
14298         SendToProgram("quit\n", &second);
14299         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14300     }
14301     if (first.isr != NULL) {
14302         RemoveInputSource(first.isr);
14303     }
14304     if (second.isr != NULL) {
14305         RemoveInputSource(second.isr);
14306     }
14307
14308     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14309     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14310
14311     ShutDownFrontEnd();
14312     exit(status);
14313 }
14314
14315 void
14316 PauseEngine (ChessProgramState *cps)
14317 {
14318     SendToProgram("pause\n", cps);
14319     cps->pause = 2;
14320 }
14321
14322 void
14323 UnPauseEngine (ChessProgramState *cps)
14324 {
14325     SendToProgram("resume\n", cps);
14326     cps->pause = 1;
14327 }
14328
14329 void
14330 PauseEvent ()
14331 {
14332     if (appData.debugMode)
14333         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14334     if (pausing) {
14335         pausing = FALSE;
14336         ModeHighlight();
14337         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14338             StartClocks();
14339             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14340                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14341                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14342             }
14343             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14344             HandleMachineMove(stashedInputMove, stalledEngine);
14345             stalledEngine = NULL;
14346             return;
14347         }
14348         if (gameMode == MachinePlaysWhite ||
14349             gameMode == TwoMachinesPlay   ||
14350             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14351             if(first.pause)  UnPauseEngine(&first);
14352             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14353             if(second.pause) UnPauseEngine(&second);
14354             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14355             StartClocks();
14356         } else {
14357             DisplayBothClocks();
14358         }
14359         if (gameMode == PlayFromGameFile) {
14360             if (appData.timeDelay >= 0)
14361                 AutoPlayGameLoop();
14362         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14363             Reset(FALSE, TRUE);
14364             SendToICS(ics_prefix);
14365             SendToICS("refresh\n");
14366         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14367             ForwardInner(forwardMostMove);
14368         }
14369         pauseExamInvalid = FALSE;
14370     } else {
14371         switch (gameMode) {
14372           default:
14373             return;
14374           case IcsExamining:
14375             pauseExamForwardMostMove = forwardMostMove;
14376             pauseExamInvalid = FALSE;
14377             /* fall through */
14378           case IcsObserving:
14379           case IcsPlayingWhite:
14380           case IcsPlayingBlack:
14381             pausing = TRUE;
14382             ModeHighlight();
14383             return;
14384           case PlayFromGameFile:
14385             (void) StopLoadGameTimer();
14386             pausing = TRUE;
14387             ModeHighlight();
14388             break;
14389           case BeginningOfGame:
14390             if (appData.icsActive) return;
14391             /* else fall through */
14392           case MachinePlaysWhite:
14393           case MachinePlaysBlack:
14394           case TwoMachinesPlay:
14395             if (forwardMostMove == 0)
14396               return;           /* don't pause if no one has moved */
14397             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14398                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14399                 if(onMove->pause) {           // thinking engine can be paused
14400                     PauseEngine(onMove);      // do it
14401                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14402                         PauseEngine(onMove->other);
14403                     else
14404                         SendToProgram("easy\n", onMove->other);
14405                     StopClocks();
14406                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14407             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14408                 if(first.pause) {
14409                     PauseEngine(&first);
14410                     StopClocks();
14411                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14412             } else { // human on move, pause pondering by either method
14413                 if(first.pause)
14414                     PauseEngine(&first);
14415                 else if(appData.ponderNextMove)
14416                     SendToProgram("easy\n", &first);
14417                 StopClocks();
14418             }
14419             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14420           case AnalyzeMode:
14421             pausing = TRUE;
14422             ModeHighlight();
14423             break;
14424         }
14425     }
14426 }
14427
14428 void
14429 EditCommentEvent ()
14430 {
14431     char title[MSG_SIZ];
14432
14433     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14434       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14435     } else {
14436       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14437                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14438                parseList[currentMove - 1]);
14439     }
14440
14441     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14442 }
14443
14444
14445 void
14446 EditTagsEvent ()
14447 {
14448     char *tags = PGNTags(&gameInfo);
14449     bookUp = FALSE;
14450     EditTagsPopUp(tags, NULL);
14451     free(tags);
14452 }
14453
14454 void
14455 ToggleSecond ()
14456 {
14457   if(second.analyzing) {
14458     SendToProgram("exit\n", &second);
14459     second.analyzing = FALSE;
14460   } else {
14461     if (second.pr == NoProc) StartChessProgram(&second);
14462     InitChessProgram(&second, FALSE);
14463     FeedMovesToProgram(&second, currentMove);
14464
14465     SendToProgram("analyze\n", &second);
14466     second.analyzing = TRUE;
14467   }
14468 }
14469
14470 /* Toggle ShowThinking */
14471 void
14472 ToggleShowThinking()
14473 {
14474   appData.showThinking = !appData.showThinking;
14475   ShowThinkingEvent();
14476 }
14477
14478 int
14479 AnalyzeModeEvent ()
14480 {
14481     char buf[MSG_SIZ];
14482
14483     if (!first.analysisSupport) {
14484       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14485       DisplayError(buf, 0);
14486       return 0;
14487     }
14488     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14489     if (appData.icsActive) {
14490         if (gameMode != IcsObserving) {
14491           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14492             DisplayError(buf, 0);
14493             /* secure check */
14494             if (appData.icsEngineAnalyze) {
14495                 if (appData.debugMode)
14496                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14497                 ExitAnalyzeMode();
14498                 ModeHighlight();
14499             }
14500             return 0;
14501         }
14502         /* if enable, user wants to disable icsEngineAnalyze */
14503         if (appData.icsEngineAnalyze) {
14504                 ExitAnalyzeMode();
14505                 ModeHighlight();
14506                 return 0;
14507         }
14508         appData.icsEngineAnalyze = TRUE;
14509         if (appData.debugMode)
14510             fprintf(debugFP, "ICS engine analyze starting... \n");
14511     }
14512
14513     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14514     if (appData.noChessProgram || gameMode == AnalyzeMode)
14515       return 0;
14516
14517     if (gameMode != AnalyzeFile) {
14518         if (!appData.icsEngineAnalyze) {
14519                EditGameEvent();
14520                if (gameMode != EditGame) return 0;
14521         }
14522         if (!appData.showThinking) ToggleShowThinking();
14523         ResurrectChessProgram();
14524         SendToProgram("analyze\n", &first);
14525         first.analyzing = TRUE;
14526         /*first.maybeThinking = TRUE;*/
14527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14528         EngineOutputPopUp();
14529     }
14530     if (!appData.icsEngineAnalyze) {
14531         gameMode = AnalyzeMode;
14532         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14533     }
14534     pausing = FALSE;
14535     ModeHighlight();
14536     SetGameInfo();
14537
14538     StartAnalysisClock();
14539     GetTimeMark(&lastNodeCountTime);
14540     lastNodeCount = 0;
14541     return 1;
14542 }
14543
14544 void
14545 AnalyzeFileEvent ()
14546 {
14547     if (appData.noChessProgram || gameMode == AnalyzeFile)
14548       return;
14549
14550     if (!first.analysisSupport) {
14551       char buf[MSG_SIZ];
14552       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14553       DisplayError(buf, 0);
14554       return;
14555     }
14556
14557     if (gameMode != AnalyzeMode) {
14558         keepInfo = 1; // mere annotating should not alter PGN tags
14559         EditGameEvent();
14560         keepInfo = 0;
14561         if (gameMode != EditGame) return;
14562         if (!appData.showThinking) ToggleShowThinking();
14563         ResurrectChessProgram();
14564         SendToProgram("analyze\n", &first);
14565         first.analyzing = TRUE;
14566         /*first.maybeThinking = TRUE;*/
14567         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14568         EngineOutputPopUp();
14569     }
14570     gameMode = AnalyzeFile;
14571     pausing = FALSE;
14572     ModeHighlight();
14573
14574     StartAnalysisClock();
14575     GetTimeMark(&lastNodeCountTime);
14576     lastNodeCount = 0;
14577     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14578     AnalysisPeriodicEvent(1);
14579 }
14580
14581 void
14582 MachineWhiteEvent ()
14583 {
14584     char buf[MSG_SIZ];
14585     char *bookHit = NULL;
14586
14587     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14588       return;
14589
14590
14591     if (gameMode == PlayFromGameFile ||
14592         gameMode == TwoMachinesPlay  ||
14593         gameMode == Training         ||
14594         gameMode == AnalyzeMode      ||
14595         gameMode == EndOfGame)
14596         EditGameEvent();
14597
14598     if (gameMode == EditPosition)
14599         EditPositionDone(TRUE);
14600
14601     if (!WhiteOnMove(currentMove)) {
14602         DisplayError(_("It is not White's turn"), 0);
14603         return;
14604     }
14605
14606     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14607       ExitAnalyzeMode();
14608
14609     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14610         gameMode == AnalyzeFile)
14611         TruncateGame();
14612
14613     ResurrectChessProgram();    /* in case it isn't running */
14614     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14615         gameMode = MachinePlaysWhite;
14616         ResetClocks();
14617     } else
14618     gameMode = MachinePlaysWhite;
14619     pausing = FALSE;
14620     ModeHighlight();
14621     SetGameInfo();
14622     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14623     DisplayTitle(buf);
14624     if (first.sendName) {
14625       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14626       SendToProgram(buf, &first);
14627     }
14628     if (first.sendTime) {
14629       if (first.useColors) {
14630         SendToProgram("black\n", &first); /*gnu kludge*/
14631       }
14632       SendTimeRemaining(&first, TRUE);
14633     }
14634     if (first.useColors) {
14635       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14636     }
14637     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14638     SetMachineThinkingEnables();
14639     first.maybeThinking = TRUE;
14640     StartClocks();
14641     firstMove = FALSE;
14642
14643     if (appData.autoFlipView && !flipView) {
14644       flipView = !flipView;
14645       DrawPosition(FALSE, NULL);
14646       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14647     }
14648
14649     if(bookHit) { // [HGM] book: simulate book reply
14650         static char bookMove[MSG_SIZ]; // a bit generous?
14651
14652         programStats.nodes = programStats.depth = programStats.time =
14653         programStats.score = programStats.got_only_move = 0;
14654         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14655
14656         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14657         strcat(bookMove, bookHit);
14658         HandleMachineMove(bookMove, &first);
14659     }
14660 }
14661
14662 void
14663 MachineBlackEvent ()
14664 {
14665   char buf[MSG_SIZ];
14666   char *bookHit = NULL;
14667
14668     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14669         return;
14670
14671
14672     if (gameMode == PlayFromGameFile ||
14673         gameMode == TwoMachinesPlay  ||
14674         gameMode == Training         ||
14675         gameMode == AnalyzeMode      ||
14676         gameMode == EndOfGame)
14677         EditGameEvent();
14678
14679     if (gameMode == EditPosition)
14680         EditPositionDone(TRUE);
14681
14682     if (WhiteOnMove(currentMove)) {
14683         DisplayError(_("It is not Black's turn"), 0);
14684         return;
14685     }
14686
14687     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14688       ExitAnalyzeMode();
14689
14690     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14691         gameMode == AnalyzeFile)
14692         TruncateGame();
14693
14694     ResurrectChessProgram();    /* in case it isn't running */
14695     gameMode = MachinePlaysBlack;
14696     pausing = FALSE;
14697     ModeHighlight();
14698     SetGameInfo();
14699     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14700     DisplayTitle(buf);
14701     if (first.sendName) {
14702       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14703       SendToProgram(buf, &first);
14704     }
14705     if (first.sendTime) {
14706       if (first.useColors) {
14707         SendToProgram("white\n", &first); /*gnu kludge*/
14708       }
14709       SendTimeRemaining(&first, FALSE);
14710     }
14711     if (first.useColors) {
14712       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14713     }
14714     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14715     SetMachineThinkingEnables();
14716     first.maybeThinking = TRUE;
14717     StartClocks();
14718
14719     if (appData.autoFlipView && flipView) {
14720       flipView = !flipView;
14721       DrawPosition(FALSE, NULL);
14722       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14723     }
14724     if(bookHit) { // [HGM] book: simulate book reply
14725         static char bookMove[MSG_SIZ]; // a bit generous?
14726
14727         programStats.nodes = programStats.depth = programStats.time =
14728         programStats.score = programStats.got_only_move = 0;
14729         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14730
14731         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14732         strcat(bookMove, bookHit);
14733         HandleMachineMove(bookMove, &first);
14734     }
14735 }
14736
14737
14738 void
14739 DisplayTwoMachinesTitle ()
14740 {
14741     char buf[MSG_SIZ];
14742     if (appData.matchGames > 0) {
14743         if(appData.tourneyFile[0]) {
14744           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14745                    gameInfo.white, _("vs."), gameInfo.black,
14746                    nextGame+1, appData.matchGames+1,
14747                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14748         } else
14749         if (first.twoMachinesColor[0] == 'w') {
14750           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14751                    gameInfo.white, _("vs."),  gameInfo.black,
14752                    first.matchWins, second.matchWins,
14753                    matchGame - 1 - (first.matchWins + second.matchWins));
14754         } else {
14755           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14756                    gameInfo.white, _("vs."), gameInfo.black,
14757                    second.matchWins, first.matchWins,
14758                    matchGame - 1 - (first.matchWins + second.matchWins));
14759         }
14760     } else {
14761       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14762     }
14763     DisplayTitle(buf);
14764 }
14765
14766 void
14767 SettingsMenuIfReady ()
14768 {
14769   if (second.lastPing != second.lastPong) {
14770     DisplayMessage("", _("Waiting for second chess program"));
14771     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14772     return;
14773   }
14774   ThawUI();
14775   DisplayMessage("", "");
14776   SettingsPopUp(&second);
14777 }
14778
14779 int
14780 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14781 {
14782     char buf[MSG_SIZ];
14783     if (cps->pr == NoProc) {
14784         StartChessProgram(cps);
14785         if (cps->protocolVersion == 1) {
14786           retry();
14787           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14788         } else {
14789           /* kludge: allow timeout for initial "feature" command */
14790           if(retry != TwoMachinesEventIfReady) FreezeUI();
14791           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14792           DisplayMessage("", buf);
14793           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14794         }
14795         return 1;
14796     }
14797     return 0;
14798 }
14799
14800 void
14801 TwoMachinesEvent P((void))
14802 {
14803     int i;
14804     char buf[MSG_SIZ];
14805     ChessProgramState *onmove;
14806     char *bookHit = NULL;
14807     static int stalling = 0;
14808     TimeMark now;
14809     long wait;
14810
14811     if (appData.noChessProgram) return;
14812
14813     switch (gameMode) {
14814       case TwoMachinesPlay:
14815         return;
14816       case MachinePlaysWhite:
14817       case MachinePlaysBlack:
14818         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14819             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14820             return;
14821         }
14822         /* fall through */
14823       case BeginningOfGame:
14824       case PlayFromGameFile:
14825       case EndOfGame:
14826         EditGameEvent();
14827         if (gameMode != EditGame) return;
14828         break;
14829       case EditPosition:
14830         EditPositionDone(TRUE);
14831         break;
14832       case AnalyzeMode:
14833       case AnalyzeFile:
14834         ExitAnalyzeMode();
14835         break;
14836       case EditGame:
14837       default:
14838         break;
14839     }
14840
14841 //    forwardMostMove = currentMove;
14842     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14843     startingEngine = TRUE;
14844
14845     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14846
14847     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14848     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14849       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14850       return;
14851     }
14852     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14853
14854     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14855                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14856         startingEngine = matchMode = FALSE;
14857         DisplayError("second engine does not play this", 0);
14858         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14859         EditGameEvent(); // switch back to EditGame mode
14860         return;
14861     }
14862
14863     if(!stalling) {
14864       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14865       SendToProgram("force\n", &second);
14866       stalling = 1;
14867       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14868       return;
14869     }
14870     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14871     if(appData.matchPause>10000 || appData.matchPause<10)
14872                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14873     wait = SubtractTimeMarks(&now, &pauseStart);
14874     if(wait < appData.matchPause) {
14875         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14876         return;
14877     }
14878     // we are now committed to starting the game
14879     stalling = 0;
14880     DisplayMessage("", "");
14881     if (startedFromSetupPosition) {
14882         SendBoard(&second, backwardMostMove);
14883     if (appData.debugMode) {
14884         fprintf(debugFP, "Two Machines\n");
14885     }
14886     }
14887     for (i = backwardMostMove; i < forwardMostMove; i++) {
14888         SendMoveToProgram(i, &second);
14889     }
14890
14891     gameMode = TwoMachinesPlay;
14892     pausing = startingEngine = FALSE;
14893     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14894     SetGameInfo();
14895     DisplayTwoMachinesTitle();
14896     firstMove = TRUE;
14897     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14898         onmove = &first;
14899     } else {
14900         onmove = &second;
14901     }
14902     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14903     SendToProgram(first.computerString, &first);
14904     if (first.sendName) {
14905       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14906       SendToProgram(buf, &first);
14907     }
14908     SendToProgram(second.computerString, &second);
14909     if (second.sendName) {
14910       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14911       SendToProgram(buf, &second);
14912     }
14913
14914     ResetClocks();
14915     if (!first.sendTime || !second.sendTime) {
14916         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14917         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14918     }
14919     if (onmove->sendTime) {
14920       if (onmove->useColors) {
14921         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14922       }
14923       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14924     }
14925     if (onmove->useColors) {
14926       SendToProgram(onmove->twoMachinesColor, onmove);
14927     }
14928     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14929 //    SendToProgram("go\n", onmove);
14930     onmove->maybeThinking = TRUE;
14931     SetMachineThinkingEnables();
14932
14933     StartClocks();
14934
14935     if(bookHit) { // [HGM] book: simulate book reply
14936         static char bookMove[MSG_SIZ]; // a bit generous?
14937
14938         programStats.nodes = programStats.depth = programStats.time =
14939         programStats.score = programStats.got_only_move = 0;
14940         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14941
14942         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14943         strcat(bookMove, bookHit);
14944         savedMessage = bookMove; // args for deferred call
14945         savedState = onmove;
14946         ScheduleDelayedEvent(DeferredBookMove, 1);
14947     }
14948 }
14949
14950 void
14951 TrainingEvent ()
14952 {
14953     if (gameMode == Training) {
14954       SetTrainingModeOff();
14955       gameMode = PlayFromGameFile;
14956       DisplayMessage("", _("Training mode off"));
14957     } else {
14958       gameMode = Training;
14959       animateTraining = appData.animate;
14960
14961       /* make sure we are not already at the end of the game */
14962       if (currentMove < forwardMostMove) {
14963         SetTrainingModeOn();
14964         DisplayMessage("", _("Training mode on"));
14965       } else {
14966         gameMode = PlayFromGameFile;
14967         DisplayError(_("Already at end of game"), 0);
14968       }
14969     }
14970     ModeHighlight();
14971 }
14972
14973 void
14974 IcsClientEvent ()
14975 {
14976     if (!appData.icsActive) return;
14977     switch (gameMode) {
14978       case IcsPlayingWhite:
14979       case IcsPlayingBlack:
14980       case IcsObserving:
14981       case IcsIdle:
14982       case BeginningOfGame:
14983       case IcsExamining:
14984         return;
14985
14986       case EditGame:
14987         break;
14988
14989       case EditPosition:
14990         EditPositionDone(TRUE);
14991         break;
14992
14993       case AnalyzeMode:
14994       case AnalyzeFile:
14995         ExitAnalyzeMode();
14996         break;
14997
14998       default:
14999         EditGameEvent();
15000         break;
15001     }
15002
15003     gameMode = IcsIdle;
15004     ModeHighlight();
15005     return;
15006 }
15007
15008 void
15009 EditGameEvent ()
15010 {
15011     int i;
15012
15013     switch (gameMode) {
15014       case Training:
15015         SetTrainingModeOff();
15016         break;
15017       case MachinePlaysWhite:
15018       case MachinePlaysBlack:
15019       case BeginningOfGame:
15020         SendToProgram("force\n", &first);
15021         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15022             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15023                 char buf[MSG_SIZ];
15024                 abortEngineThink = TRUE;
15025                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15026                 SendToProgram(buf, &first);
15027                 DisplayMessage("Aborting engine think", "");
15028                 FreezeUI();
15029             }
15030         }
15031         SetUserThinkingEnables();
15032         break;
15033       case PlayFromGameFile:
15034         (void) StopLoadGameTimer();
15035         if (gameFileFP != NULL) {
15036             gameFileFP = NULL;
15037         }
15038         break;
15039       case EditPosition:
15040         EditPositionDone(TRUE);
15041         break;
15042       case AnalyzeMode:
15043       case AnalyzeFile:
15044         ExitAnalyzeMode();
15045         SendToProgram("force\n", &first);
15046         break;
15047       case TwoMachinesPlay:
15048         GameEnds(EndOfFile, NULL, GE_PLAYER);
15049         ResurrectChessProgram();
15050         SetUserThinkingEnables();
15051         break;
15052       case EndOfGame:
15053         ResurrectChessProgram();
15054         break;
15055       case IcsPlayingBlack:
15056       case IcsPlayingWhite:
15057         DisplayError(_("Warning: You are still playing a game"), 0);
15058         break;
15059       case IcsObserving:
15060         DisplayError(_("Warning: You are still observing a game"), 0);
15061         break;
15062       case IcsExamining:
15063         DisplayError(_("Warning: You are still examining a game"), 0);
15064         break;
15065       case IcsIdle:
15066         break;
15067       case EditGame:
15068       default:
15069         return;
15070     }
15071
15072     pausing = FALSE;
15073     StopClocks();
15074     first.offeredDraw = second.offeredDraw = 0;
15075
15076     if (gameMode == PlayFromGameFile) {
15077         whiteTimeRemaining = timeRemaining[0][currentMove];
15078         blackTimeRemaining = timeRemaining[1][currentMove];
15079         DisplayTitle("");
15080     }
15081
15082     if (gameMode == MachinePlaysWhite ||
15083         gameMode == MachinePlaysBlack ||
15084         gameMode == TwoMachinesPlay ||
15085         gameMode == EndOfGame) {
15086         i = forwardMostMove;
15087         while (i > currentMove) {
15088             SendToProgram("undo\n", &first);
15089             i--;
15090         }
15091         if(!adjustedClock) {
15092         whiteTimeRemaining = timeRemaining[0][currentMove];
15093         blackTimeRemaining = timeRemaining[1][currentMove];
15094         DisplayBothClocks();
15095         }
15096         if (whiteFlag || blackFlag) {
15097             whiteFlag = blackFlag = 0;
15098         }
15099         DisplayTitle("");
15100     }
15101
15102     gameMode = EditGame;
15103     ModeHighlight();
15104     SetGameInfo();
15105 }
15106
15107
15108 void
15109 EditPositionEvent ()
15110 {
15111     if (gameMode == EditPosition) {
15112         EditGameEvent();
15113         return;
15114     }
15115
15116     EditGameEvent();
15117     if (gameMode != EditGame) return;
15118
15119     gameMode = EditPosition;
15120     ModeHighlight();
15121     SetGameInfo();
15122     if (currentMove > 0)
15123       CopyBoard(boards[0], boards[currentMove]);
15124
15125     blackPlaysFirst = !WhiteOnMove(currentMove);
15126     ResetClocks();
15127     currentMove = forwardMostMove = backwardMostMove = 0;
15128     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15129     DisplayMove(-1);
15130     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15131 }
15132
15133 void
15134 ExitAnalyzeMode ()
15135 {
15136     /* [DM] icsEngineAnalyze - possible call from other functions */
15137     if (appData.icsEngineAnalyze) {
15138         appData.icsEngineAnalyze = FALSE;
15139
15140         DisplayMessage("",_("Close ICS engine analyze..."));
15141     }
15142     if (first.analysisSupport && first.analyzing) {
15143       SendToBoth("exit\n");
15144       first.analyzing = second.analyzing = FALSE;
15145     }
15146     thinkOutput[0] = NULLCHAR;
15147 }
15148
15149 void
15150 EditPositionDone (Boolean fakeRights)
15151 {
15152     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15153
15154     startedFromSetupPosition = TRUE;
15155     InitChessProgram(&first, FALSE);
15156     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15157       boards[0][EP_STATUS] = EP_NONE;
15158       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15159       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15160         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15161         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15162       } else boards[0][CASTLING][2] = NoRights;
15163       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15164         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15165         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15166       } else boards[0][CASTLING][5] = NoRights;
15167       if(gameInfo.variant == VariantSChess) {
15168         int i;
15169         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15170           boards[0][VIRGIN][i] = 0;
15171           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15172           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15173         }
15174       }
15175     }
15176     SendToProgram("force\n", &first);
15177     if (blackPlaysFirst) {
15178         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15179         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15180         currentMove = forwardMostMove = backwardMostMove = 1;
15181         CopyBoard(boards[1], boards[0]);
15182     } else {
15183         currentMove = forwardMostMove = backwardMostMove = 0;
15184     }
15185     SendBoard(&first, forwardMostMove);
15186     if (appData.debugMode) {
15187         fprintf(debugFP, "EditPosDone\n");
15188     }
15189     DisplayTitle("");
15190     DisplayMessage("", "");
15191     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15192     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15193     gameMode = EditGame;
15194     ModeHighlight();
15195     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15196     ClearHighlights(); /* [AS] */
15197 }
15198
15199 /* Pause for `ms' milliseconds */
15200 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15201 void
15202 TimeDelay (long ms)
15203 {
15204     TimeMark m1, m2;
15205
15206     GetTimeMark(&m1);
15207     do {
15208         GetTimeMark(&m2);
15209     } while (SubtractTimeMarks(&m2, &m1) < ms);
15210 }
15211
15212 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15213 void
15214 SendMultiLineToICS (char *buf)
15215 {
15216     char temp[MSG_SIZ+1], *p;
15217     int len;
15218
15219     len = strlen(buf);
15220     if (len > MSG_SIZ)
15221       len = MSG_SIZ;
15222
15223     strncpy(temp, buf, len);
15224     temp[len] = 0;
15225
15226     p = temp;
15227     while (*p) {
15228         if (*p == '\n' || *p == '\r')
15229           *p = ' ';
15230         ++p;
15231     }
15232
15233     strcat(temp, "\n");
15234     SendToICS(temp);
15235     SendToPlayer(temp, strlen(temp));
15236 }
15237
15238 void
15239 SetWhiteToPlayEvent ()
15240 {
15241     if (gameMode == EditPosition) {
15242         blackPlaysFirst = FALSE;
15243         DisplayBothClocks();    /* works because currentMove is 0 */
15244     } else if (gameMode == IcsExamining) {
15245         SendToICS(ics_prefix);
15246         SendToICS("tomove white\n");
15247     }
15248 }
15249
15250 void
15251 SetBlackToPlayEvent ()
15252 {
15253     if (gameMode == EditPosition) {
15254         blackPlaysFirst = TRUE;
15255         currentMove = 1;        /* kludge */
15256         DisplayBothClocks();
15257         currentMove = 0;
15258     } else if (gameMode == IcsExamining) {
15259         SendToICS(ics_prefix);
15260         SendToICS("tomove black\n");
15261     }
15262 }
15263
15264 void
15265 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15266 {
15267     char buf[MSG_SIZ];
15268     ChessSquare piece = boards[0][y][x];
15269     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15270     static int lastVariant;
15271
15272     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15273
15274     switch (selection) {
15275       case ClearBoard:
15276         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15277         MarkTargetSquares(1);
15278         CopyBoard(currentBoard, boards[0]);
15279         CopyBoard(menuBoard, initialPosition);
15280         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15281             SendToICS(ics_prefix);
15282             SendToICS("bsetup clear\n");
15283         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15284             SendToICS(ics_prefix);
15285             SendToICS("clearboard\n");
15286         } else {
15287             int nonEmpty = 0;
15288             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15289                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15290                 for (y = 0; y < BOARD_HEIGHT; y++) {
15291                     if (gameMode == IcsExamining) {
15292                         if (boards[currentMove][y][x] != EmptySquare) {
15293                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15294                                     AAA + x, ONE + y);
15295                             SendToICS(buf);
15296                         }
15297                     } else if(boards[0][y][x] != DarkSquare) {
15298                         if(boards[0][y][x] != p) nonEmpty++;
15299                         boards[0][y][x] = p;
15300                     }
15301                 }
15302             }
15303             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15304                 int r;
15305                 for(r = 0; r < BOARD_HEIGHT; r++) {
15306                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15307                     ChessSquare p = menuBoard[r][x];
15308                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15309                   }
15310                 }
15311                 DisplayMessage("Clicking clock again restores position", "");
15312                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15313                 if(!nonEmpty) { // asked to clear an empty board
15314                     CopyBoard(boards[0], menuBoard);
15315                 } else
15316                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15317                     CopyBoard(boards[0], initialPosition);
15318                 } else
15319                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15320                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15321                     CopyBoard(boards[0], erasedBoard);
15322                 } else
15323                     CopyBoard(erasedBoard, currentBoard);
15324
15325             }
15326         }
15327         if (gameMode == EditPosition) {
15328             DrawPosition(FALSE, boards[0]);
15329         }
15330         break;
15331
15332       case WhitePlay:
15333         SetWhiteToPlayEvent();
15334         break;
15335
15336       case BlackPlay:
15337         SetBlackToPlayEvent();
15338         break;
15339
15340       case EmptySquare:
15341         if (gameMode == IcsExamining) {
15342             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15343             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15344             SendToICS(buf);
15345         } else {
15346             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15347                 if(x == BOARD_LEFT-2) {
15348                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15349                     boards[0][y][1] = 0;
15350                 } else
15351                 if(x == BOARD_RGHT+1) {
15352                     if(y >= gameInfo.holdingsSize) break;
15353                     boards[0][y][BOARD_WIDTH-2] = 0;
15354                 } else break;
15355             }
15356             boards[0][y][x] = EmptySquare;
15357             DrawPosition(FALSE, boards[0]);
15358         }
15359         break;
15360
15361       case PromotePiece:
15362         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15363            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15364             selection = (ChessSquare) (PROMOTED piece);
15365         } else if(piece == EmptySquare) selection = WhiteSilver;
15366         else selection = (ChessSquare)((int)piece - 1);
15367         goto defaultlabel;
15368
15369       case DemotePiece:
15370         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15371            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15372             selection = (ChessSquare) (DEMOTED piece);
15373         } else if(piece == EmptySquare) selection = BlackSilver;
15374         else selection = (ChessSquare)((int)piece + 1);
15375         goto defaultlabel;
15376
15377       case WhiteQueen:
15378       case BlackQueen:
15379         if(gameInfo.variant == VariantShatranj ||
15380            gameInfo.variant == VariantXiangqi  ||
15381            gameInfo.variant == VariantCourier  ||
15382            gameInfo.variant == VariantASEAN    ||
15383            gameInfo.variant == VariantMakruk     )
15384             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15385         goto defaultlabel;
15386
15387       case WhiteKing:
15388       case BlackKing:
15389         if(gameInfo.variant == VariantXiangqi)
15390             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15391         if(gameInfo.variant == VariantKnightmate)
15392             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15393       default:
15394         defaultlabel:
15395         if (gameMode == IcsExamining) {
15396             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15397             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15398                      PieceToChar(selection), AAA + x, ONE + y);
15399             SendToICS(buf);
15400         } else {
15401             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15402                 int n;
15403                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15404                     n = PieceToNumber(selection - BlackPawn);
15405                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15406                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15407                     boards[0][BOARD_HEIGHT-1-n][1]++;
15408                 } else
15409                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15410                     n = PieceToNumber(selection);
15411                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15412                     boards[0][n][BOARD_WIDTH-1] = selection;
15413                     boards[0][n][BOARD_WIDTH-2]++;
15414                 }
15415             } else
15416             boards[0][y][x] = selection;
15417             DrawPosition(TRUE, boards[0]);
15418             ClearHighlights();
15419             fromX = fromY = -1;
15420         }
15421         break;
15422     }
15423 }
15424
15425
15426 void
15427 DropMenuEvent (ChessSquare selection, int x, int y)
15428 {
15429     ChessMove moveType;
15430
15431     switch (gameMode) {
15432       case IcsPlayingWhite:
15433       case MachinePlaysBlack:
15434         if (!WhiteOnMove(currentMove)) {
15435             DisplayMoveError(_("It is Black's turn"));
15436             return;
15437         }
15438         moveType = WhiteDrop;
15439         break;
15440       case IcsPlayingBlack:
15441       case MachinePlaysWhite:
15442         if (WhiteOnMove(currentMove)) {
15443             DisplayMoveError(_("It is White's turn"));
15444             return;
15445         }
15446         moveType = BlackDrop;
15447         break;
15448       case EditGame:
15449         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15450         break;
15451       default:
15452         return;
15453     }
15454
15455     if (moveType == BlackDrop && selection < BlackPawn) {
15456       selection = (ChessSquare) ((int) selection
15457                                  + (int) BlackPawn - (int) WhitePawn);
15458     }
15459     if (boards[currentMove][y][x] != EmptySquare) {
15460         DisplayMoveError(_("That square is occupied"));
15461         return;
15462     }
15463
15464     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15465 }
15466
15467 void
15468 AcceptEvent ()
15469 {
15470     /* Accept a pending offer of any kind from opponent */
15471
15472     if (appData.icsActive) {
15473         SendToICS(ics_prefix);
15474         SendToICS("accept\n");
15475     } else if (cmailMsgLoaded) {
15476         if (currentMove == cmailOldMove &&
15477             commentList[cmailOldMove] != NULL &&
15478             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15479                    "Black offers a draw" : "White offers a draw")) {
15480             TruncateGame();
15481             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15482             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15483         } else {
15484             DisplayError(_("There is no pending offer on this move"), 0);
15485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15486         }
15487     } else {
15488         /* Not used for offers from chess program */
15489     }
15490 }
15491
15492 void
15493 DeclineEvent ()
15494 {
15495     /* Decline a pending offer of any kind from opponent */
15496
15497     if (appData.icsActive) {
15498         SendToICS(ics_prefix);
15499         SendToICS("decline\n");
15500     } else if (cmailMsgLoaded) {
15501         if (currentMove == cmailOldMove &&
15502             commentList[cmailOldMove] != NULL &&
15503             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15504                    "Black offers a draw" : "White offers a draw")) {
15505 #ifdef NOTDEF
15506             AppendComment(cmailOldMove, "Draw declined", TRUE);
15507             DisplayComment(cmailOldMove - 1, "Draw declined");
15508 #endif /*NOTDEF*/
15509         } else {
15510             DisplayError(_("There is no pending offer on this move"), 0);
15511         }
15512     } else {
15513         /* Not used for offers from chess program */
15514     }
15515 }
15516
15517 void
15518 RematchEvent ()
15519 {
15520     /* Issue ICS rematch command */
15521     if (appData.icsActive) {
15522         SendToICS(ics_prefix);
15523         SendToICS("rematch\n");
15524     }
15525 }
15526
15527 void
15528 CallFlagEvent ()
15529 {
15530     /* Call your opponent's flag (claim a win on time) */
15531     if (appData.icsActive) {
15532         SendToICS(ics_prefix);
15533         SendToICS("flag\n");
15534     } else {
15535         switch (gameMode) {
15536           default:
15537             return;
15538           case MachinePlaysWhite:
15539             if (whiteFlag) {
15540                 if (blackFlag)
15541                   GameEnds(GameIsDrawn, "Both players ran out of time",
15542                            GE_PLAYER);
15543                 else
15544                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15545             } else {
15546                 DisplayError(_("Your opponent is not out of time"), 0);
15547             }
15548             break;
15549           case MachinePlaysBlack:
15550             if (blackFlag) {
15551                 if (whiteFlag)
15552                   GameEnds(GameIsDrawn, "Both players ran out of time",
15553                            GE_PLAYER);
15554                 else
15555                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15556             } else {
15557                 DisplayError(_("Your opponent is not out of time"), 0);
15558             }
15559             break;
15560         }
15561     }
15562 }
15563
15564 void
15565 ClockClick (int which)
15566 {       // [HGM] code moved to back-end from winboard.c
15567         if(which) { // black clock
15568           if (gameMode == EditPosition || gameMode == IcsExamining) {
15569             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15570             SetBlackToPlayEvent();
15571           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15572                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15573           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15574           } else if (shiftKey) {
15575             AdjustClock(which, -1);
15576           } else if (gameMode == IcsPlayingWhite ||
15577                      gameMode == MachinePlaysBlack) {
15578             CallFlagEvent();
15579           }
15580         } else { // white clock
15581           if (gameMode == EditPosition || gameMode == IcsExamining) {
15582             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15583             SetWhiteToPlayEvent();
15584           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15585                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15586           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15587           } else if (shiftKey) {
15588             AdjustClock(which, -1);
15589           } else if (gameMode == IcsPlayingBlack ||
15590                    gameMode == MachinePlaysWhite) {
15591             CallFlagEvent();
15592           }
15593         }
15594 }
15595
15596 void
15597 DrawEvent ()
15598 {
15599     /* Offer draw or accept pending draw offer from opponent */
15600
15601     if (appData.icsActive) {
15602         /* Note: tournament rules require draw offers to be
15603            made after you make your move but before you punch
15604            your clock.  Currently ICS doesn't let you do that;
15605            instead, you immediately punch your clock after making
15606            a move, but you can offer a draw at any time. */
15607
15608         SendToICS(ics_prefix);
15609         SendToICS("draw\n");
15610         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15611     } else if (cmailMsgLoaded) {
15612         if (currentMove == cmailOldMove &&
15613             commentList[cmailOldMove] != NULL &&
15614             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15615                    "Black offers a draw" : "White offers a draw")) {
15616             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15617             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15618         } else if (currentMove == cmailOldMove + 1) {
15619             char *offer = WhiteOnMove(cmailOldMove) ?
15620               "White offers a draw" : "Black offers a draw";
15621             AppendComment(currentMove, offer, TRUE);
15622             DisplayComment(currentMove - 1, offer);
15623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15624         } else {
15625             DisplayError(_("You must make your move before offering a draw"), 0);
15626             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15627         }
15628     } else if (first.offeredDraw) {
15629         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15630     } else {
15631         if (first.sendDrawOffers) {
15632             SendToProgram("draw\n", &first);
15633             userOfferedDraw = TRUE;
15634         }
15635     }
15636 }
15637
15638 void
15639 AdjournEvent ()
15640 {
15641     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15642
15643     if (appData.icsActive) {
15644         SendToICS(ics_prefix);
15645         SendToICS("adjourn\n");
15646     } else {
15647         /* Currently GNU Chess doesn't offer or accept Adjourns */
15648     }
15649 }
15650
15651
15652 void
15653 AbortEvent ()
15654 {
15655     /* Offer Abort or accept pending Abort offer from opponent */
15656
15657     if (appData.icsActive) {
15658         SendToICS(ics_prefix);
15659         SendToICS("abort\n");
15660     } else {
15661         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15662     }
15663 }
15664
15665 void
15666 ResignEvent ()
15667 {
15668     /* Resign.  You can do this even if it's not your turn. */
15669
15670     if (appData.icsActive) {
15671         SendToICS(ics_prefix);
15672         SendToICS("resign\n");
15673     } else {
15674         switch (gameMode) {
15675           case MachinePlaysWhite:
15676             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15677             break;
15678           case MachinePlaysBlack:
15679             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15680             break;
15681           case EditGame:
15682             if (cmailMsgLoaded) {
15683                 TruncateGame();
15684                 if (WhiteOnMove(cmailOldMove)) {
15685                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15686                 } else {
15687                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15688                 }
15689                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15690             }
15691             break;
15692           default:
15693             break;
15694         }
15695     }
15696 }
15697
15698
15699 void
15700 StopObservingEvent ()
15701 {
15702     /* Stop observing current games */
15703     SendToICS(ics_prefix);
15704     SendToICS("unobserve\n");
15705 }
15706
15707 void
15708 StopExaminingEvent ()
15709 {
15710     /* Stop observing current game */
15711     SendToICS(ics_prefix);
15712     SendToICS("unexamine\n");
15713 }
15714
15715 void
15716 ForwardInner (int target)
15717 {
15718     int limit; int oldSeekGraphUp = seekGraphUp;
15719
15720     if (appData.debugMode)
15721         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15722                 target, currentMove, forwardMostMove);
15723
15724     if (gameMode == EditPosition)
15725       return;
15726
15727     seekGraphUp = FALSE;
15728     MarkTargetSquares(1);
15729     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15730
15731     if (gameMode == PlayFromGameFile && !pausing)
15732       PauseEvent();
15733
15734     if (gameMode == IcsExamining && pausing)
15735       limit = pauseExamForwardMostMove;
15736     else
15737       limit = forwardMostMove;
15738
15739     if (target > limit) target = limit;
15740
15741     if (target > 0 && moveList[target - 1][0]) {
15742         int fromX, fromY, toX, toY;
15743         toX = moveList[target - 1][2] - AAA;
15744         toY = moveList[target - 1][3] - ONE;
15745         if (moveList[target - 1][1] == '@') {
15746             if (appData.highlightLastMove) {
15747                 SetHighlights(-1, -1, toX, toY);
15748             }
15749         } else {
15750             int viaX = moveList[target - 1][5] - AAA;
15751             int viaY = moveList[target - 1][6] - ONE;
15752             fromX = moveList[target - 1][0] - AAA;
15753             fromY = moveList[target - 1][1] - ONE;
15754             if (target == currentMove + 1) {
15755                 if(moveList[target - 1][4] == ';') { // multi-leg
15756                     ChessSquare piece = boards[currentMove][viaY][viaX];
15757                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15758                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15759                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15760                     boards[currentMove][viaY][viaX] = piece;
15761                 } else
15762                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15763             }
15764             if (appData.highlightLastMove) {
15765                 SetHighlights(fromX, fromY, toX, toY);
15766             }
15767         }
15768     }
15769     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15770         gameMode == Training || gameMode == PlayFromGameFile ||
15771         gameMode == AnalyzeFile) {
15772         while (currentMove < target) {
15773             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15774             SendMoveToProgram(currentMove++, &first);
15775         }
15776     } else {
15777         currentMove = target;
15778     }
15779
15780     if (gameMode == EditGame || gameMode == EndOfGame) {
15781         whiteTimeRemaining = timeRemaining[0][currentMove];
15782         blackTimeRemaining = timeRemaining[1][currentMove];
15783     }
15784     DisplayBothClocks();
15785     DisplayMove(currentMove - 1);
15786     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15787     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15788     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15789         DisplayComment(currentMove - 1, commentList[currentMove]);
15790     }
15791     ClearMap(); // [HGM] exclude: invalidate map
15792 }
15793
15794
15795 void
15796 ForwardEvent ()
15797 {
15798     if (gameMode == IcsExamining && !pausing) {
15799         SendToICS(ics_prefix);
15800         SendToICS("forward\n");
15801     } else {
15802         ForwardInner(currentMove + 1);
15803     }
15804 }
15805
15806 void
15807 ToEndEvent ()
15808 {
15809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15810         /* to optimze, we temporarily turn off analysis mode while we feed
15811          * the remaining moves to the engine. Otherwise we get analysis output
15812          * after each move.
15813          */
15814         if (first.analysisSupport) {
15815           SendToProgram("exit\nforce\n", &first);
15816           first.analyzing = FALSE;
15817         }
15818     }
15819
15820     if (gameMode == IcsExamining && !pausing) {
15821         SendToICS(ics_prefix);
15822         SendToICS("forward 999999\n");
15823     } else {
15824         ForwardInner(forwardMostMove);
15825     }
15826
15827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15828         /* we have fed all the moves, so reactivate analysis mode */
15829         SendToProgram("analyze\n", &first);
15830         first.analyzing = TRUE;
15831         /*first.maybeThinking = TRUE;*/
15832         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15833     }
15834 }
15835
15836 void
15837 BackwardInner (int target)
15838 {
15839     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15840
15841     if (appData.debugMode)
15842         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15843                 target, currentMove, forwardMostMove);
15844
15845     if (gameMode == EditPosition) return;
15846     seekGraphUp = FALSE;
15847     MarkTargetSquares(1);
15848     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15849     if (currentMove <= backwardMostMove) {
15850         ClearHighlights();
15851         DrawPosition(full_redraw, boards[currentMove]);
15852         return;
15853     }
15854     if (gameMode == PlayFromGameFile && !pausing)
15855       PauseEvent();
15856
15857     if (moveList[target][0]) {
15858         int fromX, fromY, toX, toY;
15859         toX = moveList[target][2] - AAA;
15860         toY = moveList[target][3] - ONE;
15861         if (moveList[target][1] == '@') {
15862             if (appData.highlightLastMove) {
15863                 SetHighlights(-1, -1, toX, toY);
15864             }
15865         } else {
15866             fromX = moveList[target][0] - AAA;
15867             fromY = moveList[target][1] - ONE;
15868             if (target == currentMove - 1) {
15869                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15870             }
15871             if (appData.highlightLastMove) {
15872                 SetHighlights(fromX, fromY, toX, toY);
15873             }
15874         }
15875     }
15876     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15877         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15878         while (currentMove > target) {
15879             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15880                 // null move cannot be undone. Reload program with move history before it.
15881                 int i;
15882                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15883                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15884                 }
15885                 SendBoard(&first, i);
15886               if(second.analyzing) SendBoard(&second, i);
15887                 for(currentMove=i; currentMove<target; currentMove++) {
15888                     SendMoveToProgram(currentMove, &first);
15889                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15890                 }
15891                 break;
15892             }
15893             SendToBoth("undo\n");
15894             currentMove--;
15895         }
15896     } else {
15897         currentMove = target;
15898     }
15899
15900     if (gameMode == EditGame || gameMode == EndOfGame) {
15901         whiteTimeRemaining = timeRemaining[0][currentMove];
15902         blackTimeRemaining = timeRemaining[1][currentMove];
15903     }
15904     DisplayBothClocks();
15905     DisplayMove(currentMove - 1);
15906     DrawPosition(full_redraw, boards[currentMove]);
15907     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15908     // [HGM] PV info: routine tests if comment empty
15909     DisplayComment(currentMove - 1, commentList[currentMove]);
15910     ClearMap(); // [HGM] exclude: invalidate map
15911 }
15912
15913 void
15914 BackwardEvent ()
15915 {
15916     if (gameMode == IcsExamining && !pausing) {
15917         SendToICS(ics_prefix);
15918         SendToICS("backward\n");
15919     } else {
15920         BackwardInner(currentMove - 1);
15921     }
15922 }
15923
15924 void
15925 ToStartEvent ()
15926 {
15927     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15928         /* to optimize, we temporarily turn off analysis mode while we undo
15929          * all the moves. Otherwise we get analysis output after each undo.
15930          */
15931         if (first.analysisSupport) {
15932           SendToProgram("exit\nforce\n", &first);
15933           first.analyzing = FALSE;
15934         }
15935     }
15936
15937     if (gameMode == IcsExamining && !pausing) {
15938         SendToICS(ics_prefix);
15939         SendToICS("backward 999999\n");
15940     } else {
15941         BackwardInner(backwardMostMove);
15942     }
15943
15944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15945         /* we have fed all the moves, so reactivate analysis mode */
15946         SendToProgram("analyze\n", &first);
15947         first.analyzing = TRUE;
15948         /*first.maybeThinking = TRUE;*/
15949         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15950     }
15951 }
15952
15953 void
15954 ToNrEvent (int to)
15955 {
15956   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15957   if (to >= forwardMostMove) to = forwardMostMove;
15958   if (to <= backwardMostMove) to = backwardMostMove;
15959   if (to < currentMove) {
15960     BackwardInner(to);
15961   } else {
15962     ForwardInner(to);
15963   }
15964 }
15965
15966 void
15967 RevertEvent (Boolean annotate)
15968 {
15969     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15970         return;
15971     }
15972     if (gameMode != IcsExamining) {
15973         DisplayError(_("You are not examining a game"), 0);
15974         return;
15975     }
15976     if (pausing) {
15977         DisplayError(_("You can't revert while pausing"), 0);
15978         return;
15979     }
15980     SendToICS(ics_prefix);
15981     SendToICS("revert\n");
15982 }
15983
15984 void
15985 RetractMoveEvent ()
15986 {
15987     switch (gameMode) {
15988       case MachinePlaysWhite:
15989       case MachinePlaysBlack:
15990         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15991             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15992             return;
15993         }
15994         if (forwardMostMove < 2) return;
15995         currentMove = forwardMostMove = forwardMostMove - 2;
15996         whiteTimeRemaining = timeRemaining[0][currentMove];
15997         blackTimeRemaining = timeRemaining[1][currentMove];
15998         DisplayBothClocks();
15999         DisplayMove(currentMove - 1);
16000         ClearHighlights();/*!! could figure this out*/
16001         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16002         SendToProgram("remove\n", &first);
16003         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16004         break;
16005
16006       case BeginningOfGame:
16007       default:
16008         break;
16009
16010       case IcsPlayingWhite:
16011       case IcsPlayingBlack:
16012         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16013             SendToICS(ics_prefix);
16014             SendToICS("takeback 2\n");
16015         } else {
16016             SendToICS(ics_prefix);
16017             SendToICS("takeback 1\n");
16018         }
16019         break;
16020     }
16021 }
16022
16023 void
16024 MoveNowEvent ()
16025 {
16026     ChessProgramState *cps;
16027
16028     switch (gameMode) {
16029       case MachinePlaysWhite:
16030         if (!WhiteOnMove(forwardMostMove)) {
16031             DisplayError(_("It is your turn"), 0);
16032             return;
16033         }
16034         cps = &first;
16035         break;
16036       case MachinePlaysBlack:
16037         if (WhiteOnMove(forwardMostMove)) {
16038             DisplayError(_("It is your turn"), 0);
16039             return;
16040         }
16041         cps = &first;
16042         break;
16043       case TwoMachinesPlay:
16044         if (WhiteOnMove(forwardMostMove) ==
16045             (first.twoMachinesColor[0] == 'w')) {
16046             cps = &first;
16047         } else {
16048             cps = &second;
16049         }
16050         break;
16051       case BeginningOfGame:
16052       default:
16053         return;
16054     }
16055     SendToProgram("?\n", cps);
16056 }
16057
16058 void
16059 TruncateGameEvent ()
16060 {
16061     EditGameEvent();
16062     if (gameMode != EditGame) return;
16063     TruncateGame();
16064 }
16065
16066 void
16067 TruncateGame ()
16068 {
16069     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16070     if (forwardMostMove > currentMove) {
16071         if (gameInfo.resultDetails != NULL) {
16072             free(gameInfo.resultDetails);
16073             gameInfo.resultDetails = NULL;
16074             gameInfo.result = GameUnfinished;
16075         }
16076         forwardMostMove = currentMove;
16077         HistorySet(parseList, backwardMostMove, forwardMostMove,
16078                    currentMove-1);
16079     }
16080 }
16081
16082 void
16083 HintEvent ()
16084 {
16085     if (appData.noChessProgram) return;
16086     switch (gameMode) {
16087       case MachinePlaysWhite:
16088         if (WhiteOnMove(forwardMostMove)) {
16089             DisplayError(_("Wait until your turn."), 0);
16090             return;
16091         }
16092         break;
16093       case BeginningOfGame:
16094       case MachinePlaysBlack:
16095         if (!WhiteOnMove(forwardMostMove)) {
16096             DisplayError(_("Wait until your turn."), 0);
16097             return;
16098         }
16099         break;
16100       default:
16101         DisplayError(_("No hint available"), 0);
16102         return;
16103     }
16104     SendToProgram("hint\n", &first);
16105     hintRequested = TRUE;
16106 }
16107
16108 int
16109 SaveSelected (FILE *g, int dummy, char *dummy2)
16110 {
16111     ListGame * lg = (ListGame *) gameList.head;
16112     int nItem, cnt=0;
16113     FILE *f;
16114
16115     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16116         DisplayError(_("Game list not loaded or empty"), 0);
16117         return 0;
16118     }
16119
16120     creatingBook = TRUE; // suppresses stuff during load game
16121
16122     /* Get list size */
16123     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16124         if(lg->position >= 0) { // selected?
16125             LoadGame(f, nItem, "", TRUE);
16126             SaveGamePGN2(g); // leaves g open
16127             cnt++; DoEvents();
16128         }
16129         lg = (ListGame *) lg->node.succ;
16130     }
16131
16132     fclose(g);
16133     creatingBook = FALSE;
16134
16135     return cnt;
16136 }
16137
16138 void
16139 CreateBookEvent ()
16140 {
16141     ListGame * lg = (ListGame *) gameList.head;
16142     FILE *f, *g;
16143     int nItem;
16144     static int secondTime = FALSE;
16145
16146     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16147         DisplayError(_("Game list not loaded or empty"), 0);
16148         return;
16149     }
16150
16151     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16152         fclose(g);
16153         secondTime++;
16154         DisplayNote(_("Book file exists! Try again for overwrite."));
16155         return;
16156     }
16157
16158     creatingBook = TRUE;
16159     secondTime = FALSE;
16160
16161     /* Get list size */
16162     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16163         if(lg->position >= 0) {
16164             LoadGame(f, nItem, "", TRUE);
16165             AddGameToBook(TRUE);
16166             DoEvents();
16167         }
16168         lg = (ListGame *) lg->node.succ;
16169     }
16170
16171     creatingBook = FALSE;
16172     FlushBook();
16173 }
16174
16175 void
16176 BookEvent ()
16177 {
16178     if (appData.noChessProgram) return;
16179     switch (gameMode) {
16180       case MachinePlaysWhite:
16181         if (WhiteOnMove(forwardMostMove)) {
16182             DisplayError(_("Wait until your turn."), 0);
16183             return;
16184         }
16185         break;
16186       case BeginningOfGame:
16187       case MachinePlaysBlack:
16188         if (!WhiteOnMove(forwardMostMove)) {
16189             DisplayError(_("Wait until your turn."), 0);
16190             return;
16191         }
16192         break;
16193       case EditPosition:
16194         EditPositionDone(TRUE);
16195         break;
16196       case TwoMachinesPlay:
16197         return;
16198       default:
16199         break;
16200     }
16201     SendToProgram("bk\n", &first);
16202     bookOutput[0] = NULLCHAR;
16203     bookRequested = TRUE;
16204 }
16205
16206 void
16207 AboutGameEvent ()
16208 {
16209     char *tags = PGNTags(&gameInfo);
16210     TagsPopUp(tags, CmailMsg());
16211     free(tags);
16212 }
16213
16214 /* end button procedures */
16215
16216 void
16217 PrintPosition (FILE *fp, int move)
16218 {
16219     int i, j;
16220
16221     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16222         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16223             char c = PieceToChar(boards[move][i][j]);
16224             fputc(c == 'x' ? '.' : c, fp);
16225             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16226         }
16227     }
16228     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16229       fprintf(fp, "white to play\n");
16230     else
16231       fprintf(fp, "black to play\n");
16232 }
16233
16234 void
16235 PrintOpponents (FILE *fp)
16236 {
16237     if (gameInfo.white != NULL) {
16238         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16239     } else {
16240         fprintf(fp, "\n");
16241     }
16242 }
16243
16244 /* Find last component of program's own name, using some heuristics */
16245 void
16246 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16247 {
16248     char *p, *q, c;
16249     int local = (strcmp(host, "localhost") == 0);
16250     while (!local && (p = strchr(prog, ';')) != NULL) {
16251         p++;
16252         while (*p == ' ') p++;
16253         prog = p;
16254     }
16255     if (*prog == '"' || *prog == '\'') {
16256         q = strchr(prog + 1, *prog);
16257     } else {
16258         q = strchr(prog, ' ');
16259     }
16260     if (q == NULL) q = prog + strlen(prog);
16261     p = q;
16262     while (p >= prog && *p != '/' && *p != '\\') p--;
16263     p++;
16264     if(p == prog && *p == '"') p++;
16265     c = *q; *q = 0;
16266     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16267     memcpy(buf, p, q - p);
16268     buf[q - p] = NULLCHAR;
16269     if (!local) {
16270         strcat(buf, "@");
16271         strcat(buf, host);
16272     }
16273 }
16274
16275 char *
16276 TimeControlTagValue ()
16277 {
16278     char buf[MSG_SIZ];
16279     if (!appData.clockMode) {
16280       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16281     } else if (movesPerSession > 0) {
16282       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16283     } else if (timeIncrement == 0) {
16284       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16285     } else {
16286       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16287     }
16288     return StrSave(buf);
16289 }
16290
16291 void
16292 SetGameInfo ()
16293 {
16294     /* This routine is used only for certain modes */
16295     VariantClass v = gameInfo.variant;
16296     ChessMove r = GameUnfinished;
16297     char *p = NULL;
16298
16299     if(keepInfo) return;
16300
16301     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16302         r = gameInfo.result;
16303         p = gameInfo.resultDetails;
16304         gameInfo.resultDetails = NULL;
16305     }
16306     ClearGameInfo(&gameInfo);
16307     gameInfo.variant = v;
16308
16309     switch (gameMode) {
16310       case MachinePlaysWhite:
16311         gameInfo.event = StrSave( appData.pgnEventHeader );
16312         gameInfo.site = StrSave(HostName());
16313         gameInfo.date = PGNDate();
16314         gameInfo.round = StrSave("-");
16315         gameInfo.white = StrSave(first.tidy);
16316         gameInfo.black = StrSave(UserName());
16317         gameInfo.timeControl = TimeControlTagValue();
16318         break;
16319
16320       case MachinePlaysBlack:
16321         gameInfo.event = StrSave( appData.pgnEventHeader );
16322         gameInfo.site = StrSave(HostName());
16323         gameInfo.date = PGNDate();
16324         gameInfo.round = StrSave("-");
16325         gameInfo.white = StrSave(UserName());
16326         gameInfo.black = StrSave(first.tidy);
16327         gameInfo.timeControl = TimeControlTagValue();
16328         break;
16329
16330       case TwoMachinesPlay:
16331         gameInfo.event = StrSave( appData.pgnEventHeader );
16332         gameInfo.site = StrSave(HostName());
16333         gameInfo.date = PGNDate();
16334         if (roundNr > 0) {
16335             char buf[MSG_SIZ];
16336             snprintf(buf, MSG_SIZ, "%d", roundNr);
16337             gameInfo.round = StrSave(buf);
16338         } else {
16339             gameInfo.round = StrSave("-");
16340         }
16341         if (first.twoMachinesColor[0] == 'w') {
16342             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16343             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16344         } else {
16345             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16346             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16347         }
16348         gameInfo.timeControl = TimeControlTagValue();
16349         break;
16350
16351       case EditGame:
16352         gameInfo.event = StrSave("Edited game");
16353         gameInfo.site = StrSave(HostName());
16354         gameInfo.date = PGNDate();
16355         gameInfo.round = StrSave("-");
16356         gameInfo.white = StrSave("-");
16357         gameInfo.black = StrSave("-");
16358         gameInfo.result = r;
16359         gameInfo.resultDetails = p;
16360         break;
16361
16362       case EditPosition:
16363         gameInfo.event = StrSave("Edited position");
16364         gameInfo.site = StrSave(HostName());
16365         gameInfo.date = PGNDate();
16366         gameInfo.round = StrSave("-");
16367         gameInfo.white = StrSave("-");
16368         gameInfo.black = StrSave("-");
16369         break;
16370
16371       case IcsPlayingWhite:
16372       case IcsPlayingBlack:
16373       case IcsObserving:
16374       case IcsExamining:
16375         break;
16376
16377       case PlayFromGameFile:
16378         gameInfo.event = StrSave("Game from non-PGN file");
16379         gameInfo.site = StrSave(HostName());
16380         gameInfo.date = PGNDate();
16381         gameInfo.round = StrSave("-");
16382         gameInfo.white = StrSave("?");
16383         gameInfo.black = StrSave("?");
16384         break;
16385
16386       default:
16387         break;
16388     }
16389 }
16390
16391 void
16392 ReplaceComment (int index, char *text)
16393 {
16394     int len;
16395     char *p;
16396     float score;
16397
16398     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16399        pvInfoList[index-1].depth == len &&
16400        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16401        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16402     while (*text == '\n') text++;
16403     len = strlen(text);
16404     while (len > 0 && text[len - 1] == '\n') len--;
16405
16406     if (commentList[index] != NULL)
16407       free(commentList[index]);
16408
16409     if (len == 0) {
16410         commentList[index] = NULL;
16411         return;
16412     }
16413   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16414       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16415       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16416     commentList[index] = (char *) malloc(len + 2);
16417     strncpy(commentList[index], text, len);
16418     commentList[index][len] = '\n';
16419     commentList[index][len + 1] = NULLCHAR;
16420   } else {
16421     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16422     char *p;
16423     commentList[index] = (char *) malloc(len + 7);
16424     safeStrCpy(commentList[index], "{\n", 3);
16425     safeStrCpy(commentList[index]+2, text, len+1);
16426     commentList[index][len+2] = NULLCHAR;
16427     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16428     strcat(commentList[index], "\n}\n");
16429   }
16430 }
16431
16432 void
16433 CrushCRs (char *text)
16434 {
16435   char *p = text;
16436   char *q = text;
16437   char ch;
16438
16439   do {
16440     ch = *p++;
16441     if (ch == '\r') continue;
16442     *q++ = ch;
16443   } while (ch != '\0');
16444 }
16445
16446 void
16447 AppendComment (int index, char *text, Boolean addBraces)
16448 /* addBraces  tells if we should add {} */
16449 {
16450     int oldlen, len;
16451     char *old;
16452
16453 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16454     if(addBraces == 3) addBraces = 0; else // force appending literally
16455     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16456
16457     CrushCRs(text);
16458     while (*text == '\n') text++;
16459     len = strlen(text);
16460     while (len > 0 && text[len - 1] == '\n') len--;
16461     text[len] = NULLCHAR;
16462
16463     if (len == 0) return;
16464
16465     if (commentList[index] != NULL) {
16466       Boolean addClosingBrace = addBraces;
16467         old = commentList[index];
16468         oldlen = strlen(old);
16469         while(commentList[index][oldlen-1] ==  '\n')
16470           commentList[index][--oldlen] = NULLCHAR;
16471         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16472         safeStrCpy(commentList[index], old, oldlen + len + 6);
16473         free(old);
16474         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16475         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16476           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16477           while (*text == '\n') { text++; len--; }
16478           commentList[index][--oldlen] = NULLCHAR;
16479       }
16480         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16481         else          strcat(commentList[index], "\n");
16482         strcat(commentList[index], text);
16483         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16484         else          strcat(commentList[index], "\n");
16485     } else {
16486         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16487         if(addBraces)
16488           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16489         else commentList[index][0] = NULLCHAR;
16490         strcat(commentList[index], text);
16491         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16492         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16493     }
16494 }
16495
16496 static char *
16497 FindStr (char * text, char * sub_text)
16498 {
16499     char * result = strstr( text, sub_text );
16500
16501     if( result != NULL ) {
16502         result += strlen( sub_text );
16503     }
16504
16505     return result;
16506 }
16507
16508 /* [AS] Try to extract PV info from PGN comment */
16509 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16510 char *
16511 GetInfoFromComment (int index, char * text)
16512 {
16513     char * sep = text, *p;
16514
16515     if( text != NULL && index > 0 ) {
16516         int score = 0;
16517         int depth = 0;
16518         int time = -1, sec = 0, deci;
16519         char * s_eval = FindStr( text, "[%eval " );
16520         char * s_emt = FindStr( text, "[%emt " );
16521 #if 0
16522         if( s_eval != NULL || s_emt != NULL ) {
16523 #else
16524         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16525 #endif
16526             /* New style */
16527             char delim;
16528
16529             if( s_eval != NULL ) {
16530                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16531                     return text;
16532                 }
16533
16534                 if( delim != ']' ) {
16535                     return text;
16536                 }
16537             }
16538
16539             if( s_emt != NULL ) {
16540             }
16541                 return text;
16542         }
16543         else {
16544             /* We expect something like: [+|-]nnn.nn/dd */
16545             int score_lo = 0;
16546
16547             if(*text != '{') return text; // [HGM] braces: must be normal comment
16548
16549             sep = strchr( text, '/' );
16550             if( sep == NULL || sep < (text+4) ) {
16551                 return text;
16552             }
16553
16554             p = text;
16555             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16556             if(p[1] == '(') { // comment starts with PV
16557                p = strchr(p, ')'); // locate end of PV
16558                if(p == NULL || sep < p+5) return text;
16559                // at this point we have something like "{(.*) +0.23/6 ..."
16560                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16561                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16562                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16563             }
16564             time = -1; sec = -1; deci = -1;
16565             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16566                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16567                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16568                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16569                 return text;
16570             }
16571
16572             if( score_lo < 0 || score_lo >= 100 ) {
16573                 return text;
16574             }
16575
16576             if(sec >= 0) time = 600*time + 10*sec; else
16577             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16578
16579             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16580
16581             /* [HGM] PV time: now locate end of PV info */
16582             while( *++sep >= '0' && *sep <= '9'); // strip depth
16583             if(time >= 0)
16584             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16585             if(sec >= 0)
16586             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16587             if(deci >= 0)
16588             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16589             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16590         }
16591
16592         if( depth <= 0 ) {
16593             return text;
16594         }
16595
16596         if( time < 0 ) {
16597             time = -1;
16598         }
16599
16600         pvInfoList[index-1].depth = depth;
16601         pvInfoList[index-1].score = score;
16602         pvInfoList[index-1].time  = 10*time; // centi-sec
16603         if(*sep == '}') *sep = 0; else *--sep = '{';
16604         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16605     }
16606     return sep;
16607 }
16608
16609 void
16610 SendToProgram (char *message, ChessProgramState *cps)
16611 {
16612     int count, outCount, error;
16613     char buf[MSG_SIZ];
16614
16615     if (cps->pr == NoProc) return;
16616     Attention(cps);
16617
16618     if (appData.debugMode) {
16619         TimeMark now;
16620         GetTimeMark(&now);
16621         fprintf(debugFP, "%ld >%-6s: %s",
16622                 SubtractTimeMarks(&now, &programStartTime),
16623                 cps->which, message);
16624         if(serverFP)
16625             fprintf(serverFP, "%ld >%-6s: %s",
16626                 SubtractTimeMarks(&now, &programStartTime),
16627                 cps->which, message), fflush(serverFP);
16628     }
16629
16630     count = strlen(message);
16631     outCount = OutputToProcess(cps->pr, message, count, &error);
16632     if (outCount < count && !exiting
16633                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16634       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16635       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16636         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16637             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16638                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16639                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16640                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16641             } else {
16642                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16643                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16644                 gameInfo.result = res;
16645             }
16646             gameInfo.resultDetails = StrSave(buf);
16647         }
16648         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16649         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16650     }
16651 }
16652
16653 void
16654 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16655 {
16656     char *end_str;
16657     char buf[MSG_SIZ];
16658     ChessProgramState *cps = (ChessProgramState *)closure;
16659
16660     if (isr != cps->isr) return; /* Killed intentionally */
16661     if (count <= 0) {
16662         if (count == 0) {
16663             RemoveInputSource(cps->isr);
16664             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16665                     _(cps->which), cps->program);
16666             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16667             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16668                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16669                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16670                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16671                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16672                 } else {
16673                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16674                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16675                     gameInfo.result = res;
16676                 }
16677                 gameInfo.resultDetails = StrSave(buf);
16678             }
16679             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16680             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16681         } else {
16682             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16683                     _(cps->which), cps->program);
16684             RemoveInputSource(cps->isr);
16685
16686             /* [AS] Program is misbehaving badly... kill it */
16687             if( count == -2 ) {
16688                 DestroyChildProcess( cps->pr, 9 );
16689                 cps->pr = NoProc;
16690             }
16691
16692             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16693         }
16694         return;
16695     }
16696
16697     if ((end_str = strchr(message, '\r')) != NULL)
16698       *end_str = NULLCHAR;
16699     if ((end_str = strchr(message, '\n')) != NULL)
16700       *end_str = NULLCHAR;
16701
16702     if (appData.debugMode) {
16703         TimeMark now; int print = 1;
16704         char *quote = ""; char c; int i;
16705
16706         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16707                 char start = message[0];
16708                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16709                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16710                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16711                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16712                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16713                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16714                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16715                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16716                    sscanf(message, "hint: %c", &c)!=1 &&
16717                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16718                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16719                     print = (appData.engineComments >= 2);
16720                 }
16721                 message[0] = start; // restore original message
16722         }
16723         if(print) {
16724                 GetTimeMark(&now);
16725                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16726                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16727                         quote,
16728                         message);
16729                 if(serverFP)
16730                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16731                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16732                         quote,
16733                         message), fflush(serverFP);
16734         }
16735     }
16736
16737     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16738     if (appData.icsEngineAnalyze) {
16739         if (strstr(message, "whisper") != NULL ||
16740              strstr(message, "kibitz") != NULL ||
16741             strstr(message, "tellics") != NULL) return;
16742     }
16743
16744     HandleMachineMove(message, cps);
16745 }
16746
16747
16748 void
16749 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16750 {
16751     char buf[MSG_SIZ];
16752     int seconds;
16753
16754     if( timeControl_2 > 0 ) {
16755         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16756             tc = timeControl_2;
16757         }
16758     }
16759     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16760     inc /= cps->timeOdds;
16761     st  /= cps->timeOdds;
16762
16763     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16764
16765     if (st > 0) {
16766       /* Set exact time per move, normally using st command */
16767       if (cps->stKludge) {
16768         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16769         seconds = st % 60;
16770         if (seconds == 0) {
16771           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16772         } else {
16773           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16774         }
16775       } else {
16776         snprintf(buf, MSG_SIZ, "st %d\n", st);
16777       }
16778     } else {
16779       /* Set conventional or incremental time control, using level command */
16780       if (seconds == 0) {
16781         /* Note old gnuchess bug -- minutes:seconds used to not work.
16782            Fixed in later versions, but still avoid :seconds
16783            when seconds is 0. */
16784         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16785       } else {
16786         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16787                  seconds, inc/1000.);
16788       }
16789     }
16790     SendToProgram(buf, cps);
16791
16792     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16793     /* Orthogonally, limit search to given depth */
16794     if (sd > 0) {
16795       if (cps->sdKludge) {
16796         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16797       } else {
16798         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16799       }
16800       SendToProgram(buf, cps);
16801     }
16802
16803     if(cps->nps >= 0) { /* [HGM] nps */
16804         if(cps->supportsNPS == FALSE)
16805           cps->nps = -1; // don't use if engine explicitly says not supported!
16806         else {
16807           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16808           SendToProgram(buf, cps);
16809         }
16810     }
16811 }
16812
16813 ChessProgramState *
16814 WhitePlayer ()
16815 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16816 {
16817     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16818        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16819         return &second;
16820     return &first;
16821 }
16822
16823 void
16824 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16825 {
16826     char message[MSG_SIZ];
16827     long time, otime;
16828
16829     /* Note: this routine must be called when the clocks are stopped
16830        or when they have *just* been set or switched; otherwise
16831        it will be off by the time since the current tick started.
16832     */
16833     if (machineWhite) {
16834         time = whiteTimeRemaining / 10;
16835         otime = blackTimeRemaining / 10;
16836     } else {
16837         time = blackTimeRemaining / 10;
16838         otime = whiteTimeRemaining / 10;
16839     }
16840     /* [HGM] translate opponent's time by time-odds factor */
16841     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16842
16843     if (time <= 0) time = 1;
16844     if (otime <= 0) otime = 1;
16845
16846     snprintf(message, MSG_SIZ, "time %ld\n", time);
16847     SendToProgram(message, cps);
16848
16849     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16850     SendToProgram(message, cps);
16851 }
16852
16853 char *
16854 EngineDefinedVariant (ChessProgramState *cps, int n)
16855 {   // return name of n-th unknown variant that engine supports
16856     static char buf[MSG_SIZ];
16857     char *p, *s = cps->variants;
16858     if(!s) return NULL;
16859     do { // parse string from variants feature
16860       VariantClass v;
16861         p = strchr(s, ',');
16862         if(p) *p = NULLCHAR;
16863       v = StringToVariant(s);
16864       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16865         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16866             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16867                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16868                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16869                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16870             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16871         }
16872         if(p) *p++ = ',';
16873         if(n < 0) return buf;
16874     } while(s = p);
16875     return NULL;
16876 }
16877
16878 int
16879 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16880 {
16881   char buf[MSG_SIZ];
16882   int len = strlen(name);
16883   int val;
16884
16885   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16886     (*p) += len + 1;
16887     sscanf(*p, "%d", &val);
16888     *loc = (val != 0);
16889     while (**p && **p != ' ')
16890       (*p)++;
16891     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16892     SendToProgram(buf, cps);
16893     return TRUE;
16894   }
16895   return FALSE;
16896 }
16897
16898 int
16899 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16900 {
16901   char buf[MSG_SIZ];
16902   int len = strlen(name);
16903   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16904     (*p) += len + 1;
16905     sscanf(*p, "%d", loc);
16906     while (**p && **p != ' ') (*p)++;
16907     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16908     SendToProgram(buf, cps);
16909     return TRUE;
16910   }
16911   return FALSE;
16912 }
16913
16914 int
16915 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16916 {
16917   char buf[MSG_SIZ];
16918   int len = strlen(name);
16919   if (strncmp((*p), name, len) == 0
16920       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16921     (*p) += len + 2;
16922     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16923     sscanf(*p, "%[^\"]", *loc);
16924     while (**p && **p != '\"') (*p)++;
16925     if (**p == '\"') (*p)++;
16926     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16927     SendToProgram(buf, cps);
16928     return TRUE;
16929   }
16930   return FALSE;
16931 }
16932
16933 int
16934 ParseOption (Option *opt, ChessProgramState *cps)
16935 // [HGM] options: process the string that defines an engine option, and determine
16936 // name, type, default value, and allowed value range
16937 {
16938         char *p, *q, buf[MSG_SIZ];
16939         int n, min = (-1)<<31, max = 1<<31, def;
16940
16941         if(p = strstr(opt->name, " -spin ")) {
16942             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16943             if(max < min) max = min; // enforce consistency
16944             if(def < min) def = min;
16945             if(def > max) def = max;
16946             opt->value = def;
16947             opt->min = min;
16948             opt->max = max;
16949             opt->type = Spin;
16950         } else if((p = strstr(opt->name, " -slider "))) {
16951             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16952             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16953             if(max < min) max = min; // enforce consistency
16954             if(def < min) def = min;
16955             if(def > max) def = max;
16956             opt->value = def;
16957             opt->min = min;
16958             opt->max = max;
16959             opt->type = Spin; // Slider;
16960         } else if((p = strstr(opt->name, " -string "))) {
16961             opt->textValue = p+9;
16962             opt->type = TextBox;
16963         } else if((p = strstr(opt->name, " -file "))) {
16964             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16965             opt->textValue = p+7;
16966             opt->type = FileName; // FileName;
16967         } else if((p = strstr(opt->name, " -path "))) {
16968             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16969             opt->textValue = p+7;
16970             opt->type = PathName; // PathName;
16971         } else if(p = strstr(opt->name, " -check ")) {
16972             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16973             opt->value = (def != 0);
16974             opt->type = CheckBox;
16975         } else if(p = strstr(opt->name, " -combo ")) {
16976             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16977             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16978             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16979             opt->value = n = 0;
16980             while(q = StrStr(q, " /// ")) {
16981                 n++; *q = 0;    // count choices, and null-terminate each of them
16982                 q += 5;
16983                 if(*q == '*') { // remember default, which is marked with * prefix
16984                     q++;
16985                     opt->value = n;
16986                 }
16987                 cps->comboList[cps->comboCnt++] = q;
16988             }
16989             cps->comboList[cps->comboCnt++] = NULL;
16990             opt->max = n + 1;
16991             opt->type = ComboBox;
16992         } else if(p = strstr(opt->name, " -button")) {
16993             opt->type = Button;
16994         } else if(p = strstr(opt->name, " -save")) {
16995             opt->type = SaveButton;
16996         } else return FALSE;
16997         *p = 0; // terminate option name
16998         // now look if the command-line options define a setting for this engine option.
16999         if(cps->optionSettings && cps->optionSettings[0])
17000             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17001         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17002           snprintf(buf, MSG_SIZ, "option %s", p);
17003                 if(p = strstr(buf, ",")) *p = 0;
17004                 if(q = strchr(buf, '=')) switch(opt->type) {
17005                     case ComboBox:
17006                         for(n=0; n<opt->max; n++)
17007                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17008                         break;
17009                     case TextBox:
17010                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17011                         break;
17012                     case Spin:
17013                     case CheckBox:
17014                         opt->value = atoi(q+1);
17015                     default:
17016                         break;
17017                 }
17018                 strcat(buf, "\n");
17019                 SendToProgram(buf, cps);
17020         }
17021         return TRUE;
17022 }
17023
17024 void
17025 FeatureDone (ChessProgramState *cps, int val)
17026 {
17027   DelayedEventCallback cb = GetDelayedEvent();
17028   if ((cb == InitBackEnd3 && cps == &first) ||
17029       (cb == SettingsMenuIfReady && cps == &second) ||
17030       (cb == LoadEngine) ||
17031       (cb == TwoMachinesEventIfReady)) {
17032     CancelDelayedEvent();
17033     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17034   }
17035   cps->initDone = val;
17036   if(val) cps->reload = FALSE;
17037 }
17038
17039 /* Parse feature command from engine */
17040 void
17041 ParseFeatures (char *args, ChessProgramState *cps)
17042 {
17043   char *p = args;
17044   char *q = NULL;
17045   int val;
17046   char buf[MSG_SIZ];
17047
17048   for (;;) {
17049     while (*p == ' ') p++;
17050     if (*p == NULLCHAR) return;
17051
17052     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17053     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17054     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17055     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17056     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17057     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17058     if (BoolFeature(&p, "reuse", &val, cps)) {
17059       /* Engine can disable reuse, but can't enable it if user said no */
17060       if (!val) cps->reuse = FALSE;
17061       continue;
17062     }
17063     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17064     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17065       if (gameMode == TwoMachinesPlay) {
17066         DisplayTwoMachinesTitle();
17067       } else {
17068         DisplayTitle("");
17069       }
17070       continue;
17071     }
17072     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17073     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17074     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17075     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17076     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17077     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17078     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17079     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17080     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17081     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17082     if (IntFeature(&p, "done", &val, cps)) {
17083       FeatureDone(cps, val);
17084       continue;
17085     }
17086     /* Added by Tord: */
17087     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17088     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17089     /* End of additions by Tord */
17090
17091     /* [HGM] added features: */
17092     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17093     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17094     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17095     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17096     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17097     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17098     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17099     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17100         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17101         FREE(cps->option[cps->nrOptions].name);
17102         cps->option[cps->nrOptions].name = q; q = NULL;
17103         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17104           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17105             SendToProgram(buf, cps);
17106             continue;
17107         }
17108         if(cps->nrOptions >= MAX_OPTIONS) {
17109             cps->nrOptions--;
17110             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17111             DisplayError(buf, 0);
17112         }
17113         continue;
17114     }
17115     /* End of additions by HGM */
17116
17117     /* unknown feature: complain and skip */
17118     q = p;
17119     while (*q && *q != '=') q++;
17120     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17121     SendToProgram(buf, cps);
17122     p = q;
17123     if (*p == '=') {
17124       p++;
17125       if (*p == '\"') {
17126         p++;
17127         while (*p && *p != '\"') p++;
17128         if (*p == '\"') p++;
17129       } else {
17130         while (*p && *p != ' ') p++;
17131       }
17132     }
17133   }
17134
17135 }
17136
17137 void
17138 PeriodicUpdatesEvent (int newState)
17139 {
17140     if (newState == appData.periodicUpdates)
17141       return;
17142
17143     appData.periodicUpdates=newState;
17144
17145     /* Display type changes, so update it now */
17146 //    DisplayAnalysis();
17147
17148     /* Get the ball rolling again... */
17149     if (newState) {
17150         AnalysisPeriodicEvent(1);
17151         StartAnalysisClock();
17152     }
17153 }
17154
17155 void
17156 PonderNextMoveEvent (int newState)
17157 {
17158     if (newState == appData.ponderNextMove) return;
17159     if (gameMode == EditPosition) EditPositionDone(TRUE);
17160     if (newState) {
17161         SendToProgram("hard\n", &first);
17162         if (gameMode == TwoMachinesPlay) {
17163             SendToProgram("hard\n", &second);
17164         }
17165     } else {
17166         SendToProgram("easy\n", &first);
17167         thinkOutput[0] = NULLCHAR;
17168         if (gameMode == TwoMachinesPlay) {
17169             SendToProgram("easy\n", &second);
17170         }
17171     }
17172     appData.ponderNextMove = newState;
17173 }
17174
17175 void
17176 NewSettingEvent (int option, int *feature, char *command, int value)
17177 {
17178     char buf[MSG_SIZ];
17179
17180     if (gameMode == EditPosition) EditPositionDone(TRUE);
17181     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17182     if(feature == NULL || *feature) SendToProgram(buf, &first);
17183     if (gameMode == TwoMachinesPlay) {
17184         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17185     }
17186 }
17187
17188 void
17189 ShowThinkingEvent ()
17190 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17191 {
17192     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17193     int newState = appData.showThinking
17194         // [HGM] thinking: other features now need thinking output as well
17195         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17196
17197     if (oldState == newState) return;
17198     oldState = newState;
17199     if (gameMode == EditPosition) EditPositionDone(TRUE);
17200     if (oldState) {
17201         SendToProgram("post\n", &first);
17202         if (gameMode == TwoMachinesPlay) {
17203             SendToProgram("post\n", &second);
17204         }
17205     } else {
17206         SendToProgram("nopost\n", &first);
17207         thinkOutput[0] = NULLCHAR;
17208         if (gameMode == TwoMachinesPlay) {
17209             SendToProgram("nopost\n", &second);
17210         }
17211     }
17212 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17213 }
17214
17215 void
17216 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17217 {
17218   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17219   if (pr == NoProc) return;
17220   AskQuestion(title, question, replyPrefix, pr);
17221 }
17222
17223 void
17224 TypeInEvent (char firstChar)
17225 {
17226     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17227         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17228         gameMode == AnalyzeMode || gameMode == EditGame ||
17229         gameMode == EditPosition || gameMode == IcsExamining ||
17230         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17231         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17232                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17233                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17234         gameMode == Training) PopUpMoveDialog(firstChar);
17235 }
17236
17237 void
17238 TypeInDoneEvent (char *move)
17239 {
17240         Board board;
17241         int n, fromX, fromY, toX, toY;
17242         char promoChar;
17243         ChessMove moveType;
17244
17245         // [HGM] FENedit
17246         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17247                 EditPositionPasteFEN(move);
17248                 return;
17249         }
17250         // [HGM] movenum: allow move number to be typed in any mode
17251         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17252           ToNrEvent(2*n-1);
17253           return;
17254         }
17255         // undocumented kludge: allow command-line option to be typed in!
17256         // (potentially fatal, and does not implement the effect of the option.)
17257         // should only be used for options that are values on which future decisions will be made,
17258         // and definitely not on options that would be used during initialization.
17259         if(strstr(move, "!!! -") == move) {
17260             ParseArgsFromString(move+4);
17261             return;
17262         }
17263
17264       if (gameMode != EditGame && currentMove != forwardMostMove &&
17265         gameMode != Training) {
17266         DisplayMoveError(_("Displayed move is not current"));
17267       } else {
17268         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17269           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17270         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17271         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17272           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17273           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17274         } else {
17275           DisplayMoveError(_("Could not parse move"));
17276         }
17277       }
17278 }
17279
17280 void
17281 DisplayMove (int moveNumber)
17282 {
17283     char message[MSG_SIZ];
17284     char res[MSG_SIZ];
17285     char cpThinkOutput[MSG_SIZ];
17286
17287     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17288
17289     if (moveNumber == forwardMostMove - 1 ||
17290         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17291
17292         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17293
17294         if (strchr(cpThinkOutput, '\n')) {
17295             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17296         }
17297     } else {
17298         *cpThinkOutput = NULLCHAR;
17299     }
17300
17301     /* [AS] Hide thinking from human user */
17302     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17303         *cpThinkOutput = NULLCHAR;
17304         if( thinkOutput[0] != NULLCHAR ) {
17305             int i;
17306
17307             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17308                 cpThinkOutput[i] = '.';
17309             }
17310             cpThinkOutput[i] = NULLCHAR;
17311             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17312         }
17313     }
17314
17315     if (moveNumber == forwardMostMove - 1 &&
17316         gameInfo.resultDetails != NULL) {
17317         if (gameInfo.resultDetails[0] == NULLCHAR) {
17318           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17319         } else {
17320           snprintf(res, MSG_SIZ, " {%s} %s",
17321                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17322         }
17323     } else {
17324         res[0] = NULLCHAR;
17325     }
17326
17327     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17328         DisplayMessage(res, cpThinkOutput);
17329     } else {
17330       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17331                 WhiteOnMove(moveNumber) ? " " : ".. ",
17332                 parseList[moveNumber], res);
17333         DisplayMessage(message, cpThinkOutput);
17334     }
17335 }
17336
17337 void
17338 DisplayComment (int moveNumber, char *text)
17339 {
17340     char title[MSG_SIZ];
17341
17342     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17343       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17344     } else {
17345       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17346               WhiteOnMove(moveNumber) ? " " : ".. ",
17347               parseList[moveNumber]);
17348     }
17349     if (text != NULL && (appData.autoDisplayComment || commentUp))
17350         CommentPopUp(title, text);
17351 }
17352
17353 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17354  * might be busy thinking or pondering.  It can be omitted if your
17355  * gnuchess is configured to stop thinking immediately on any user
17356  * input.  However, that gnuchess feature depends on the FIONREAD
17357  * ioctl, which does not work properly on some flavors of Unix.
17358  */
17359 void
17360 Attention (ChessProgramState *cps)
17361 {
17362 #if ATTENTION
17363     if (!cps->useSigint) return;
17364     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17365     switch (gameMode) {
17366       case MachinePlaysWhite:
17367       case MachinePlaysBlack:
17368       case TwoMachinesPlay:
17369       case IcsPlayingWhite:
17370       case IcsPlayingBlack:
17371       case AnalyzeMode:
17372       case AnalyzeFile:
17373         /* Skip if we know it isn't thinking */
17374         if (!cps->maybeThinking) return;
17375         if (appData.debugMode)
17376           fprintf(debugFP, "Interrupting %s\n", cps->which);
17377         InterruptChildProcess(cps->pr);
17378         cps->maybeThinking = FALSE;
17379         break;
17380       default:
17381         break;
17382     }
17383 #endif /*ATTENTION*/
17384 }
17385
17386 int
17387 CheckFlags ()
17388 {
17389     if (whiteTimeRemaining <= 0) {
17390         if (!whiteFlag) {
17391             whiteFlag = TRUE;
17392             if (appData.icsActive) {
17393                 if (appData.autoCallFlag &&
17394                     gameMode == IcsPlayingBlack && !blackFlag) {
17395                   SendToICS(ics_prefix);
17396                   SendToICS("flag\n");
17397                 }
17398             } else {
17399                 if (blackFlag) {
17400                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17401                 } else {
17402                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17403                     if (appData.autoCallFlag) {
17404                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17405                         return TRUE;
17406                     }
17407                 }
17408             }
17409         }
17410     }
17411     if (blackTimeRemaining <= 0) {
17412         if (!blackFlag) {
17413             blackFlag = TRUE;
17414             if (appData.icsActive) {
17415                 if (appData.autoCallFlag &&
17416                     gameMode == IcsPlayingWhite && !whiteFlag) {
17417                   SendToICS(ics_prefix);
17418                   SendToICS("flag\n");
17419                 }
17420             } else {
17421                 if (whiteFlag) {
17422                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17423                 } else {
17424                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17425                     if (appData.autoCallFlag) {
17426                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17427                         return TRUE;
17428                     }
17429                 }
17430             }
17431         }
17432     }
17433     return FALSE;
17434 }
17435
17436 void
17437 CheckTimeControl ()
17438 {
17439     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17440         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17441
17442     /*
17443      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17444      */
17445     if ( !WhiteOnMove(forwardMostMove) ) {
17446         /* White made time control */
17447         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17448         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17449         /* [HGM] time odds: correct new time quota for time odds! */
17450                                             / WhitePlayer()->timeOdds;
17451         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17452     } else {
17453         lastBlack -= blackTimeRemaining;
17454         /* Black made time control */
17455         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17456                                             / WhitePlayer()->other->timeOdds;
17457         lastWhite = whiteTimeRemaining;
17458     }
17459 }
17460
17461 void
17462 DisplayBothClocks ()
17463 {
17464     int wom = gameMode == EditPosition ?
17465       !blackPlaysFirst : WhiteOnMove(currentMove);
17466     DisplayWhiteClock(whiteTimeRemaining, wom);
17467     DisplayBlackClock(blackTimeRemaining, !wom);
17468 }
17469
17470
17471 /* Timekeeping seems to be a portability nightmare.  I think everyone
17472    has ftime(), but I'm really not sure, so I'm including some ifdefs
17473    to use other calls if you don't.  Clocks will be less accurate if
17474    you have neither ftime nor gettimeofday.
17475 */
17476
17477 /* VS 2008 requires the #include outside of the function */
17478 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17479 #include <sys/timeb.h>
17480 #endif
17481
17482 /* Get the current time as a TimeMark */
17483 void
17484 GetTimeMark (TimeMark *tm)
17485 {
17486 #if HAVE_GETTIMEOFDAY
17487
17488     struct timeval timeVal;
17489     struct timezone timeZone;
17490
17491     gettimeofday(&timeVal, &timeZone);
17492     tm->sec = (long) timeVal.tv_sec;
17493     tm->ms = (int) (timeVal.tv_usec / 1000L);
17494
17495 #else /*!HAVE_GETTIMEOFDAY*/
17496 #if HAVE_FTIME
17497
17498 // include <sys/timeb.h> / moved to just above start of function
17499     struct timeb timeB;
17500
17501     ftime(&timeB);
17502     tm->sec = (long) timeB.time;
17503     tm->ms = (int) timeB.millitm;
17504
17505 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17506     tm->sec = (long) time(NULL);
17507     tm->ms = 0;
17508 #endif
17509 #endif
17510 }
17511
17512 /* Return the difference in milliseconds between two
17513    time marks.  We assume the difference will fit in a long!
17514 */
17515 long
17516 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17517 {
17518     return 1000L*(tm2->sec - tm1->sec) +
17519            (long) (tm2->ms - tm1->ms);
17520 }
17521
17522
17523 /*
17524  * Code to manage the game clocks.
17525  *
17526  * In tournament play, black starts the clock and then white makes a move.
17527  * We give the human user a slight advantage if he is playing white---the
17528  * clocks don't run until he makes his first move, so it takes zero time.
17529  * Also, we don't account for network lag, so we could get out of sync
17530  * with GNU Chess's clock -- but then, referees are always right.
17531  */
17532
17533 static TimeMark tickStartTM;
17534 static long intendedTickLength;
17535
17536 long
17537 NextTickLength (long timeRemaining)
17538 {
17539     long nominalTickLength, nextTickLength;
17540
17541     if (timeRemaining > 0L && timeRemaining <= 10000L)
17542       nominalTickLength = 100L;
17543     else
17544       nominalTickLength = 1000L;
17545     nextTickLength = timeRemaining % nominalTickLength;
17546     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17547
17548     return nextTickLength;
17549 }
17550
17551 /* Adjust clock one minute up or down */
17552 void
17553 AdjustClock (Boolean which, int dir)
17554 {
17555     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17556     if(which) blackTimeRemaining += 60000*dir;
17557     else      whiteTimeRemaining += 60000*dir;
17558     DisplayBothClocks();
17559     adjustedClock = TRUE;
17560 }
17561
17562 /* Stop clocks and reset to a fresh time control */
17563 void
17564 ResetClocks ()
17565 {
17566     (void) StopClockTimer();
17567     if (appData.icsActive) {
17568         whiteTimeRemaining = blackTimeRemaining = 0;
17569     } else if (searchTime) {
17570         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17571         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17572     } else { /* [HGM] correct new time quote for time odds */
17573         whiteTC = blackTC = fullTimeControlString;
17574         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17575         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17576     }
17577     if (whiteFlag || blackFlag) {
17578         DisplayTitle("");
17579         whiteFlag = blackFlag = FALSE;
17580     }
17581     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17582     DisplayBothClocks();
17583     adjustedClock = FALSE;
17584 }
17585
17586 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17587
17588 /* Decrement running clock by amount of time that has passed */
17589 void
17590 DecrementClocks ()
17591 {
17592     long timeRemaining;
17593     long lastTickLength, fudge;
17594     TimeMark now;
17595
17596     if (!appData.clockMode) return;
17597     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17598
17599     GetTimeMark(&now);
17600
17601     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17602
17603     /* Fudge if we woke up a little too soon */
17604     fudge = intendedTickLength - lastTickLength;
17605     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17606
17607     if (WhiteOnMove(forwardMostMove)) {
17608         if(whiteNPS >= 0) lastTickLength = 0;
17609         timeRemaining = whiteTimeRemaining -= lastTickLength;
17610         if(timeRemaining < 0 && !appData.icsActive) {
17611             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17612             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17613                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17614                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17615             }
17616         }
17617         DisplayWhiteClock(whiteTimeRemaining - fudge,
17618                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17619     } else {
17620         if(blackNPS >= 0) lastTickLength = 0;
17621         timeRemaining = blackTimeRemaining -= lastTickLength;
17622         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17623             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17624             if(suddenDeath) {
17625                 blackStartMove = forwardMostMove;
17626                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17627             }
17628         }
17629         DisplayBlackClock(blackTimeRemaining - fudge,
17630                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17631     }
17632     if (CheckFlags()) return;
17633
17634     if(twoBoards) { // count down secondary board's clocks as well
17635         activePartnerTime -= lastTickLength;
17636         partnerUp = 1;
17637         if(activePartner == 'W')
17638             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17639         else
17640             DisplayBlackClock(activePartnerTime, TRUE);
17641         partnerUp = 0;
17642     }
17643
17644     tickStartTM = now;
17645     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17646     StartClockTimer(intendedTickLength);
17647
17648     /* if the time remaining has fallen below the alarm threshold, sound the
17649      * alarm. if the alarm has sounded and (due to a takeback or time control
17650      * with increment) the time remaining has increased to a level above the
17651      * threshold, reset the alarm so it can sound again.
17652      */
17653
17654     if (appData.icsActive && appData.icsAlarm) {
17655
17656         /* make sure we are dealing with the user's clock */
17657         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17658                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17659            )) return;
17660
17661         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17662             alarmSounded = FALSE;
17663         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17664             PlayAlarmSound();
17665             alarmSounded = TRUE;
17666         }
17667     }
17668 }
17669
17670
17671 /* A player has just moved, so stop the previously running
17672    clock and (if in clock mode) start the other one.
17673    We redisplay both clocks in case we're in ICS mode, because
17674    ICS gives us an update to both clocks after every move.
17675    Note that this routine is called *after* forwardMostMove
17676    is updated, so the last fractional tick must be subtracted
17677    from the color that is *not* on move now.
17678 */
17679 void
17680 SwitchClocks (int newMoveNr)
17681 {
17682     long lastTickLength;
17683     TimeMark now;
17684     int flagged = FALSE;
17685
17686     GetTimeMark(&now);
17687
17688     if (StopClockTimer() && appData.clockMode) {
17689         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17690         if (!WhiteOnMove(forwardMostMove)) {
17691             if(blackNPS >= 0) lastTickLength = 0;
17692             blackTimeRemaining -= lastTickLength;
17693            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17694 //         if(pvInfoList[forwardMostMove].time == -1)
17695                  pvInfoList[forwardMostMove].time =               // use GUI time
17696                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17697         } else {
17698            if(whiteNPS >= 0) lastTickLength = 0;
17699            whiteTimeRemaining -= lastTickLength;
17700            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17701 //         if(pvInfoList[forwardMostMove].time == -1)
17702                  pvInfoList[forwardMostMove].time =
17703                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17704         }
17705         flagged = CheckFlags();
17706     }
17707     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17708     CheckTimeControl();
17709
17710     if (flagged || !appData.clockMode) return;
17711
17712     switch (gameMode) {
17713       case MachinePlaysBlack:
17714       case MachinePlaysWhite:
17715       case BeginningOfGame:
17716         if (pausing) return;
17717         break;
17718
17719       case EditGame:
17720       case PlayFromGameFile:
17721       case IcsExamining:
17722         return;
17723
17724       default:
17725         break;
17726     }
17727
17728     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17729         if(WhiteOnMove(forwardMostMove))
17730              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17731         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17732     }
17733
17734     tickStartTM = now;
17735     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17736       whiteTimeRemaining : blackTimeRemaining);
17737     StartClockTimer(intendedTickLength);
17738 }
17739
17740
17741 /* Stop both clocks */
17742 void
17743 StopClocks ()
17744 {
17745     long lastTickLength;
17746     TimeMark now;
17747
17748     if (!StopClockTimer()) return;
17749     if (!appData.clockMode) return;
17750
17751     GetTimeMark(&now);
17752
17753     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17754     if (WhiteOnMove(forwardMostMove)) {
17755         if(whiteNPS >= 0) lastTickLength = 0;
17756         whiteTimeRemaining -= lastTickLength;
17757         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17758     } else {
17759         if(blackNPS >= 0) lastTickLength = 0;
17760         blackTimeRemaining -= lastTickLength;
17761         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17762     }
17763     CheckFlags();
17764 }
17765
17766 /* Start clock of player on move.  Time may have been reset, so
17767    if clock is already running, stop and restart it. */
17768 void
17769 StartClocks ()
17770 {
17771     (void) StopClockTimer(); /* in case it was running already */
17772     DisplayBothClocks();
17773     if (CheckFlags()) return;
17774
17775     if (!appData.clockMode) return;
17776     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17777
17778     GetTimeMark(&tickStartTM);
17779     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17780       whiteTimeRemaining : blackTimeRemaining);
17781
17782    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17783     whiteNPS = blackNPS = -1;
17784     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17785        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17786         whiteNPS = first.nps;
17787     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17788        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17789         blackNPS = first.nps;
17790     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17791         whiteNPS = second.nps;
17792     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17793         blackNPS = second.nps;
17794     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17795
17796     StartClockTimer(intendedTickLength);
17797 }
17798
17799 char *
17800 TimeString (long ms)
17801 {
17802     long second, minute, hour, day;
17803     char *sign = "";
17804     static char buf[32];
17805
17806     if (ms > 0 && ms <= 9900) {
17807       /* convert milliseconds to tenths, rounding up */
17808       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17809
17810       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17811       return buf;
17812     }
17813
17814     /* convert milliseconds to seconds, rounding up */
17815     /* use floating point to avoid strangeness of integer division
17816        with negative dividends on many machines */
17817     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17818
17819     if (second < 0) {
17820         sign = "-";
17821         second = -second;
17822     }
17823
17824     day = second / (60 * 60 * 24);
17825     second = second % (60 * 60 * 24);
17826     hour = second / (60 * 60);
17827     second = second % (60 * 60);
17828     minute = second / 60;
17829     second = second % 60;
17830
17831     if (day > 0)
17832       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17833               sign, day, hour, minute, second);
17834     else if (hour > 0)
17835       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17836     else
17837       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17838
17839     return buf;
17840 }
17841
17842
17843 /*
17844  * This is necessary because some C libraries aren't ANSI C compliant yet.
17845  */
17846 char *
17847 StrStr (char *string, char *match)
17848 {
17849     int i, length;
17850
17851     length = strlen(match);
17852
17853     for (i = strlen(string) - length; i >= 0; i--, string++)
17854       if (!strncmp(match, string, length))
17855         return string;
17856
17857     return NULL;
17858 }
17859
17860 char *
17861 StrCaseStr (char *string, char *match)
17862 {
17863     int i, j, length;
17864
17865     length = strlen(match);
17866
17867     for (i = strlen(string) - length; i >= 0; i--, string++) {
17868         for (j = 0; j < length; j++) {
17869             if (ToLower(match[j]) != ToLower(string[j]))
17870               break;
17871         }
17872         if (j == length) return string;
17873     }
17874
17875     return NULL;
17876 }
17877
17878 #ifndef _amigados
17879 int
17880 StrCaseCmp (char *s1, char *s2)
17881 {
17882     char c1, c2;
17883
17884     for (;;) {
17885         c1 = ToLower(*s1++);
17886         c2 = ToLower(*s2++);
17887         if (c1 > c2) return 1;
17888         if (c1 < c2) return -1;
17889         if (c1 == NULLCHAR) return 0;
17890     }
17891 }
17892
17893
17894 int
17895 ToLower (int c)
17896 {
17897     return isupper(c) ? tolower(c) : c;
17898 }
17899
17900
17901 int
17902 ToUpper (int c)
17903 {
17904     return islower(c) ? toupper(c) : c;
17905 }
17906 #endif /* !_amigados    */
17907
17908 char *
17909 StrSave (char *s)
17910 {
17911   char *ret;
17912
17913   if ((ret = (char *) malloc(strlen(s) + 1)))
17914     {
17915       safeStrCpy(ret, s, strlen(s)+1);
17916     }
17917   return ret;
17918 }
17919
17920 char *
17921 StrSavePtr (char *s, char **savePtr)
17922 {
17923     if (*savePtr) {
17924         free(*savePtr);
17925     }
17926     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17927       safeStrCpy(*savePtr, s, strlen(s)+1);
17928     }
17929     return(*savePtr);
17930 }
17931
17932 char *
17933 PGNDate ()
17934 {
17935     time_t clock;
17936     struct tm *tm;
17937     char buf[MSG_SIZ];
17938
17939     clock = time((time_t *)NULL);
17940     tm = localtime(&clock);
17941     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17942             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17943     return StrSave(buf);
17944 }
17945
17946
17947 char *
17948 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17949 {
17950     int i, j, fromX, fromY, toX, toY;
17951     int whiteToPlay, haveRights = nrCastlingRights;
17952     char buf[MSG_SIZ];
17953     char *p, *q;
17954     int emptycount;
17955     ChessSquare piece;
17956
17957     whiteToPlay = (gameMode == EditPosition) ?
17958       !blackPlaysFirst : (move % 2 == 0);
17959     p = buf;
17960
17961     /* Piece placement data */
17962     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17963         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17964         emptycount = 0;
17965         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17966             if (boards[move][i][j] == EmptySquare) {
17967                 emptycount++;
17968             } else { ChessSquare piece = boards[move][i][j];
17969                 if (emptycount > 0) {
17970                     if(emptycount<10) /* [HGM] can be >= 10 */
17971                         *p++ = '0' + emptycount;
17972                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17973                     emptycount = 0;
17974                 }
17975                 if(PieceToChar(piece) == '+') {
17976                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17977                     *p++ = '+';
17978                     piece = (ChessSquare)(CHUDEMOTED piece);
17979                 }
17980                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17981                 if(*p = PieceSuffix(piece)) p++;
17982                 if(p[-1] == '~') {
17983                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17984                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17985                     *p++ = '~';
17986                 }
17987             }
17988         }
17989         if (emptycount > 0) {
17990             if(emptycount<10) /* [HGM] can be >= 10 */
17991                 *p++ = '0' + emptycount;
17992             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17993             emptycount = 0;
17994         }
17995         *p++ = '/';
17996     }
17997     *(p - 1) = ' ';
17998
17999     /* [HGM] print Crazyhouse or Shogi holdings */
18000     if( gameInfo.holdingsWidth ) {
18001         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18002         q = p;
18003         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18004             piece = boards[move][i][BOARD_WIDTH-1];
18005             if( piece != EmptySquare )
18006               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18007                   *p++ = PieceToChar(piece);
18008         }
18009         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18010             piece = boards[move][BOARD_HEIGHT-i-1][0];
18011             if( piece != EmptySquare )
18012               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18013                   *p++ = PieceToChar(piece);
18014         }
18015
18016         if( q == p ) *p++ = '-';
18017         *p++ = ']';
18018         *p++ = ' ';
18019     }
18020
18021     /* Active color */
18022     *p++ = whiteToPlay ? 'w' : 'b';
18023     *p++ = ' ';
18024
18025   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18026     haveRights = 0; q = p;
18027     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18028       piece = boards[move][0][i];
18029       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18030         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18031       }
18032     }
18033     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18034       piece = boards[move][BOARD_HEIGHT-1][i];
18035       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18036         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18037       }
18038     }
18039     if(p == q) *p++ = '-';
18040     *p++ = ' ';
18041   }
18042
18043   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18044     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18045   } else {
18046   if(haveRights) {
18047      int handW=0, handB=0;
18048      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18049         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18050         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18051      }
18052      q = p;
18053      if(appData.fischerCastling) {
18054         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18055            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18056                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18057         } else {
18058        /* [HGM] write directly from rights */
18059            if(boards[move][CASTLING][2] != NoRights &&
18060               boards[move][CASTLING][0] != NoRights   )
18061                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18062            if(boards[move][CASTLING][2] != NoRights &&
18063               boards[move][CASTLING][1] != NoRights   )
18064                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18065         }
18066         if(handB) {
18067            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18068                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18069         } else {
18070            if(boards[move][CASTLING][5] != NoRights &&
18071               boards[move][CASTLING][3] != NoRights   )
18072                 *p++ = boards[move][CASTLING][3] + AAA;
18073            if(boards[move][CASTLING][5] != NoRights &&
18074               boards[move][CASTLING][4] != NoRights   )
18075                 *p++ = boards[move][CASTLING][4] + AAA;
18076         }
18077      } else {
18078
18079         /* [HGM] write true castling rights */
18080         if( nrCastlingRights == 6 ) {
18081             int q, k=0;
18082             if(boards[move][CASTLING][0] != NoRights &&
18083                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18084             q = (boards[move][CASTLING][1] != NoRights &&
18085                  boards[move][CASTLING][2] != NoRights  );
18086             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18087                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18088                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18089                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18090             }
18091             if(q) *p++ = 'Q';
18092             k = 0;
18093             if(boards[move][CASTLING][3] != NoRights &&
18094                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18095             q = (boards[move][CASTLING][4] != NoRights &&
18096                  boards[move][CASTLING][5] != NoRights  );
18097             if(handB) {
18098                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18099                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18100                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18101             }
18102             if(q) *p++ = 'q';
18103         }
18104      }
18105      if (q == p) *p++ = '-'; /* No castling rights */
18106      *p++ = ' ';
18107   }
18108
18109   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18110      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18111      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18112     /* En passant target square */
18113     if (move > backwardMostMove) {
18114         fromX = moveList[move - 1][0] - AAA;
18115         fromY = moveList[move - 1][1] - ONE;
18116         toX = moveList[move - 1][2] - AAA;
18117         toY = moveList[move - 1][3] - ONE;
18118         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18119             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18120             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18121             fromX == toX) {
18122             /* 2-square pawn move just happened */
18123             *p++ = toX + AAA;
18124             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18125         } else {
18126             *p++ = '-';
18127         }
18128     } else if(move == backwardMostMove) {
18129         // [HGM] perhaps we should always do it like this, and forget the above?
18130         if((signed char)boards[move][EP_STATUS] >= 0) {
18131             *p++ = boards[move][EP_STATUS] + AAA;
18132             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18133         } else {
18134             *p++ = '-';
18135         }
18136     } else {
18137         *p++ = '-';
18138     }
18139     *p++ = ' ';
18140   }
18141   }
18142
18143     if(moveCounts)
18144     {   int i = 0, j=move;
18145
18146         /* [HGM] find reversible plies */
18147         if (appData.debugMode) { int k;
18148             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18149             for(k=backwardMostMove; k<=forwardMostMove; k++)
18150                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18151
18152         }
18153
18154         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18155         if( j == backwardMostMove ) i += initialRulePlies;
18156         sprintf(p, "%d ", i);
18157         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18158
18159         /* Fullmove number */
18160         sprintf(p, "%d", (move / 2) + 1);
18161     } else *--p = NULLCHAR;
18162
18163     return StrSave(buf);
18164 }
18165
18166 Boolean
18167 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18168 {
18169     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18170     char *p, c;
18171     int emptycount, virgin[BOARD_FILES];
18172     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18173
18174     p = fen;
18175
18176     /* Piece placement data */
18177     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18178         j = 0;
18179         for (;;) {
18180             if (*p == '/' || *p == ' ' || *p == '[' ) {
18181                 if(j > w) w = j;
18182                 emptycount = gameInfo.boardWidth - j;
18183                 while (emptycount--)
18184                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18185                 if (*p == '/') p++;
18186                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18187                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18188                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18189                     }
18190                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18191                 }
18192                 break;
18193 #if(BOARD_FILES >= 10)*0
18194             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18195                 p++; emptycount=10;
18196                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18197                 while (emptycount--)
18198                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18199 #endif
18200             } else if (*p == '*') {
18201                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18202             } else if (isdigit(*p)) {
18203                 emptycount = *p++ - '0';
18204                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18205                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18206                 while (emptycount--)
18207                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18208             } else if (*p == '<') {
18209                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18210                 else if (i != 0 || !shuffle) return FALSE;
18211                 p++;
18212             } else if (shuffle && *p == '>') {
18213                 p++; // for now ignore closing shuffle range, and assume rank-end
18214             } else if (*p == '?') {
18215                 if (j >= gameInfo.boardWidth) return FALSE;
18216                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18217                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18218             } else if (*p == '+' || isalpha(*p)) {
18219                 char *q, *s = SUFFIXES;
18220                 if (j >= gameInfo.boardWidth) return FALSE;
18221                 if(*p=='+') {
18222                     char c = *++p;
18223                     if(q = strchr(s, p[1])) p++;
18224                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18225                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18226                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18227                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18228                 } else {
18229                     char c = *p++;
18230                     if(q = strchr(s, *p)) p++;
18231                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18232                 }
18233
18234                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18235                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18236                     piece = (ChessSquare) (PROMOTED piece);
18237                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18238                     p++;
18239                 }
18240                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18241                 if(piece == king) wKingRank = i;
18242                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18243             } else {
18244                 return FALSE;
18245             }
18246         }
18247     }
18248     while (*p == '/' || *p == ' ') p++;
18249
18250     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18251
18252     /* [HGM] by default clear Crazyhouse holdings, if present */
18253     if(gameInfo.holdingsWidth) {
18254        for(i=0; i<BOARD_HEIGHT; i++) {
18255            board[i][0]             = EmptySquare; /* black holdings */
18256            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18257            board[i][1]             = (ChessSquare) 0; /* black counts */
18258            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18259        }
18260     }
18261
18262     /* [HGM] look for Crazyhouse holdings here */
18263     while(*p==' ') p++;
18264     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18265         int swap=0, wcnt=0, bcnt=0;
18266         if(*p == '[') p++;
18267         if(*p == '<') swap++, p++;
18268         if(*p == '-' ) p++; /* empty holdings */ else {
18269             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18270             /* if we would allow FEN reading to set board size, we would   */
18271             /* have to add holdings and shift the board read so far here   */
18272             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18273                 p++;
18274                 if((int) piece >= (int) BlackPawn ) {
18275                     i = (int)piece - (int)BlackPawn;
18276                     i = PieceToNumber((ChessSquare)i);
18277                     if( i >= gameInfo.holdingsSize ) return FALSE;
18278                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18279                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18280                     bcnt++;
18281                 } else {
18282                     i = (int)piece - (int)WhitePawn;
18283                     i = PieceToNumber((ChessSquare)i);
18284                     if( i >= gameInfo.holdingsSize ) return FALSE;
18285                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18286                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18287                     wcnt++;
18288                 }
18289             }
18290             if(subst) { // substitute back-rank question marks by holdings pieces
18291                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18292                     int k, m, n = bcnt + 1;
18293                     if(board[0][j] == ClearBoard) {
18294                         if(!wcnt) return FALSE;
18295                         n = rand() % wcnt;
18296                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18297                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18298                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18299                             break;
18300                         }
18301                     }
18302                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18303                         if(!bcnt) return FALSE;
18304                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18305                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18306                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18307                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18308                             break;
18309                         }
18310                     }
18311                 }
18312                 subst = 0;
18313             }
18314         }
18315         if(*p == ']') p++;
18316     }
18317
18318     if(subst) return FALSE; // substitution requested, but no holdings
18319
18320     while(*p == ' ') p++;
18321
18322     /* Active color */
18323     c = *p++;
18324     if(appData.colorNickNames) {
18325       if( c == appData.colorNickNames[0] ) c = 'w'; else
18326       if( c == appData.colorNickNames[1] ) c = 'b';
18327     }
18328     switch (c) {
18329       case 'w':
18330         *blackPlaysFirst = FALSE;
18331         break;
18332       case 'b':
18333         *blackPlaysFirst = TRUE;
18334         break;
18335       default:
18336         return FALSE;
18337     }
18338
18339     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18340     /* return the extra info in global variiables             */
18341
18342     while(*p==' ') p++;
18343
18344     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18345         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18346         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18347     }
18348
18349     /* set defaults in case FEN is incomplete */
18350     board[EP_STATUS] = EP_UNKNOWN;
18351     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18352     for(i=0; i<nrCastlingRights; i++ ) {
18353         board[CASTLING][i] =
18354             appData.fischerCastling ? NoRights : initialRights[i];
18355     }   /* assume possible unless obviously impossible */
18356     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18357     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18358     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18359                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18360     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18361     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18362     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18363                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18364     FENrulePlies = 0;
18365
18366     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18367       char *q = p;
18368       int w=0, b=0;
18369       while(isalpha(*p)) {
18370         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18371         if(islower(*p)) b |= 1 << (*p++ - 'a');
18372       }
18373       if(*p == '-') p++;
18374       if(p != q) {
18375         board[TOUCHED_W] = ~w;
18376         board[TOUCHED_B] = ~b;
18377         while(*p == ' ') p++;
18378       }
18379     } else
18380
18381     if(nrCastlingRights) {
18382       int fischer = 0;
18383       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18384       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18385           /* castling indicator present, so default becomes no castlings */
18386           for(i=0; i<nrCastlingRights; i++ ) {
18387                  board[CASTLING][i] = NoRights;
18388           }
18389       }
18390       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18391              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18392              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18393              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18394         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18395
18396         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18397             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18398             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18399         }
18400         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18401             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18402         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18403                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18404         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18405                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18406         switch(c) {
18407           case'K':
18408               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18409               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18410               board[CASTLING][2] = whiteKingFile;
18411               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18412               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18413               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18414               break;
18415           case'Q':
18416               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18417               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18418               board[CASTLING][2] = whiteKingFile;
18419               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18420               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18421               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18422               break;
18423           case'k':
18424               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18425               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18426               board[CASTLING][5] = blackKingFile;
18427               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18428               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18429               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18430               break;
18431           case'q':
18432               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18433               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18434               board[CASTLING][5] = blackKingFile;
18435               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18436               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18437               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18438           case '-':
18439               break;
18440           default: /* FRC castlings */
18441               if(c >= 'a') { /* black rights */
18442                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18443                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18444                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18445                   if(i == BOARD_RGHT) break;
18446                   board[CASTLING][5] = i;
18447                   c -= AAA;
18448                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18449                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18450                   if(c > i)
18451                       board[CASTLING][3] = c;
18452                   else
18453                       board[CASTLING][4] = c;
18454               } else { /* white rights */
18455                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18456                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18457                     if(board[0][i] == WhiteKing) break;
18458                   if(i == BOARD_RGHT) break;
18459                   board[CASTLING][2] = i;
18460                   c -= AAA - 'a' + 'A';
18461                   if(board[0][c] >= WhiteKing) break;
18462                   if(c > i)
18463                       board[CASTLING][0] = c;
18464                   else
18465                       board[CASTLING][1] = c;
18466               }
18467         }
18468       }
18469       for(i=0; i<nrCastlingRights; i++)
18470         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18471       if(gameInfo.variant == VariantSChess)
18472         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18473       if(fischer && shuffle) appData.fischerCastling = TRUE;
18474     if (appData.debugMode) {
18475         fprintf(debugFP, "FEN castling rights:");
18476         for(i=0; i<nrCastlingRights; i++)
18477         fprintf(debugFP, " %d", board[CASTLING][i]);
18478         fprintf(debugFP, "\n");
18479     }
18480
18481       while(*p==' ') p++;
18482     }
18483
18484     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18485
18486     /* read e.p. field in games that know e.p. capture */
18487     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18488        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18489        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18490       if(*p=='-') {
18491         p++; board[EP_STATUS] = EP_NONE;
18492       } else {
18493          char c = *p++ - AAA;
18494
18495          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18496          if(*p >= '0' && *p <='9') p++;
18497          board[EP_STATUS] = c;
18498       }
18499     }
18500
18501
18502     if(sscanf(p, "%d", &i) == 1) {
18503         FENrulePlies = i; /* 50-move ply counter */
18504         /* (The move number is still ignored)    */
18505     }
18506
18507     return TRUE;
18508 }
18509
18510 void
18511 EditPositionPasteFEN (char *fen)
18512 {
18513   if (fen != NULL) {
18514     Board initial_position;
18515
18516     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18517       DisplayError(_("Bad FEN position in clipboard"), 0);
18518       return ;
18519     } else {
18520       int savedBlackPlaysFirst = blackPlaysFirst;
18521       EditPositionEvent();
18522       blackPlaysFirst = savedBlackPlaysFirst;
18523       CopyBoard(boards[0], initial_position);
18524       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18525       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18526       DisplayBothClocks();
18527       DrawPosition(FALSE, boards[currentMove]);
18528     }
18529   }
18530 }
18531
18532 static char cseq[12] = "\\   ";
18533
18534 Boolean
18535 set_cont_sequence (char *new_seq)
18536 {
18537     int len;
18538     Boolean ret;
18539
18540     // handle bad attempts to set the sequence
18541         if (!new_seq)
18542                 return 0; // acceptable error - no debug
18543
18544     len = strlen(new_seq);
18545     ret = (len > 0) && (len < sizeof(cseq));
18546     if (ret)
18547       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18548     else if (appData.debugMode)
18549       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18550     return ret;
18551 }
18552
18553 /*
18554     reformat a source message so words don't cross the width boundary.  internal
18555     newlines are not removed.  returns the wrapped size (no null character unless
18556     included in source message).  If dest is NULL, only calculate the size required
18557     for the dest buffer.  lp argument indicats line position upon entry, and it's
18558     passed back upon exit.
18559 */
18560 int
18561 wrap (char *dest, char *src, int count, int width, int *lp)
18562 {
18563     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18564
18565     cseq_len = strlen(cseq);
18566     old_line = line = *lp;
18567     ansi = len = clen = 0;
18568
18569     for (i=0; i < count; i++)
18570     {
18571         if (src[i] == '\033')
18572             ansi = 1;
18573
18574         // if we hit the width, back up
18575         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18576         {
18577             // store i & len in case the word is too long
18578             old_i = i, old_len = len;
18579
18580             // find the end of the last word
18581             while (i && src[i] != ' ' && src[i] != '\n')
18582             {
18583                 i--;
18584                 len--;
18585             }
18586
18587             // word too long?  restore i & len before splitting it
18588             if ((old_i-i+clen) >= width)
18589             {
18590                 i = old_i;
18591                 len = old_len;
18592             }
18593
18594             // extra space?
18595             if (i && src[i-1] == ' ')
18596                 len--;
18597
18598             if (src[i] != ' ' && src[i] != '\n')
18599             {
18600                 i--;
18601                 if (len)
18602                     len--;
18603             }
18604
18605             // now append the newline and continuation sequence
18606             if (dest)
18607                 dest[len] = '\n';
18608             len++;
18609             if (dest)
18610                 strncpy(dest+len, cseq, cseq_len);
18611             len += cseq_len;
18612             line = cseq_len;
18613             clen = cseq_len;
18614             continue;
18615         }
18616
18617         if (dest)
18618             dest[len] = src[i];
18619         len++;
18620         if (!ansi)
18621             line++;
18622         if (src[i] == '\n')
18623             line = 0;
18624         if (src[i] == 'm')
18625             ansi = 0;
18626     }
18627     if (dest && appData.debugMode)
18628     {
18629         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18630             count, width, line, len, *lp);
18631         show_bytes(debugFP, src, count);
18632         fprintf(debugFP, "\ndest: ");
18633         show_bytes(debugFP, dest, len);
18634         fprintf(debugFP, "\n");
18635     }
18636     *lp = dest ? line : old_line;
18637
18638     return len;
18639 }
18640
18641 // [HGM] vari: routines for shelving variations
18642 Boolean modeRestore = FALSE;
18643
18644 void
18645 PushInner (int firstMove, int lastMove)
18646 {
18647         int i, j, nrMoves = lastMove - firstMove;
18648
18649         // push current tail of game on stack
18650         savedResult[storedGames] = gameInfo.result;
18651         savedDetails[storedGames] = gameInfo.resultDetails;
18652         gameInfo.resultDetails = NULL;
18653         savedFirst[storedGames] = firstMove;
18654         savedLast [storedGames] = lastMove;
18655         savedFramePtr[storedGames] = framePtr;
18656         framePtr -= nrMoves; // reserve space for the boards
18657         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18658             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18659             for(j=0; j<MOVE_LEN; j++)
18660                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18661             for(j=0; j<2*MOVE_LEN; j++)
18662                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18663             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18664             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18665             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18666             pvInfoList[firstMove+i-1].depth = 0;
18667             commentList[framePtr+i] = commentList[firstMove+i];
18668             commentList[firstMove+i] = NULL;
18669         }
18670
18671         storedGames++;
18672         forwardMostMove = firstMove; // truncate game so we can start variation
18673 }
18674
18675 void
18676 PushTail (int firstMove, int lastMove)
18677 {
18678         if(appData.icsActive) { // only in local mode
18679                 forwardMostMove = currentMove; // mimic old ICS behavior
18680                 return;
18681         }
18682         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18683
18684         PushInner(firstMove, lastMove);
18685         if(storedGames == 1) GreyRevert(FALSE);
18686         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18687 }
18688
18689 void
18690 PopInner (Boolean annotate)
18691 {
18692         int i, j, nrMoves;
18693         char buf[8000], moveBuf[20];
18694
18695         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18696         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18697         nrMoves = savedLast[storedGames] - currentMove;
18698         if(annotate) {
18699                 int cnt = 10;
18700                 if(!WhiteOnMove(currentMove))
18701                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18702                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18703                 for(i=currentMove; i<forwardMostMove; i++) {
18704                         if(WhiteOnMove(i))
18705                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18706                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18707                         strcat(buf, moveBuf);
18708                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18709                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18710                 }
18711                 strcat(buf, ")");
18712         }
18713         for(i=1; i<=nrMoves; i++) { // copy last variation back
18714             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18715             for(j=0; j<MOVE_LEN; j++)
18716                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18717             for(j=0; j<2*MOVE_LEN; j++)
18718                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18719             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18720             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18721             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18722             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18723             commentList[currentMove+i] = commentList[framePtr+i];
18724             commentList[framePtr+i] = NULL;
18725         }
18726         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18727         framePtr = savedFramePtr[storedGames];
18728         gameInfo.result = savedResult[storedGames];
18729         if(gameInfo.resultDetails != NULL) {
18730             free(gameInfo.resultDetails);
18731       }
18732         gameInfo.resultDetails = savedDetails[storedGames];
18733         forwardMostMove = currentMove + nrMoves;
18734 }
18735
18736 Boolean
18737 PopTail (Boolean annotate)
18738 {
18739         if(appData.icsActive) return FALSE; // only in local mode
18740         if(!storedGames) return FALSE; // sanity
18741         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18742
18743         PopInner(annotate);
18744         if(currentMove < forwardMostMove) ForwardEvent(); else
18745         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18746
18747         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18748         return TRUE;
18749 }
18750
18751 void
18752 CleanupTail ()
18753 {       // remove all shelved variations
18754         int i;
18755         for(i=0; i<storedGames; i++) {
18756             if(savedDetails[i])
18757                 free(savedDetails[i]);
18758             savedDetails[i] = NULL;
18759         }
18760         for(i=framePtr; i<MAX_MOVES; i++) {
18761                 if(commentList[i]) free(commentList[i]);
18762                 commentList[i] = NULL;
18763         }
18764         framePtr = MAX_MOVES-1;
18765         storedGames = 0;
18766 }
18767
18768 void
18769 LoadVariation (int index, char *text)
18770 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18771         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18772         int level = 0, move;
18773
18774         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18775         // first find outermost bracketing variation
18776         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18777             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18778                 if(*p == '{') wait = '}'; else
18779                 if(*p == '[') wait = ']'; else
18780                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18781                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18782             }
18783             if(*p == wait) wait = NULLCHAR; // closing ]} found
18784             p++;
18785         }
18786         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18787         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18788         end[1] = NULLCHAR; // clip off comment beyond variation
18789         ToNrEvent(currentMove-1);
18790         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18791         // kludge: use ParsePV() to append variation to game
18792         move = currentMove;
18793         ParsePV(start, TRUE, TRUE);
18794         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18795         ClearPremoveHighlights();
18796         CommentPopDown();
18797         ToNrEvent(currentMove+1);
18798 }
18799
18800 void
18801 LoadTheme ()
18802 {
18803     char *p, *q, buf[MSG_SIZ];
18804     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18805         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18806         ParseArgsFromString(buf);
18807         ActivateTheme(TRUE); // also redo colors
18808         return;
18809     }
18810     p = nickName;
18811     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18812     {
18813         int len;
18814         q = appData.themeNames;
18815         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18816       if(appData.useBitmaps) {
18817         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18818                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18819                 appData.liteBackTextureMode,
18820                 appData.darkBackTextureMode );
18821       } else {
18822         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18823                 Col2Text(2),   // lightSquareColor
18824                 Col2Text(3) ); // darkSquareColor
18825       }
18826       if(appData.useBorder) {
18827         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18828                 appData.border);
18829       } else {
18830         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18831       }
18832       if(appData.useFont) {
18833         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18834                 appData.renderPiecesWithFont,
18835                 appData.fontToPieceTable,
18836                 Col2Text(9),    // appData.fontBackColorWhite
18837                 Col2Text(10) ); // appData.fontForeColorBlack
18838       } else {
18839         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18840                 appData.pieceDirectory);
18841         if(!appData.pieceDirectory[0])
18842           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18843                 Col2Text(0),   // whitePieceColor
18844                 Col2Text(1) ); // blackPieceColor
18845       }
18846       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18847                 Col2Text(4),   // highlightSquareColor
18848                 Col2Text(5) ); // premoveHighlightColor
18849         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18850         if(insert != q) insert[-1] = NULLCHAR;
18851         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18852         if(q)   free(q);
18853     }
18854     ActivateTheme(FALSE);
18855 }