Let target-square highlighting prevail over legality test
[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     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7084
7085     /* [HGM] but possibly ignore an IllegalMove result */
7086     if (appData.testLegality) {
7087         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7088             DisplayMoveError(_("Illegal move"));
7089             return;
7090         }
7091     }
7092
7093     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7094         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7095              ClearPremoveHighlights(); // was included
7096         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7097         return;
7098     }
7099
7100     if(addToBookFlag) { // adding moves to book
7101         char buf[MSG_SIZ], move[MSG_SIZ];
7102         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7103         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');
7104         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7105         AddBookMove(buf);
7106         addToBookFlag = FALSE;
7107         ClearHighlights();
7108         return;
7109     }
7110
7111     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7112 }
7113
7114 /* Common tail of UserMoveEvent and DropMenuEvent */
7115 int
7116 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7117 {
7118     char *bookHit = 0;
7119
7120     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7121         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7122         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7123         if(WhiteOnMove(currentMove)) {
7124             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7125         } else {
7126             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7127         }
7128     }
7129
7130     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7131        move type in caller when we know the move is a legal promotion */
7132     if(moveType == NormalMove && promoChar)
7133         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7134
7135     /* [HGM] <popupFix> The following if has been moved here from
7136        UserMoveEvent(). Because it seemed to belong here (why not allow
7137        piece drops in training games?), and because it can only be
7138        performed after it is known to what we promote. */
7139     if (gameMode == Training) {
7140       /* compare the move played on the board to the next move in the
7141        * game. If they match, display the move and the opponent's response.
7142        * If they don't match, display an error message.
7143        */
7144       int saveAnimate;
7145       Board testBoard;
7146       CopyBoard(testBoard, boards[currentMove]);
7147       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7148
7149       if (CompareBoards(testBoard, boards[currentMove+1])) {
7150         ForwardInner(currentMove+1);
7151
7152         /* Autoplay the opponent's response.
7153          * if appData.animate was TRUE when Training mode was entered,
7154          * the response will be animated.
7155          */
7156         saveAnimate = appData.animate;
7157         appData.animate = animateTraining;
7158         ForwardInner(currentMove+1);
7159         appData.animate = saveAnimate;
7160
7161         /* check for the end of the game */
7162         if (currentMove >= forwardMostMove) {
7163           gameMode = PlayFromGameFile;
7164           ModeHighlight();
7165           SetTrainingModeOff();
7166           DisplayInformation(_("End of game"));
7167         }
7168       } else {
7169         DisplayError(_("Incorrect move"), 0);
7170       }
7171       return 1;
7172     }
7173
7174   /* Ok, now we know that the move is good, so we can kill
7175      the previous line in Analysis Mode */
7176   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7177                                 && currentMove < forwardMostMove) {
7178     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7179     else forwardMostMove = currentMove;
7180   }
7181
7182   ClearMap();
7183
7184   /* If we need the chess program but it's dead, restart it */
7185   ResurrectChessProgram();
7186
7187   /* A user move restarts a paused game*/
7188   if (pausing)
7189     PauseEvent();
7190
7191   thinkOutput[0] = NULLCHAR;
7192
7193   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7194
7195   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7196     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7197     return 1;
7198   }
7199
7200   if (gameMode == BeginningOfGame) {
7201     if (appData.noChessProgram) {
7202       gameMode = EditGame;
7203       SetGameInfo();
7204     } else {
7205       char buf[MSG_SIZ];
7206       gameMode = MachinePlaysBlack;
7207       StartClocks();
7208       SetGameInfo();
7209       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7210       DisplayTitle(buf);
7211       if (first.sendName) {
7212         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7213         SendToProgram(buf, &first);
7214       }
7215       StartClocks();
7216     }
7217     ModeHighlight();
7218   }
7219
7220   /* Relay move to ICS or chess engine */
7221   if (appData.icsActive) {
7222     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7223         gameMode == IcsExamining) {
7224       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7225         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7226         SendToICS("draw ");
7227         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7228       }
7229       // also send plain move, in case ICS does not understand atomic claims
7230       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7231       ics_user_moved = 1;
7232     }
7233   } else {
7234     if (first.sendTime && (gameMode == BeginningOfGame ||
7235                            gameMode == MachinePlaysWhite ||
7236                            gameMode == MachinePlaysBlack)) {
7237       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7238     }
7239     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7240          // [HGM] book: if program might be playing, let it use book
7241         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7242         first.maybeThinking = TRUE;
7243     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7244         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7245         SendBoard(&first, currentMove+1);
7246         if(second.analyzing) {
7247             if(!second.useSetboard) SendToProgram("undo\n", &second);
7248             SendBoard(&second, currentMove+1);
7249         }
7250     } else {
7251         SendMoveToProgram(forwardMostMove-1, &first);
7252         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7253     }
7254     if (currentMove == cmailOldMove + 1) {
7255       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7256     }
7257   }
7258
7259   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7260
7261   switch (gameMode) {
7262   case EditGame:
7263     if(appData.testLegality)
7264     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7265     case MT_NONE:
7266     case MT_CHECK:
7267       break;
7268     case MT_CHECKMATE:
7269     case MT_STAINMATE:
7270       if (WhiteOnMove(currentMove)) {
7271         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7272       } else {
7273         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7274       }
7275       break;
7276     case MT_STALEMATE:
7277       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7278       break;
7279     }
7280     break;
7281
7282   case MachinePlaysBlack:
7283   case MachinePlaysWhite:
7284     /* disable certain menu options while machine is thinking */
7285     SetMachineThinkingEnables();
7286     break;
7287
7288   default:
7289     break;
7290   }
7291
7292   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7293   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7294
7295   if(bookHit) { // [HGM] book: simulate book reply
7296         static char bookMove[MSG_SIZ]; // a bit generous?
7297
7298         programStats.nodes = programStats.depth = programStats.time =
7299         programStats.score = programStats.got_only_move = 0;
7300         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7301
7302         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7303         strcat(bookMove, bookHit);
7304         HandleMachineMove(bookMove, &first);
7305   }
7306   return 1;
7307 }
7308
7309 void
7310 MarkByFEN(char *fen)
7311 {
7312         int r, f;
7313         if(!appData.markers || !appData.highlightDragging) return;
7314         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7315         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7316         while(*fen) {
7317             int s = 0;
7318             marker[r][f] = 0;
7319             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7320             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7321             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7322             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7323             if(*fen == 'T') marker[r][f++] = 0; else
7324             if(*fen == 'Y') marker[r][f++] = 1; else
7325             if(*fen == 'G') marker[r][f++] = 3; else
7326             if(*fen == 'B') marker[r][f++] = 4; else
7327             if(*fen == 'C') marker[r][f++] = 5; else
7328             if(*fen == 'M') marker[r][f++] = 6; else
7329             if(*fen == 'W') marker[r][f++] = 7; else
7330             if(*fen == 'D') marker[r][f++] = 8; else
7331             if(*fen == 'R') marker[r][f++] = 2; else {
7332                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7333               f += s; fen -= s>0;
7334             }
7335             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7336             if(r < 0) break;
7337             fen++;
7338         }
7339         DrawPosition(TRUE, NULL);
7340 }
7341
7342 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7343
7344 void
7345 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7346 {
7347     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7348     Markers *m = (Markers *) closure;
7349     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7350         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7351                          || kind == WhiteCapturesEnPassant
7352                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7353     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7354 }
7355
7356 static int hoverSavedValid;
7357
7358 void
7359 MarkTargetSquares (int clear)
7360 {
7361   int x, y, sum=0;
7362   if(clear) { // no reason to ever suppress clearing
7363     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7364     hoverSavedValid = 0;
7365     if(!sum) return; // nothing was cleared,no redraw needed
7366   } else {
7367     int capt = 0;
7368     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7369        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7370     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7371     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7372       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7373       if(capt)
7374       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7375     }
7376   }
7377   DrawPosition(FALSE, NULL);
7378 }
7379
7380 int
7381 Explode (Board board, int fromX, int fromY, int toX, int toY)
7382 {
7383     if(gameInfo.variant == VariantAtomic &&
7384        (board[toY][toX] != EmptySquare ||                     // capture?
7385         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7386                          board[fromY][fromX] == BlackPawn   )
7387       )) {
7388         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7389         return TRUE;
7390     }
7391     return FALSE;
7392 }
7393
7394 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7395
7396 int
7397 CanPromote (ChessSquare piece, int y)
7398 {
7399         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7400         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7401         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7402         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7403            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7404            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7405          gameInfo.variant == VariantMakruk) return FALSE;
7406         return (piece == BlackPawn && y <= zone ||
7407                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7408                 piece == BlackLance && y <= zone ||
7409                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7410 }
7411
7412 void
7413 HoverEvent (int xPix, int yPix, int x, int y)
7414 {
7415         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7416         int r, f;
7417         if(!first.highlight) return;
7418         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7419         if(x == oldX && y == oldY) return; // only do something if we enter new square
7420         oldFromX = fromX; oldFromY = fromY;
7421         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7422           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7423             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7424           hoverSavedValid = 1;
7425         } else if(oldX != x || oldY != y) {
7426           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7427           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7428           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7429             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7430           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7431             char buf[MSG_SIZ];
7432             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7433             SendToProgram(buf, &first);
7434           }
7435           oldX = x; oldY = y;
7436 //        SetHighlights(fromX, fromY, x, y);
7437         }
7438 }
7439
7440 void ReportClick(char *action, int x, int y)
7441 {
7442         char buf[MSG_SIZ]; // Inform engine of what user does
7443         int r, f;
7444         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7445           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7446             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7447         if(!first.highlight || gameMode == EditPosition) return;
7448         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7449         SendToProgram(buf, &first);
7450 }
7451
7452 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7453
7454 void
7455 LeftClick (ClickType clickType, int xPix, int yPix)
7456 {
7457     int x, y;
7458     Boolean saveAnimate;
7459     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7460     char promoChoice = NULLCHAR;
7461     ChessSquare piece;
7462     static TimeMark lastClickTime, prevClickTime;
7463
7464     x = EventToSquare(xPix, BOARD_WIDTH);
7465     y = EventToSquare(yPix, BOARD_HEIGHT);
7466     if (!flipView && y >= 0) {
7467         y = BOARD_HEIGHT - 1 - y;
7468     }
7469     if (flipView && x >= 0) {
7470         x = BOARD_WIDTH - 1 - x;
7471     }
7472
7473     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7474         static int dummy;
7475         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7476         right = TRUE;
7477         return;
7478     }
7479
7480     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7481
7482     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7483
7484     if (clickType == Press) ErrorPopDown();
7485     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7486
7487     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7488         defaultPromoChoice = promoSweep;
7489         promoSweep = EmptySquare;   // terminate sweep
7490         promoDefaultAltered = TRUE;
7491         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7492     }
7493
7494     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7495         if(clickType == Release) return; // ignore upclick of click-click destination
7496         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7497         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7498         if(gameInfo.holdingsWidth &&
7499                 (WhiteOnMove(currentMove)
7500                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7501                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7502             // click in right holdings, for determining promotion piece
7503             ChessSquare p = boards[currentMove][y][x];
7504             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7505             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7506             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7507                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7508                 fromX = fromY = -1;
7509                 return;
7510             }
7511         }
7512         DrawPosition(FALSE, boards[currentMove]);
7513         return;
7514     }
7515
7516     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7517     if(clickType == Press
7518             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7519               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7520               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7521         return;
7522
7523     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7524         // could be static click on premove from-square: abort premove
7525         gotPremove = 0;
7526         ClearPremoveHighlights();
7527     }
7528
7529     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7530         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7531
7532     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7533         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7534                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7535         defaultPromoChoice = DefaultPromoChoice(side);
7536     }
7537
7538     autoQueen = appData.alwaysPromoteToQueen;
7539
7540     if (fromX == -1) {
7541       int originalY = y;
7542       gatingPiece = EmptySquare;
7543       if (clickType != Press) {
7544         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7545             DragPieceEnd(xPix, yPix); dragging = 0;
7546             DrawPosition(FALSE, NULL);
7547         }
7548         return;
7549       }
7550       doubleClick = FALSE;
7551       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7552         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7553       }
7554       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7555       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7556          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7557          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7558             /* First square */
7559             if (OKToStartUserMove(fromX, fromY)) {
7560                 second = 0;
7561                 ReportClick("lift", x, y);
7562                 MarkTargetSquares(0);
7563                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7564                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7565                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7566                     promoSweep = defaultPromoChoice;
7567                     selectFlag = 0; lastX = xPix; lastY = yPix;
7568                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7569                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7570                 }
7571                 if (appData.highlightDragging) {
7572                     SetHighlights(fromX, fromY, -1, -1);
7573                 } else {
7574                     ClearHighlights();
7575                 }
7576             } else fromX = fromY = -1;
7577             return;
7578         }
7579     }
7580
7581     /* fromX != -1 */
7582     if (clickType == Press && gameMode != EditPosition) {
7583         ChessSquare fromP;
7584         ChessSquare toP;
7585         int frc;
7586
7587         // ignore off-board to clicks
7588         if(y < 0 || x < 0) return;
7589
7590         /* Check if clicking again on the same color piece */
7591         fromP = boards[currentMove][fromY][fromX];
7592         toP = boards[currentMove][y][x];
7593         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7594         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7595             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7596            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7597              WhitePawn <= toP && toP <= WhiteKing &&
7598              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7599              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7600             (BlackPawn <= fromP && fromP <= BlackKing &&
7601              BlackPawn <= toP && toP <= BlackKing &&
7602              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7603              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7604             /* Clicked again on same color piece -- changed his mind */
7605             second = (x == fromX && y == fromY);
7606             killX = killY = -1;
7607             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7608                 second = FALSE; // first double-click rather than scond click
7609                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7610             }
7611             promoDefaultAltered = FALSE;
7612             MarkTargetSquares(1);
7613            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7614             if (appData.highlightDragging) {
7615                 SetHighlights(x, y, -1, -1);
7616             } else {
7617                 ClearHighlights();
7618             }
7619             if (OKToStartUserMove(x, y)) {
7620                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7621                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7622                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7623                  gatingPiece = boards[currentMove][fromY][fromX];
7624                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7625                 fromX = x;
7626                 fromY = y; dragging = 1;
7627                 if(!second) ReportClick("lift", x, y);
7628                 MarkTargetSquares(0);
7629                 DragPieceBegin(xPix, yPix, FALSE);
7630                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7631                     promoSweep = defaultPromoChoice;
7632                     selectFlag = 0; lastX = xPix; lastY = yPix;
7633                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7634                 }
7635             }
7636            }
7637            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7638            second = FALSE;
7639         }
7640         // ignore clicks on holdings
7641         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7642     }
7643
7644     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7645         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7646         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7647         return;
7648     }
7649
7650     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7651         DragPieceEnd(xPix, yPix); dragging = 0;
7652         if(clearFlag) {
7653             // a deferred attempt to click-click move an empty square on top of a piece
7654             boards[currentMove][y][x] = EmptySquare;
7655             ClearHighlights();
7656             DrawPosition(FALSE, boards[currentMove]);
7657             fromX = fromY = -1; clearFlag = 0;
7658             return;
7659         }
7660         if (appData.animateDragging) {
7661             /* Undo animation damage if any */
7662             DrawPosition(FALSE, NULL);
7663         }
7664         if (second) {
7665             /* Second up/down in same square; just abort move */
7666             second = 0;
7667             fromX = fromY = -1;
7668             gatingPiece = EmptySquare;
7669             MarkTargetSquares(1);
7670             ClearHighlights();
7671             gotPremove = 0;
7672             ClearPremoveHighlights();
7673         } else {
7674             /* First upclick in same square; start click-click mode */
7675             SetHighlights(x, y, -1, -1);
7676         }
7677         return;
7678     }
7679
7680     clearFlag = 0;
7681
7682     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7683        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7684         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7685         DisplayMessage(_("only marked squares are legal"),"");
7686         DrawPosition(TRUE, NULL);
7687         return; // ignore to-click
7688     }
7689
7690     /* we now have a different from- and (possibly off-board) to-square */
7691     /* Completed move */
7692     if(!sweepSelecting) {
7693         toX = x;
7694         toY = y;
7695     }
7696
7697     piece = boards[currentMove][fromY][fromX];
7698
7699     saveAnimate = appData.animate;
7700     if (clickType == Press) {
7701         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7702         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7703             // must be Edit Position mode with empty-square selected
7704             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7705             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7706             return;
7707         }
7708         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7709             return;
7710         }
7711         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7712             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7713         } else
7714         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7715         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7716           if(appData.sweepSelect) {
7717             promoSweep = defaultPromoChoice;
7718             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7719             selectFlag = 0; lastX = xPix; lastY = yPix;
7720             Sweep(0); // Pawn that is going to promote: preview promotion piece
7721             sweepSelecting = 1;
7722             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7723             MarkTargetSquares(1);
7724           }
7725           return; // promo popup appears on up-click
7726         }
7727         /* Finish clickclick move */
7728         if (appData.animate || appData.highlightLastMove) {
7729             SetHighlights(fromX, fromY, toX, toY);
7730         } else {
7731             ClearHighlights();
7732         }
7733     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7734         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7735         if (appData.animate || appData.highlightLastMove) {
7736             SetHighlights(fromX, fromY, toX, toY);
7737         } else {
7738             ClearHighlights();
7739         }
7740     } else {
7741 #if 0
7742 // [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
7743         /* Finish drag move */
7744         if (appData.highlightLastMove) {
7745             SetHighlights(fromX, fromY, toX, toY);
7746         } else {
7747             ClearHighlights();
7748         }
7749 #endif
7750         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7751         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7752           dragging *= 2;            // flag button-less dragging if we are dragging
7753           MarkTargetSquares(1);
7754           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7755           else {
7756             kill2X = killX; kill2Y = killY;
7757             killX = x; killY = y;     //remeber this square as intermediate
7758             ReportClick("put", x, y); // and inform engine
7759             ReportClick("lift", x, y);
7760             MarkTargetSquares(0);
7761             return;
7762           }
7763         }
7764         DragPieceEnd(xPix, yPix); dragging = 0;
7765         /* Don't animate move and drag both */
7766         appData.animate = FALSE;
7767     }
7768
7769     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7770     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7771         ChessSquare piece = boards[currentMove][fromY][fromX];
7772         if(gameMode == EditPosition && piece != EmptySquare &&
7773            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7774             int n;
7775
7776             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7777                 n = PieceToNumber(piece - (int)BlackPawn);
7778                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7779                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7780                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7781             } else
7782             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7783                 n = PieceToNumber(piece);
7784                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7785                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7786                 boards[currentMove][n][BOARD_WIDTH-2]++;
7787             }
7788             boards[currentMove][fromY][fromX] = EmptySquare;
7789         }
7790         ClearHighlights();
7791         fromX = fromY = -1;
7792         MarkTargetSquares(1);
7793         DrawPosition(TRUE, boards[currentMove]);
7794         return;
7795     }
7796
7797     // off-board moves should not be highlighted
7798     if(x < 0 || y < 0) ClearHighlights();
7799     else ReportClick("put", x, y);
7800
7801     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7802
7803     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7804
7805     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7806         SetHighlights(fromX, fromY, toX, toY);
7807         MarkTargetSquares(1);
7808         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7809             // [HGM] super: promotion to captured piece selected from holdings
7810             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7811             promotionChoice = TRUE;
7812             // kludge follows to temporarily execute move on display, without promoting yet
7813             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7814             boards[currentMove][toY][toX] = p;
7815             DrawPosition(FALSE, boards[currentMove]);
7816             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7817             boards[currentMove][toY][toX] = q;
7818             DisplayMessage("Click in holdings to choose piece", "");
7819             return;
7820         }
7821         PromotionPopUp(promoChoice);
7822     } else {
7823         int oldMove = currentMove;
7824         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7825         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7826         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7827         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7828            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7829             DrawPosition(TRUE, boards[currentMove]);
7830         MarkTargetSquares(1);
7831         fromX = fromY = -1;
7832     }
7833     appData.animate = saveAnimate;
7834     if (appData.animate || appData.animateDragging) {
7835         /* Undo animation damage if needed */
7836         DrawPosition(FALSE, NULL);
7837     }
7838 }
7839
7840 int
7841 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7842 {   // front-end-free part taken out of PieceMenuPopup
7843     int whichMenu; int xSqr, ySqr;
7844
7845     if(seekGraphUp) { // [HGM] seekgraph
7846         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7847         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7848         return -2;
7849     }
7850
7851     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7852          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7853         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7854         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7855         if(action == Press)   {
7856             originalFlip = flipView;
7857             flipView = !flipView; // temporarily flip board to see game from partners perspective
7858             DrawPosition(TRUE, partnerBoard);
7859             DisplayMessage(partnerStatus, "");
7860             partnerUp = TRUE;
7861         } else if(action == Release) {
7862             flipView = originalFlip;
7863             DrawPosition(TRUE, boards[currentMove]);
7864             partnerUp = FALSE;
7865         }
7866         return -2;
7867     }
7868
7869     xSqr = EventToSquare(x, BOARD_WIDTH);
7870     ySqr = EventToSquare(y, BOARD_HEIGHT);
7871     if (action == Release) {
7872         if(pieceSweep != EmptySquare) {
7873             EditPositionMenuEvent(pieceSweep, toX, toY);
7874             pieceSweep = EmptySquare;
7875         } else UnLoadPV(); // [HGM] pv
7876     }
7877     if (action != Press) return -2; // return code to be ignored
7878     switch (gameMode) {
7879       case IcsExamining:
7880         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7881       case EditPosition:
7882         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7883         if (xSqr < 0 || ySqr < 0) return -1;
7884         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7885         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7886         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7887         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7888         NextPiece(0);
7889         return 2; // grab
7890       case IcsObserving:
7891         if(!appData.icsEngineAnalyze) return -1;
7892       case IcsPlayingWhite:
7893       case IcsPlayingBlack:
7894         if(!appData.zippyPlay) goto noZip;
7895       case AnalyzeMode:
7896       case AnalyzeFile:
7897       case MachinePlaysWhite:
7898       case MachinePlaysBlack:
7899       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7900         if (!appData.dropMenu) {
7901           LoadPV(x, y);
7902           return 2; // flag front-end to grab mouse events
7903         }
7904         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7905            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7906       case EditGame:
7907       noZip:
7908         if (xSqr < 0 || ySqr < 0) return -1;
7909         if (!appData.dropMenu || appData.testLegality &&
7910             gameInfo.variant != VariantBughouse &&
7911             gameInfo.variant != VariantCrazyhouse) return -1;
7912         whichMenu = 1; // drop menu
7913         break;
7914       default:
7915         return -1;
7916     }
7917
7918     if (((*fromX = xSqr) < 0) ||
7919         ((*fromY = ySqr) < 0)) {
7920         *fromX = *fromY = -1;
7921         return -1;
7922     }
7923     if (flipView)
7924       *fromX = BOARD_WIDTH - 1 - *fromX;
7925     else
7926       *fromY = BOARD_HEIGHT - 1 - *fromY;
7927
7928     return whichMenu;
7929 }
7930
7931 void
7932 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7933 {
7934 //    char * hint = lastHint;
7935     FrontEndProgramStats stats;
7936
7937     stats.which = cps == &first ? 0 : 1;
7938     stats.depth = cpstats->depth;
7939     stats.nodes = cpstats->nodes;
7940     stats.score = cpstats->score;
7941     stats.time = cpstats->time;
7942     stats.pv = cpstats->movelist;
7943     stats.hint = lastHint;
7944     stats.an_move_index = 0;
7945     stats.an_move_count = 0;
7946
7947     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7948         stats.hint = cpstats->move_name;
7949         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7950         stats.an_move_count = cpstats->nr_moves;
7951     }
7952
7953     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
7954
7955     SetProgramStats( &stats );
7956 }
7957
7958 void
7959 ClearEngineOutputPane (int which)
7960 {
7961     static FrontEndProgramStats dummyStats;
7962     dummyStats.which = which;
7963     dummyStats.pv = "#";
7964     SetProgramStats( &dummyStats );
7965 }
7966
7967 #define MAXPLAYERS 500
7968
7969 char *
7970 TourneyStandings (int display)
7971 {
7972     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7973     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7974     char result, *p, *names[MAXPLAYERS];
7975
7976     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7977         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7978     names[0] = p = strdup(appData.participants);
7979     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7980
7981     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7982
7983     while(result = appData.results[nr]) {
7984         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7985         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7986         wScore = bScore = 0;
7987         switch(result) {
7988           case '+': wScore = 2; break;
7989           case '-': bScore = 2; break;
7990           case '=': wScore = bScore = 1; break;
7991           case ' ':
7992           case '*': return strdup("busy"); // tourney not finished
7993         }
7994         score[w] += wScore;
7995         score[b] += bScore;
7996         games[w]++;
7997         games[b]++;
7998         nr++;
7999     }
8000     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8001     for(w=0; w<nPlayers; w++) {
8002         bScore = -1;
8003         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8004         ranking[w] = b; points[w] = bScore; score[b] = -2;
8005     }
8006     p = malloc(nPlayers*34+1);
8007     for(w=0; w<nPlayers && w<display; w++)
8008         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8009     free(names[0]);
8010     return p;
8011 }
8012
8013 void
8014 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8015 {       // count all piece types
8016         int p, f, r;
8017         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8018         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8019         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8020                 p = board[r][f];
8021                 pCnt[p]++;
8022                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8023                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8024                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8025                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8026                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8027                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8028         }
8029 }
8030
8031 int
8032 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8033 {
8034         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8035         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8036
8037         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8038         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8039         if(myPawns == 2 && nMine == 3) // KPP
8040             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8041         if(myPawns == 1 && nMine == 2) // KP
8042             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8043         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8044             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8045         if(myPawns) return FALSE;
8046         if(pCnt[WhiteRook+side])
8047             return pCnt[BlackRook-side] ||
8048                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8049                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8050                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8051         if(pCnt[WhiteCannon+side]) {
8052             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8053             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8054         }
8055         if(pCnt[WhiteKnight+side])
8056             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8057         return FALSE;
8058 }
8059
8060 int
8061 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8062 {
8063         VariantClass v = gameInfo.variant;
8064
8065         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8066         if(v == VariantShatranj) return TRUE; // always winnable through baring
8067         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8068         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8069
8070         if(v == VariantXiangqi) {
8071                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8072
8073                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8074                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8075                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8076                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8077                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8078                 if(stale) // we have at least one last-rank P plus perhaps C
8079                     return majors // KPKX
8080                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8081                 else // KCA*E*
8082                     return pCnt[WhiteFerz+side] // KCAK
8083                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8084                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8085                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8086
8087         } else if(v == VariantKnightmate) {
8088                 if(nMine == 1) return FALSE;
8089                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8090         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8091                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8092
8093                 if(nMine == 1) return FALSE; // bare King
8094                 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
8095                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8096                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8097                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8098                 if(pCnt[WhiteKnight+side])
8099                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8100                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8101                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8102                 if(nBishops)
8103                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8104                 if(pCnt[WhiteAlfil+side])
8105                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8106                 if(pCnt[WhiteWazir+side])
8107                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8108         }
8109
8110         return TRUE;
8111 }
8112
8113 int
8114 CompareWithRights (Board b1, Board b2)
8115 {
8116     int rights = 0;
8117     if(!CompareBoards(b1, b2)) return FALSE;
8118     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8119     /* compare castling rights */
8120     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8121            rights++; /* King lost rights, while rook still had them */
8122     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8123         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8124            rights++; /* but at least one rook lost them */
8125     }
8126     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8127            rights++;
8128     if( b1[CASTLING][5] != NoRights ) {
8129         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8130            rights++;
8131     }
8132     return rights == 0;
8133 }
8134
8135 int
8136 Adjudicate (ChessProgramState *cps)
8137 {       // [HGM] some adjudications useful with buggy engines
8138         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8139         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8140         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8141         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8142         int k, drop, count = 0; static int bare = 1;
8143         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8144         Boolean canAdjudicate = !appData.icsActive;
8145
8146         // most tests only when we understand the game, i.e. legality-checking on
8147             if( appData.testLegality )
8148             {   /* [HGM] Some more adjudications for obstinate engines */
8149                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8150                 static int moveCount = 6;
8151                 ChessMove result;
8152                 char *reason = NULL;
8153
8154                 /* Count what is on board. */
8155                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8156
8157                 /* Some material-based adjudications that have to be made before stalemate test */
8158                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8159                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8160                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8161                      if(canAdjudicate && appData.checkMates) {
8162                          if(engineOpponent)
8163                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8164                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8165                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8166                          return 1;
8167                      }
8168                 }
8169
8170                 /* Bare King in Shatranj (loses) or Losers (wins) */
8171                 if( nrW == 1 || nrB == 1) {
8172                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8173                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8174                      if(canAdjudicate && appData.checkMates) {
8175                          if(engineOpponent)
8176                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8177                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8178                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8179                          return 1;
8180                      }
8181                   } else
8182                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8183                   {    /* bare King */
8184                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8185                         if(canAdjudicate && appData.checkMates) {
8186                             /* but only adjudicate if adjudication enabled */
8187                             if(engineOpponent)
8188                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8189                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8190                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8191                             return 1;
8192                         }
8193                   }
8194                 } else bare = 1;
8195
8196
8197             // don't wait for engine to announce game end if we can judge ourselves
8198             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8199               case MT_CHECK:
8200                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8201                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8202                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8203                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8204                             checkCnt++;
8205                         if(checkCnt >= 2) {
8206                             reason = "Xboard adjudication: 3rd check";
8207                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8208                             break;
8209                         }
8210                     }
8211                 }
8212               case MT_NONE:
8213               default:
8214                 break;
8215               case MT_STEALMATE:
8216               case MT_STALEMATE:
8217               case MT_STAINMATE:
8218                 reason = "Xboard adjudication: Stalemate";
8219                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8220                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8221                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8222                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8223                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8224                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8225                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8226                                                                         EP_CHECKMATE : EP_WINS);
8227                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8228                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8229                 }
8230                 break;
8231               case MT_CHECKMATE:
8232                 reason = "Xboard adjudication: Checkmate";
8233                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8234                 if(gameInfo.variant == VariantShogi) {
8235                     if(forwardMostMove > backwardMostMove
8236                        && moveList[forwardMostMove-1][1] == '@'
8237                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8238                         reason = "XBoard adjudication: pawn-drop mate";
8239                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8240                     }
8241                 }
8242                 break;
8243             }
8244
8245                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8246                     case EP_STALEMATE:
8247                         result = GameIsDrawn; break;
8248                     case EP_CHECKMATE:
8249                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8250                     case EP_WINS:
8251                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8252                     default:
8253                         result = EndOfFile;
8254                 }
8255                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8256                     if(engineOpponent)
8257                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258                     GameEnds( result, reason, GE_XBOARD );
8259                     return 1;
8260                 }
8261
8262                 /* Next absolutely insufficient mating material. */
8263                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8264                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8265                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8266
8267                      /* always flag draws, for judging claims */
8268                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8269
8270                      if(canAdjudicate && appData.materialDraws) {
8271                          /* but only adjudicate them if adjudication enabled */
8272                          if(engineOpponent) {
8273                            SendToProgram("force\n", engineOpponent); // suppress reply
8274                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8275                          }
8276                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8277                          return 1;
8278                      }
8279                 }
8280
8281                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8282                 if(gameInfo.variant == VariantXiangqi ?
8283                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8284                  : nrW + nrB == 4 &&
8285                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8286                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8287                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8288                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8289                    ) ) {
8290                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8291                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8292                           if(engineOpponent) {
8293                             SendToProgram("force\n", engineOpponent); // suppress reply
8294                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8295                           }
8296                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8297                           return 1;
8298                      }
8299                 } else moveCount = 6;
8300             }
8301
8302         // Repetition draws and 50-move rule can be applied independently of legality testing
8303
8304                 /* Check for rep-draws */
8305                 count = 0;
8306                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8307                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8308                 for(k = forwardMostMove-2;
8309                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8310                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8311                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8312                     k-=2)
8313                 {   int rights=0;
8314                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8315                         /* compare castling rights */
8316                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8317                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8318                                 rights++; /* King lost rights, while rook still had them */
8319                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8320                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8321                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8322                                    rights++; /* but at least one rook lost them */
8323                         }
8324                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8325                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8326                                 rights++;
8327                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8328                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8329                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8330                                    rights++;
8331                         }
8332                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8333                             && appData.drawRepeats > 1) {
8334                              /* adjudicate after user-specified nr of repeats */
8335                              int result = GameIsDrawn;
8336                              char *details = "XBoard adjudication: repetition draw";
8337                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8338                                 // [HGM] xiangqi: check for forbidden perpetuals
8339                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8340                                 for(m=forwardMostMove; m>k; m-=2) {
8341                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8342                                         ourPerpetual = 0; // the current mover did not always check
8343                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8344                                         hisPerpetual = 0; // the opponent did not always check
8345                                 }
8346                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8347                                                                         ourPerpetual, hisPerpetual);
8348                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8349                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8350                                     details = "Xboard adjudication: perpetual checking";
8351                                 } else
8352                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8353                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8354                                 } else
8355                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8356                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8357                                         result = BlackWins;
8358                                         details = "Xboard adjudication: repetition";
8359                                     }
8360                                 } else // it must be XQ
8361                                 // Now check for perpetual chases
8362                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8363                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8364                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8365                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8366                                         static char resdet[MSG_SIZ];
8367                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8368                                         details = resdet;
8369                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8370                                     } else
8371                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8372                                         break; // Abort repetition-checking loop.
8373                                 }
8374                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8375                              }
8376                              if(engineOpponent) {
8377                                SendToProgram("force\n", engineOpponent); // suppress reply
8378                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8379                              }
8380                              GameEnds( result, details, GE_XBOARD );
8381                              return 1;
8382                         }
8383                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8384                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8385                     }
8386                 }
8387
8388                 /* Now we test for 50-move draws. Determine ply count */
8389                 count = forwardMostMove;
8390                 /* look for last irreversble move */
8391                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8392                     count--;
8393                 /* if we hit starting position, add initial plies */
8394                 if( count == backwardMostMove )
8395                     count -= initialRulePlies;
8396                 count = forwardMostMove - count;
8397                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8398                         // adjust reversible move counter for checks in Xiangqi
8399                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8400                         if(i < backwardMostMove) i = backwardMostMove;
8401                         while(i <= forwardMostMove) {
8402                                 lastCheck = inCheck; // check evasion does not count
8403                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8404                                 if(inCheck || lastCheck) count--; // check does not count
8405                                 i++;
8406                         }
8407                 }
8408                 if( count >= 100)
8409                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8410                          /* this is used to judge if draw claims are legal */
8411                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8412                          if(engineOpponent) {
8413                            SendToProgram("force\n", engineOpponent); // suppress reply
8414                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8415                          }
8416                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8417                          return 1;
8418                 }
8419
8420                 /* if draw offer is pending, treat it as a draw claim
8421                  * when draw condition present, to allow engines a way to
8422                  * claim draws before making their move to avoid a race
8423                  * condition occurring after their move
8424                  */
8425                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8426                          char *p = NULL;
8427                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8428                              p = "Draw claim: 50-move rule";
8429                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8430                              p = "Draw claim: 3-fold repetition";
8431                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8432                              p = "Draw claim: insufficient mating material";
8433                          if( p != NULL && canAdjudicate) {
8434                              if(engineOpponent) {
8435                                SendToProgram("force\n", engineOpponent); // suppress reply
8436                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8437                              }
8438                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8439                              return 1;
8440                          }
8441                 }
8442
8443                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8444                     if(engineOpponent) {
8445                       SendToProgram("force\n", engineOpponent); // suppress reply
8446                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8447                     }
8448                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8449                     return 1;
8450                 }
8451         return 0;
8452 }
8453
8454 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8455 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8456 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8457
8458 static int
8459 BitbaseProbe ()
8460 {
8461     int pieces[10], squares[10], cnt=0, r, f, res;
8462     static int loaded;
8463     static PPROBE_EGBB probeBB;
8464     if(!appData.testLegality) return 10;
8465     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8466     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8467     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8468     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8469         ChessSquare piece = boards[forwardMostMove][r][f];
8470         int black = (piece >= BlackPawn);
8471         int type = piece - black*BlackPawn;
8472         if(piece == EmptySquare) continue;
8473         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8474         if(type == WhiteKing) type = WhiteQueen + 1;
8475         type = egbbCode[type];
8476         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8477         pieces[cnt] = type + black*6;
8478         if(++cnt > 5) return 11;
8479     }
8480     pieces[cnt] = squares[cnt] = 0;
8481     // probe EGBB
8482     if(loaded == 2) return 13; // loading failed before
8483     if(loaded == 0) {
8484         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8485         HMODULE lib;
8486         PLOAD_EGBB loadBB;
8487         loaded = 2; // prepare for failure
8488         if(!path) return 13; // no egbb installed
8489         strncpy(buf, path + 8, MSG_SIZ);
8490         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8491         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8492         lib = LoadLibrary(buf);
8493         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8494         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8495         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8496         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8497         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8498         loaded = 1; // success!
8499     }
8500     res = probeBB(forwardMostMove & 1, pieces, squares);
8501     return res > 0 ? 1 : res < 0 ? -1 : 0;
8502 }
8503
8504 char *
8505 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8506 {   // [HGM] book: this routine intercepts moves to simulate book replies
8507     char *bookHit = NULL;
8508
8509     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8510         char buf[MSG_SIZ];
8511         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8512         SendToProgram(buf, cps);
8513     }
8514     //first determine if the incoming move brings opponent into his book
8515     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8516         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8517     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8518     if(bookHit != NULL && !cps->bookSuspend) {
8519         // make sure opponent is not going to reply after receiving move to book position
8520         SendToProgram("force\n", cps);
8521         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8522     }
8523     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8524     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8525     // now arrange restart after book miss
8526     if(bookHit) {
8527         // after a book hit we never send 'go', and the code after the call to this routine
8528         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8529         char buf[MSG_SIZ], *move = bookHit;
8530         if(cps->useSAN) {
8531             int fromX, fromY, toX, toY;
8532             char promoChar;
8533             ChessMove moveType;
8534             move = buf + 30;
8535             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8536                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8537                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8538                                     PosFlags(forwardMostMove),
8539                                     fromY, fromX, toY, toX, promoChar, move);
8540             } else {
8541                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8542                 bookHit = NULL;
8543             }
8544         }
8545         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8546         SendToProgram(buf, cps);
8547         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8548     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8549         SendToProgram("go\n", cps);
8550         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8551     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8552         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8553             SendToProgram("go\n", cps);
8554         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8555     }
8556     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8557 }
8558
8559 int
8560 LoadError (char *errmess, ChessProgramState *cps)
8561 {   // unloads engine and switches back to -ncp mode if it was first
8562     if(cps->initDone) return FALSE;
8563     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8564     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8565     cps->pr = NoProc;
8566     if(cps == &first) {
8567         appData.noChessProgram = TRUE;
8568         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8569         gameMode = BeginningOfGame; ModeHighlight();
8570         SetNCPMode();
8571     }
8572     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8573     DisplayMessage("", ""); // erase waiting message
8574     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8575     return TRUE;
8576 }
8577
8578 char *savedMessage;
8579 ChessProgramState *savedState;
8580 void
8581 DeferredBookMove (void)
8582 {
8583         if(savedState->lastPing != savedState->lastPong)
8584                     ScheduleDelayedEvent(DeferredBookMove, 10);
8585         else
8586         HandleMachineMove(savedMessage, savedState);
8587 }
8588
8589 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8590 static ChessProgramState *stalledEngine;
8591 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8592
8593 void
8594 HandleMachineMove (char *message, ChessProgramState *cps)
8595 {
8596     static char firstLeg[20];
8597     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8598     char realname[MSG_SIZ];
8599     int fromX, fromY, toX, toY;
8600     ChessMove moveType;
8601     char promoChar, roar;
8602     char *p, *pv=buf1;
8603     int machineWhite, oldError;
8604     char *bookHit;
8605
8606     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8607         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8608         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8609             DisplayError(_("Invalid pairing from pairing engine"), 0);
8610             return;
8611         }
8612         pairingReceived = 1;
8613         NextMatchGame();
8614         return; // Skim the pairing messages here.
8615     }
8616
8617     oldError = cps->userError; cps->userError = 0;
8618
8619 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8620     /*
8621      * Kludge to ignore BEL characters
8622      */
8623     while (*message == '\007') message++;
8624
8625     /*
8626      * [HGM] engine debug message: ignore lines starting with '#' character
8627      */
8628     if(cps->debug && *message == '#') return;
8629
8630     /*
8631      * Look for book output
8632      */
8633     if (cps == &first && bookRequested) {
8634         if (message[0] == '\t' || message[0] == ' ') {
8635             /* Part of the book output is here; append it */
8636             strcat(bookOutput, message);
8637             strcat(bookOutput, "  \n");
8638             return;
8639         } else if (bookOutput[0] != NULLCHAR) {
8640             /* All of book output has arrived; display it */
8641             char *p = bookOutput;
8642             while (*p != NULLCHAR) {
8643                 if (*p == '\t') *p = ' ';
8644                 p++;
8645             }
8646             DisplayInformation(bookOutput);
8647             bookRequested = FALSE;
8648             /* Fall through to parse the current output */
8649         }
8650     }
8651
8652     /*
8653      * Look for machine move.
8654      */
8655     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8656         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8657     {
8658         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8659             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8660             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8661             stalledEngine = cps;
8662             if(appData.ponderNextMove) { // bring opponent out of ponder
8663                 if(gameMode == TwoMachinesPlay) {
8664                     if(cps->other->pause)
8665                         PauseEngine(cps->other);
8666                     else
8667                         SendToProgram("easy\n", cps->other);
8668                 }
8669             }
8670             StopClocks();
8671             return;
8672         }
8673
8674       if(cps->usePing) {
8675
8676         /* This method is only useful on engines that support ping */
8677         if(abortEngineThink) {
8678             if (appData.debugMode) {
8679                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8680             }
8681             SendToProgram("undo\n", cps);
8682             return;
8683         }
8684
8685         if (cps->lastPing != cps->lastPong) {
8686             /* Extra move from before last new; ignore */
8687             if (appData.debugMode) {
8688                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8689             }
8690           return;
8691         }
8692
8693       } else {
8694
8695         switch (gameMode) {
8696           case BeginningOfGame:
8697             /* Extra move from before last reset; ignore */
8698             if (appData.debugMode) {
8699                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8700             }
8701             return;
8702
8703           case EndOfGame:
8704           case IcsIdle:
8705           default:
8706             /* Extra move after we tried to stop.  The mode test is
8707                not a reliable way of detecting this problem, but it's
8708                the best we can do on engines that don't support ping.
8709             */
8710             if (appData.debugMode) {
8711                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8712                         cps->which, gameMode);
8713             }
8714             SendToProgram("undo\n", cps);
8715             return;
8716
8717           case MachinePlaysWhite:
8718           case IcsPlayingWhite:
8719             machineWhite = TRUE;
8720             break;
8721
8722           case MachinePlaysBlack:
8723           case IcsPlayingBlack:
8724             machineWhite = FALSE;
8725             break;
8726
8727           case TwoMachinesPlay:
8728             machineWhite = (cps->twoMachinesColor[0] == 'w');
8729             break;
8730         }
8731         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8732             if (appData.debugMode) {
8733                 fprintf(debugFP,
8734                         "Ignoring move out of turn by %s, gameMode %d"
8735                         ", forwardMost %d\n",
8736                         cps->which, gameMode, forwardMostMove);
8737             }
8738             return;
8739         }
8740       }
8741
8742         if(cps->alphaRank) AlphaRank(machineMove, 4);
8743
8744         // [HGM] lion: (some very limited) support for Alien protocol
8745         killX = killY = kill2X = kill2Y = -1;
8746         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8747             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8748             return;
8749         }
8750         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8751             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8752             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8753         }
8754         if(firstLeg[0]) { // there was a previous leg;
8755             // only support case where same piece makes two step
8756             char buf[20], *p = machineMove+1, *q = buf+1, f;
8757             safeStrCpy(buf, machineMove, 20);
8758             while(isdigit(*q)) q++; // find start of to-square
8759             safeStrCpy(machineMove, firstLeg, 20);
8760             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8761             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8762             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8763             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8764             firstLeg[0] = NULLCHAR;
8765         }
8766
8767         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8768                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8769             /* Machine move could not be parsed; ignore it. */
8770           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8771                     machineMove, _(cps->which));
8772             DisplayMoveError(buf1);
8773             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8774                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8775             if (gameMode == TwoMachinesPlay) {
8776               GameEnds(machineWhite ? BlackWins : WhiteWins,
8777                        buf1, GE_XBOARD);
8778             }
8779             return;
8780         }
8781
8782         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8783         /* So we have to redo legality test with true e.p. status here,  */
8784         /* to make sure an illegal e.p. capture does not slip through,   */
8785         /* to cause a forfeit on a justified illegal-move complaint      */
8786         /* of the opponent.                                              */
8787         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8788            ChessMove moveType;
8789            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8790                              fromY, fromX, toY, toX, promoChar);
8791             if(moveType == IllegalMove) {
8792               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8793                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8794                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8795                            buf1, GE_XBOARD);
8796                 return;
8797            } else if(!appData.fischerCastling)
8798            /* [HGM] Kludge to handle engines that send FRC-style castling
8799               when they shouldn't (like TSCP-Gothic) */
8800            switch(moveType) {
8801              case WhiteASideCastleFR:
8802              case BlackASideCastleFR:
8803                toX+=2;
8804                currentMoveString[2]++;
8805                break;
8806              case WhiteHSideCastleFR:
8807              case BlackHSideCastleFR:
8808                toX--;
8809                currentMoveString[2]--;
8810                break;
8811              default: ; // nothing to do, but suppresses warning of pedantic compilers
8812            }
8813         }
8814         hintRequested = FALSE;
8815         lastHint[0] = NULLCHAR;
8816         bookRequested = FALSE;
8817         /* Program may be pondering now */
8818         cps->maybeThinking = TRUE;
8819         if (cps->sendTime == 2) cps->sendTime = 1;
8820         if (cps->offeredDraw) cps->offeredDraw--;
8821
8822         /* [AS] Save move info*/
8823         pvInfoList[ forwardMostMove ].score = programStats.score;
8824         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8825         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8826
8827         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8828
8829         /* Test suites abort the 'game' after one move */
8830         if(*appData.finger) {
8831            static FILE *f;
8832            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8833            if(!f) f = fopen(appData.finger, "w");
8834            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8835            else { DisplayFatalError("Bad output file", errno, 0); return; }
8836            free(fen);
8837            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8838         }
8839         if(appData.epd) {
8840            if(solvingTime >= 0) {
8841               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8842               totalTime += solvingTime; first.matchWins++;
8843            } else {
8844               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8845               second.matchWins++;
8846            }
8847            OutputKibitz(2, buf1);
8848            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8849         }
8850
8851         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8852         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8853             int count = 0;
8854
8855             while( count < adjudicateLossPlies ) {
8856                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8857
8858                 if( count & 1 ) {
8859                     score = -score; /* Flip score for winning side */
8860                 }
8861
8862                 if( score > appData.adjudicateLossThreshold ) {
8863                     break;
8864                 }
8865
8866                 count++;
8867             }
8868
8869             if( count >= adjudicateLossPlies ) {
8870                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8871
8872                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8873                     "Xboard adjudication",
8874                     GE_XBOARD );
8875
8876                 return;
8877             }
8878         }
8879
8880         if(Adjudicate(cps)) {
8881             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8882             return; // [HGM] adjudicate: for all automatic game ends
8883         }
8884
8885 #if ZIPPY
8886         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8887             first.initDone) {
8888           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8889                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8890                 SendToICS("draw ");
8891                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8892           }
8893           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8894           ics_user_moved = 1;
8895           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8896                 char buf[3*MSG_SIZ];
8897
8898                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8899                         programStats.score / 100.,
8900                         programStats.depth,
8901                         programStats.time / 100.,
8902                         (unsigned int)programStats.nodes,
8903                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8904                         programStats.movelist);
8905                 SendToICS(buf);
8906           }
8907         }
8908 #endif
8909
8910         /* [AS] Clear stats for next move */
8911         ClearProgramStats();
8912         thinkOutput[0] = NULLCHAR;
8913         hiddenThinkOutputState = 0;
8914
8915         bookHit = NULL;
8916         if (gameMode == TwoMachinesPlay) {
8917             /* [HGM] relaying draw offers moved to after reception of move */
8918             /* and interpreting offer as claim if it brings draw condition */
8919             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8920                 SendToProgram("draw\n", cps->other);
8921             }
8922             if (cps->other->sendTime) {
8923                 SendTimeRemaining(cps->other,
8924                                   cps->other->twoMachinesColor[0] == 'w');
8925             }
8926             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8927             if (firstMove && !bookHit) {
8928                 firstMove = FALSE;
8929                 if (cps->other->useColors) {
8930                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8931                 }
8932                 SendToProgram("go\n", cps->other);
8933             }
8934             cps->other->maybeThinking = TRUE;
8935         }
8936
8937         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8938
8939         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8940
8941         if (!pausing && appData.ringBellAfterMoves) {
8942             if(!roar) RingBell();
8943         }
8944
8945         /*
8946          * Reenable menu items that were disabled while
8947          * machine was thinking
8948          */
8949         if (gameMode != TwoMachinesPlay)
8950             SetUserThinkingEnables();
8951
8952         // [HGM] book: after book hit opponent has received move and is now in force mode
8953         // force the book reply into it, and then fake that it outputted this move by jumping
8954         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8955         if(bookHit) {
8956                 static char bookMove[MSG_SIZ]; // a bit generous?
8957
8958                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8959                 strcat(bookMove, bookHit);
8960                 message = bookMove;
8961                 cps = cps->other;
8962                 programStats.nodes = programStats.depth = programStats.time =
8963                 programStats.score = programStats.got_only_move = 0;
8964                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8965
8966                 if(cps->lastPing != cps->lastPong) {
8967                     savedMessage = message; // args for deferred call
8968                     savedState = cps;
8969                     ScheduleDelayedEvent(DeferredBookMove, 10);
8970                     return;
8971                 }
8972                 goto FakeBookMove;
8973         }
8974
8975         return;
8976     }
8977
8978     /* Set special modes for chess engines.  Later something general
8979      *  could be added here; for now there is just one kludge feature,
8980      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8981      *  when "xboard" is given as an interactive command.
8982      */
8983     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8984         cps->useSigint = FALSE;
8985         cps->useSigterm = FALSE;
8986     }
8987     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8988       ParseFeatures(message+8, cps);
8989       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8990     }
8991
8992     if (!strncmp(message, "setup ", 6) && 
8993         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8994           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8995                                         ) { // [HGM] allow first engine to define opening position
8996       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8997       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8998       *buf = NULLCHAR;
8999       if(sscanf(message, "setup (%s", buf) == 1) {
9000         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9001         ASSIGN(appData.pieceToCharTable, buf);
9002       }
9003       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9004       if(dummy >= 3) {
9005         while(message[s] && message[s++] != ' ');
9006         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9007            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9008             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9009             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9010           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9011           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9012           startedFromSetupPosition = FALSE;
9013         }
9014       }
9015       if(startedFromSetupPosition) return;
9016       ParseFEN(boards[0], &dummy, message+s, FALSE);
9017       DrawPosition(TRUE, boards[0]);
9018       CopyBoard(initialPosition, boards[0]);
9019       startedFromSetupPosition = TRUE;
9020       return;
9021     }
9022     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9023       ChessSquare piece = WhitePawn;
9024       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9025       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
9026       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9027       piece += CharToPiece(ID & 255) - WhitePawn;
9028       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9029       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9030       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9031       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9032       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9033       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9034                                                && gameInfo.variant != VariantGreat
9035                                                && gameInfo.variant != VariantFairy    ) return;
9036       if(piece < EmptySquare) {
9037         pieceDefs = TRUE;
9038         ASSIGN(pieceDesc[piece], buf1);
9039         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9040       }
9041       return;
9042     }
9043     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9044      * want this, I was asked to put it in, and obliged.
9045      */
9046     if (!strncmp(message, "setboard ", 9)) {
9047         Board initial_position;
9048
9049         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9050
9051         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9052             DisplayError(_("Bad FEN received from engine"), 0);
9053             return ;
9054         } else {
9055            Reset(TRUE, FALSE);
9056            CopyBoard(boards[0], initial_position);
9057            initialRulePlies = FENrulePlies;
9058            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9059            else gameMode = MachinePlaysBlack;
9060            DrawPosition(FALSE, boards[currentMove]);
9061         }
9062         return;
9063     }
9064
9065     /*
9066      * Look for communication commands
9067      */
9068     if (!strncmp(message, "telluser ", 9)) {
9069         if(message[9] == '\\' && message[10] == '\\')
9070             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9071         PlayTellSound();
9072         DisplayNote(message + 9);
9073         return;
9074     }
9075     if (!strncmp(message, "tellusererror ", 14)) {
9076         cps->userError = 1;
9077         if(message[14] == '\\' && message[15] == '\\')
9078             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9079         PlayTellSound();
9080         DisplayError(message + 14, 0);
9081         return;
9082     }
9083     if (!strncmp(message, "tellopponent ", 13)) {
9084       if (appData.icsActive) {
9085         if (loggedOn) {
9086           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9087           SendToICS(buf1);
9088         }
9089       } else {
9090         DisplayNote(message + 13);
9091       }
9092       return;
9093     }
9094     if (!strncmp(message, "tellothers ", 11)) {
9095       if (appData.icsActive) {
9096         if (loggedOn) {
9097           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9098           SendToICS(buf1);
9099         }
9100       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9101       return;
9102     }
9103     if (!strncmp(message, "tellall ", 8)) {
9104       if (appData.icsActive) {
9105         if (loggedOn) {
9106           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9107           SendToICS(buf1);
9108         }
9109       } else {
9110         DisplayNote(message + 8);
9111       }
9112       return;
9113     }
9114     if (strncmp(message, "warning", 7) == 0) {
9115         /* Undocumented feature, use tellusererror in new code */
9116         DisplayError(message, 0);
9117         return;
9118     }
9119     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9120         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9121         strcat(realname, " query");
9122         AskQuestion(realname, buf2, buf1, cps->pr);
9123         return;
9124     }
9125     /* Commands from the engine directly to ICS.  We don't allow these to be
9126      *  sent until we are logged on. Crafty kibitzes have been known to
9127      *  interfere with the login process.
9128      */
9129     if (loggedOn) {
9130         if (!strncmp(message, "tellics ", 8)) {
9131             SendToICS(message + 8);
9132             SendToICS("\n");
9133             return;
9134         }
9135         if (!strncmp(message, "tellicsnoalias ", 15)) {
9136             SendToICS(ics_prefix);
9137             SendToICS(message + 15);
9138             SendToICS("\n");
9139             return;
9140         }
9141         /* The following are for backward compatibility only */
9142         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9143             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9144             SendToICS(ics_prefix);
9145             SendToICS(message);
9146             SendToICS("\n");
9147             return;
9148         }
9149     }
9150     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9151         if(initPing == cps->lastPong) {
9152             if(gameInfo.variant == VariantUnknown) {
9153                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9154                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9155                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9156             }
9157             initPing = -1;
9158         }
9159         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9160             abortEngineThink = FALSE;
9161             DisplayMessage("", "");
9162             ThawUI();
9163         }
9164         return;
9165     }
9166     if(!strncmp(message, "highlight ", 10)) {
9167         if(appData.testLegality && !*engineVariant && appData.markers) return;
9168         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9169         return;
9170     }
9171     if(!strncmp(message, "click ", 6)) {
9172         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9173         if(appData.testLegality || !appData.oneClick) return;
9174         sscanf(message+6, "%c%d%c", &f, &y, &c);
9175         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9176         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9177         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9178         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9179         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9180         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9181             LeftClick(Release, lastLeftX, lastLeftY);
9182         controlKey  = (c == ',');
9183         LeftClick(Press, x, y);
9184         LeftClick(Release, x, y);
9185         first.highlight = f;
9186         return;
9187     }
9188     /*
9189      * If the move is illegal, cancel it and redraw the board.
9190      * Also deal with other error cases.  Matching is rather loose
9191      * here to accommodate engines written before the spec.
9192      */
9193     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9194         strncmp(message, "Error", 5) == 0) {
9195         if (StrStr(message, "name") ||
9196             StrStr(message, "rating") || StrStr(message, "?") ||
9197             StrStr(message, "result") || StrStr(message, "board") ||
9198             StrStr(message, "bk") || StrStr(message, "computer") ||
9199             StrStr(message, "variant") || StrStr(message, "hint") ||
9200             StrStr(message, "random") || StrStr(message, "depth") ||
9201             StrStr(message, "accepted")) {
9202             return;
9203         }
9204         if (StrStr(message, "protover")) {
9205           /* Program is responding to input, so it's apparently done
9206              initializing, and this error message indicates it is
9207              protocol version 1.  So we don't need to wait any longer
9208              for it to initialize and send feature commands. */
9209           FeatureDone(cps, 1);
9210           cps->protocolVersion = 1;
9211           return;
9212         }
9213         cps->maybeThinking = FALSE;
9214
9215         if (StrStr(message, "draw")) {
9216             /* Program doesn't have "draw" command */
9217             cps->sendDrawOffers = 0;
9218             return;
9219         }
9220         if (cps->sendTime != 1 &&
9221             (StrStr(message, "time") || StrStr(message, "otim"))) {
9222           /* Program apparently doesn't have "time" or "otim" command */
9223           cps->sendTime = 0;
9224           return;
9225         }
9226         if (StrStr(message, "analyze")) {
9227             cps->analysisSupport = FALSE;
9228             cps->analyzing = FALSE;
9229 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9230             EditGameEvent(); // [HGM] try to preserve loaded game
9231             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9232             DisplayError(buf2, 0);
9233             return;
9234         }
9235         if (StrStr(message, "(no matching move)st")) {
9236           /* Special kludge for GNU Chess 4 only */
9237           cps->stKludge = TRUE;
9238           SendTimeControl(cps, movesPerSession, timeControl,
9239                           timeIncrement, appData.searchDepth,
9240                           searchTime);
9241           return;
9242         }
9243         if (StrStr(message, "(no matching move)sd")) {
9244           /* Special kludge for GNU Chess 4 only */
9245           cps->sdKludge = TRUE;
9246           SendTimeControl(cps, movesPerSession, timeControl,
9247                           timeIncrement, appData.searchDepth,
9248                           searchTime);
9249           return;
9250         }
9251         if (!StrStr(message, "llegal")) {
9252             return;
9253         }
9254         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9255             gameMode == IcsIdle) return;
9256         if (forwardMostMove <= backwardMostMove) return;
9257         if (pausing) PauseEvent();
9258       if(appData.forceIllegal) {
9259             // [HGM] illegal: machine refused move; force position after move into it
9260           SendToProgram("force\n", cps);
9261           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9262                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9263                 // when black is to move, while there might be nothing on a2 or black
9264                 // might already have the move. So send the board as if white has the move.
9265                 // But first we must change the stm of the engine, as it refused the last move
9266                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9267                 if(WhiteOnMove(forwardMostMove)) {
9268                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9269                     SendBoard(cps, forwardMostMove); // kludgeless board
9270                 } else {
9271                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9272                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9273                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9274                 }
9275           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9276             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9277                  gameMode == TwoMachinesPlay)
9278               SendToProgram("go\n", cps);
9279             return;
9280       } else
9281         if (gameMode == PlayFromGameFile) {
9282             /* Stop reading this game file */
9283             gameMode = EditGame;
9284             ModeHighlight();
9285         }
9286         /* [HGM] illegal-move claim should forfeit game when Xboard */
9287         /* only passes fully legal moves                            */
9288         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9289             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9290                                 "False illegal-move claim", GE_XBOARD );
9291             return; // do not take back move we tested as valid
9292         }
9293         currentMove = forwardMostMove-1;
9294         DisplayMove(currentMove-1); /* before DisplayMoveError */
9295         SwitchClocks(forwardMostMove-1); // [HGM] race
9296         DisplayBothClocks();
9297         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9298                 parseList[currentMove], _(cps->which));
9299         DisplayMoveError(buf1);
9300         DrawPosition(FALSE, boards[currentMove]);
9301
9302         SetUserThinkingEnables();
9303         return;
9304     }
9305     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9306         /* Program has a broken "time" command that
9307            outputs a string not ending in newline.
9308            Don't use it. */
9309         cps->sendTime = 0;
9310     }
9311     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9312         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9313             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9314     }
9315
9316     /*
9317      * If chess program startup fails, exit with an error message.
9318      * Attempts to recover here are futile. [HGM] Well, we try anyway
9319      */
9320     if ((StrStr(message, "unknown host") != NULL)
9321         || (StrStr(message, "No remote directory") != NULL)
9322         || (StrStr(message, "not found") != NULL)
9323         || (StrStr(message, "No such file") != NULL)
9324         || (StrStr(message, "can't alloc") != NULL)
9325         || (StrStr(message, "Permission denied") != NULL)) {
9326
9327         cps->maybeThinking = FALSE;
9328         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9329                 _(cps->which), cps->program, cps->host, message);
9330         RemoveInputSource(cps->isr);
9331         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9332             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9333             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9334         }
9335         return;
9336     }
9337
9338     /*
9339      * Look for hint output
9340      */
9341     if (sscanf(message, "Hint: %s", buf1) == 1) {
9342         if (cps == &first && hintRequested) {
9343             hintRequested = FALSE;
9344             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9345                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9346                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9347                                     PosFlags(forwardMostMove),
9348                                     fromY, fromX, toY, toX, promoChar, buf1);
9349                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9350                 DisplayInformation(buf2);
9351             } else {
9352                 /* Hint move could not be parsed!? */
9353               snprintf(buf2, sizeof(buf2),
9354                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9355                         buf1, _(cps->which));
9356                 DisplayError(buf2, 0);
9357             }
9358         } else {
9359           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9360         }
9361         return;
9362     }
9363
9364     /*
9365      * Ignore other messages if game is not in progress
9366      */
9367     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9368         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9369
9370     /*
9371      * look for win, lose, draw, or draw offer
9372      */
9373     if (strncmp(message, "1-0", 3) == 0) {
9374         char *p, *q, *r = "";
9375         p = strchr(message, '{');
9376         if (p) {
9377             q = strchr(p, '}');
9378             if (q) {
9379                 *q = NULLCHAR;
9380                 r = p + 1;
9381             }
9382         }
9383         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9384         return;
9385     } else if (strncmp(message, "0-1", 3) == 0) {
9386         char *p, *q, *r = "";
9387         p = strchr(message, '{');
9388         if (p) {
9389             q = strchr(p, '}');
9390             if (q) {
9391                 *q = NULLCHAR;
9392                 r = p + 1;
9393             }
9394         }
9395         /* Kludge for Arasan 4.1 bug */
9396         if (strcmp(r, "Black resigns") == 0) {
9397             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9398             return;
9399         }
9400         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9401         return;
9402     } else if (strncmp(message, "1/2", 3) == 0) {
9403         char *p, *q, *r = "";
9404         p = strchr(message, '{');
9405         if (p) {
9406             q = strchr(p, '}');
9407             if (q) {
9408                 *q = NULLCHAR;
9409                 r = p + 1;
9410             }
9411         }
9412
9413         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9414         return;
9415
9416     } else if (strncmp(message, "White resign", 12) == 0) {
9417         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9418         return;
9419     } else if (strncmp(message, "Black resign", 12) == 0) {
9420         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9421         return;
9422     } else if (strncmp(message, "White matches", 13) == 0 ||
9423                strncmp(message, "Black matches", 13) == 0   ) {
9424         /* [HGM] ignore GNUShogi noises */
9425         return;
9426     } else if (strncmp(message, "White", 5) == 0 &&
9427                message[5] != '(' &&
9428                StrStr(message, "Black") == NULL) {
9429         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9430         return;
9431     } else if (strncmp(message, "Black", 5) == 0 &&
9432                message[5] != '(') {
9433         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9434         return;
9435     } else if (strcmp(message, "resign") == 0 ||
9436                strcmp(message, "computer resigns") == 0) {
9437         switch (gameMode) {
9438           case MachinePlaysBlack:
9439           case IcsPlayingBlack:
9440             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9441             break;
9442           case MachinePlaysWhite:
9443           case IcsPlayingWhite:
9444             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9445             break;
9446           case TwoMachinesPlay:
9447             if (cps->twoMachinesColor[0] == 'w')
9448               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9449             else
9450               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9451             break;
9452           default:
9453             /* can't happen */
9454             break;
9455         }
9456         return;
9457     } else if (strncmp(message, "opponent mates", 14) == 0) {
9458         switch (gameMode) {
9459           case MachinePlaysBlack:
9460           case IcsPlayingBlack:
9461             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9462             break;
9463           case MachinePlaysWhite:
9464           case IcsPlayingWhite:
9465             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9466             break;
9467           case TwoMachinesPlay:
9468             if (cps->twoMachinesColor[0] == 'w')
9469               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9470             else
9471               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9472             break;
9473           default:
9474             /* can't happen */
9475             break;
9476         }
9477         return;
9478     } else if (strncmp(message, "computer mates", 14) == 0) {
9479         switch (gameMode) {
9480           case MachinePlaysBlack:
9481           case IcsPlayingBlack:
9482             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9483             break;
9484           case MachinePlaysWhite:
9485           case IcsPlayingWhite:
9486             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9487             break;
9488           case TwoMachinesPlay:
9489             if (cps->twoMachinesColor[0] == 'w')
9490               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9491             else
9492               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9493             break;
9494           default:
9495             /* can't happen */
9496             break;
9497         }
9498         return;
9499     } else if (strncmp(message, "checkmate", 9) == 0) {
9500         if (WhiteOnMove(forwardMostMove)) {
9501             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9502         } else {
9503             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9504         }
9505         return;
9506     } else if (strstr(message, "Draw") != NULL ||
9507                strstr(message, "game is a draw") != NULL) {
9508         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9509         return;
9510     } else if (strstr(message, "offer") != NULL &&
9511                strstr(message, "draw") != NULL) {
9512 #if ZIPPY
9513         if (appData.zippyPlay && first.initDone) {
9514             /* Relay offer to ICS */
9515             SendToICS(ics_prefix);
9516             SendToICS("draw\n");
9517         }
9518 #endif
9519         cps->offeredDraw = 2; /* valid until this engine moves twice */
9520         if (gameMode == TwoMachinesPlay) {
9521             if (cps->other->offeredDraw) {
9522                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9523             /* [HGM] in two-machine mode we delay relaying draw offer      */
9524             /* until after we also have move, to see if it is really claim */
9525             }
9526         } else if (gameMode == MachinePlaysWhite ||
9527                    gameMode == MachinePlaysBlack) {
9528           if (userOfferedDraw) {
9529             DisplayInformation(_("Machine accepts your draw offer"));
9530             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9531           } else {
9532             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9533           }
9534         }
9535     }
9536
9537
9538     /*
9539      * Look for thinking output
9540      */
9541     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9542           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9543                                 ) {
9544         int plylev, mvleft, mvtot, curscore, time;
9545         char mvname[MOVE_LEN];
9546         u64 nodes; // [DM]
9547         char plyext;
9548         int ignore = FALSE;
9549         int prefixHint = FALSE;
9550         mvname[0] = NULLCHAR;
9551
9552         switch (gameMode) {
9553           case MachinePlaysBlack:
9554           case IcsPlayingBlack:
9555             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9556             break;
9557           case MachinePlaysWhite:
9558           case IcsPlayingWhite:
9559             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9560             break;
9561           case AnalyzeMode:
9562           case AnalyzeFile:
9563             break;
9564           case IcsObserving: /* [DM] icsEngineAnalyze */
9565             if (!appData.icsEngineAnalyze) ignore = TRUE;
9566             break;
9567           case TwoMachinesPlay:
9568             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9569                 ignore = TRUE;
9570             }
9571             break;
9572           default:
9573             ignore = TRUE;
9574             break;
9575         }
9576
9577         if (!ignore) {
9578             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9579             buf1[0] = NULLCHAR;
9580             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9581                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9582                 char score_buf[MSG_SIZ];
9583
9584                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9585                     nodes += u64Const(0x100000000);
9586
9587                 if (plyext != ' ' && plyext != '\t') {
9588                     time *= 100;
9589                 }
9590
9591                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9592                 if( cps->scoreIsAbsolute &&
9593                     ( gameMode == MachinePlaysBlack ||
9594                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9595                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9596                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9597                      !WhiteOnMove(currentMove)
9598                     ) )
9599                 {
9600                     curscore = -curscore;
9601                 }
9602
9603                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9604
9605                 if(*bestMove) { // rememer time best EPD move was first found
9606                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9607                     ChessMove mt;
9608                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9609                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9610                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9611                 }
9612
9613                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9614                         char buf[MSG_SIZ];
9615                         FILE *f;
9616                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9617                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9618                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9619                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9620                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9621                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9622                                 fclose(f);
9623                         }
9624                         else
9625                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9626                           DisplayError(_("failed writing PV"), 0);
9627                 }
9628
9629                 tempStats.depth = plylev;
9630                 tempStats.nodes = nodes;
9631                 tempStats.time = time;
9632                 tempStats.score = curscore;
9633                 tempStats.got_only_move = 0;
9634
9635                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9636                         int ticklen;
9637
9638                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9639                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9640                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9641                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9642                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9643                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9644                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9645                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9646                 }
9647
9648                 /* Buffer overflow protection */
9649                 if (pv[0] != NULLCHAR) {
9650                     if (strlen(pv) >= sizeof(tempStats.movelist)
9651                         && appData.debugMode) {
9652                         fprintf(debugFP,
9653                                 "PV is too long; using the first %u bytes.\n",
9654                                 (unsigned) sizeof(tempStats.movelist) - 1);
9655                     }
9656
9657                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9658                 } else {
9659                     sprintf(tempStats.movelist, " no PV\n");
9660                 }
9661
9662                 if (tempStats.seen_stat) {
9663                     tempStats.ok_to_send = 1;
9664                 }
9665
9666                 if (strchr(tempStats.movelist, '(') != NULL) {
9667                     tempStats.line_is_book = 1;
9668                     tempStats.nr_moves = 0;
9669                     tempStats.moves_left = 0;
9670                 } else {
9671                     tempStats.line_is_book = 0;
9672                 }
9673
9674                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9675                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9676
9677                 SendProgramStatsToFrontend( cps, &tempStats );
9678
9679                 /*
9680                     [AS] Protect the thinkOutput buffer from overflow... this
9681                     is only useful if buf1 hasn't overflowed first!
9682                 */
9683                 if(curscore >= MATE_SCORE) 
9684                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9685                 else if(curscore <= -MATE_SCORE) 
9686                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9687                 else
9688                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9689                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9690                          plylev,
9691                          (gameMode == TwoMachinesPlay ?
9692                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9693                          score_buf,
9694                          prefixHint ? lastHint : "",
9695                          prefixHint ? " " : "" );
9696
9697                 if( buf1[0] != NULLCHAR ) {
9698                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9699
9700                     if( strlen(pv) > max_len ) {
9701                         if( appData.debugMode) {
9702                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9703                         }
9704                         pv[max_len+1] = '\0';
9705                     }
9706
9707                     strcat( thinkOutput, pv);
9708                 }
9709
9710                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9711                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9712                     DisplayMove(currentMove - 1);
9713                 }
9714                 return;
9715
9716             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9717                 /* crafty (9.25+) says "(only move) <move>"
9718                  * if there is only 1 legal move
9719                  */
9720                 sscanf(p, "(only move) %s", buf1);
9721                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9722                 sprintf(programStats.movelist, "%s (only move)", buf1);
9723                 programStats.depth = 1;
9724                 programStats.nr_moves = 1;
9725                 programStats.moves_left = 1;
9726                 programStats.nodes = 1;
9727                 programStats.time = 1;
9728                 programStats.got_only_move = 1;
9729
9730                 /* Not really, but we also use this member to
9731                    mean "line isn't going to change" (Crafty
9732                    isn't searching, so stats won't change) */
9733                 programStats.line_is_book = 1;
9734
9735                 SendProgramStatsToFrontend( cps, &programStats );
9736
9737                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9738                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9739                     DisplayMove(currentMove - 1);
9740                 }
9741                 return;
9742             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9743                               &time, &nodes, &plylev, &mvleft,
9744                               &mvtot, mvname) >= 5) {
9745                 /* The stat01: line is from Crafty (9.29+) in response
9746                    to the "." command */
9747                 programStats.seen_stat = 1;
9748                 cps->maybeThinking = TRUE;
9749
9750                 if (programStats.got_only_move || !appData.periodicUpdates)
9751                   return;
9752
9753                 programStats.depth = plylev;
9754                 programStats.time = time;
9755                 programStats.nodes = nodes;
9756                 programStats.moves_left = mvleft;
9757                 programStats.nr_moves = mvtot;
9758                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9759                 programStats.ok_to_send = 1;
9760                 programStats.movelist[0] = '\0';
9761
9762                 SendProgramStatsToFrontend( cps, &programStats );
9763
9764                 return;
9765
9766             } else if (strncmp(message,"++",2) == 0) {
9767                 /* Crafty 9.29+ outputs this */
9768                 programStats.got_fail = 2;
9769                 return;
9770
9771             } else if (strncmp(message,"--",2) == 0) {
9772                 /* Crafty 9.29+ outputs this */
9773                 programStats.got_fail = 1;
9774                 return;
9775
9776             } else if (thinkOutput[0] != NULLCHAR &&
9777                        strncmp(message, "    ", 4) == 0) {
9778                 unsigned message_len;
9779
9780                 p = message;
9781                 while (*p && *p == ' ') p++;
9782
9783                 message_len = strlen( p );
9784
9785                 /* [AS] Avoid buffer overflow */
9786                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9787                     strcat(thinkOutput, " ");
9788                     strcat(thinkOutput, p);
9789                 }
9790
9791                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9792                     strcat(programStats.movelist, " ");
9793                     strcat(programStats.movelist, p);
9794                 }
9795
9796                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9797                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9798                     DisplayMove(currentMove - 1);
9799                 }
9800                 return;
9801             }
9802         }
9803         else {
9804             buf1[0] = NULLCHAR;
9805
9806             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9807                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9808             {
9809                 ChessProgramStats cpstats;
9810
9811                 if (plyext != ' ' && plyext != '\t') {
9812                     time *= 100;
9813                 }
9814
9815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9816                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9817                     curscore = -curscore;
9818                 }
9819
9820                 cpstats.depth = plylev;
9821                 cpstats.nodes = nodes;
9822                 cpstats.time = time;
9823                 cpstats.score = curscore;
9824                 cpstats.got_only_move = 0;
9825                 cpstats.movelist[0] = '\0';
9826
9827                 if (buf1[0] != NULLCHAR) {
9828                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9829                 }
9830
9831                 cpstats.ok_to_send = 0;
9832                 cpstats.line_is_book = 0;
9833                 cpstats.nr_moves = 0;
9834                 cpstats.moves_left = 0;
9835
9836                 SendProgramStatsToFrontend( cps, &cpstats );
9837             }
9838         }
9839     }
9840 }
9841
9842
9843 /* Parse a game score from the character string "game", and
9844    record it as the history of the current game.  The game
9845    score is NOT assumed to start from the standard position.
9846    The display is not updated in any way.
9847    */
9848 void
9849 ParseGameHistory (char *game)
9850 {
9851     ChessMove moveType;
9852     int fromX, fromY, toX, toY, boardIndex;
9853     char promoChar;
9854     char *p, *q;
9855     char buf[MSG_SIZ];
9856
9857     if (appData.debugMode)
9858       fprintf(debugFP, "Parsing game history: %s\n", game);
9859
9860     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9861     gameInfo.site = StrSave(appData.icsHost);
9862     gameInfo.date = PGNDate();
9863     gameInfo.round = StrSave("-");
9864
9865     /* Parse out names of players */
9866     while (*game == ' ') game++;
9867     p = buf;
9868     while (*game != ' ') *p++ = *game++;
9869     *p = NULLCHAR;
9870     gameInfo.white = StrSave(buf);
9871     while (*game == ' ') game++;
9872     p = buf;
9873     while (*game != ' ' && *game != '\n') *p++ = *game++;
9874     *p = NULLCHAR;
9875     gameInfo.black = StrSave(buf);
9876
9877     /* Parse moves */
9878     boardIndex = blackPlaysFirst ? 1 : 0;
9879     yynewstr(game);
9880     for (;;) {
9881         yyboardindex = boardIndex;
9882         moveType = (ChessMove) Myylex();
9883         switch (moveType) {
9884           case IllegalMove:             /* maybe suicide chess, etc. */
9885   if (appData.debugMode) {
9886     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9887     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9888     setbuf(debugFP, NULL);
9889   }
9890           case WhitePromotion:
9891           case BlackPromotion:
9892           case WhiteNonPromotion:
9893           case BlackNonPromotion:
9894           case NormalMove:
9895           case FirstLeg:
9896           case WhiteCapturesEnPassant:
9897           case BlackCapturesEnPassant:
9898           case WhiteKingSideCastle:
9899           case WhiteQueenSideCastle:
9900           case BlackKingSideCastle:
9901           case BlackQueenSideCastle:
9902           case WhiteKingSideCastleWild:
9903           case WhiteQueenSideCastleWild:
9904           case BlackKingSideCastleWild:
9905           case BlackQueenSideCastleWild:
9906           /* PUSH Fabien */
9907           case WhiteHSideCastleFR:
9908           case WhiteASideCastleFR:
9909           case BlackHSideCastleFR:
9910           case BlackASideCastleFR:
9911           /* POP Fabien */
9912             fromX = currentMoveString[0] - AAA;
9913             fromY = currentMoveString[1] - ONE;
9914             toX = currentMoveString[2] - AAA;
9915             toY = currentMoveString[3] - ONE;
9916             promoChar = currentMoveString[4];
9917             break;
9918           case WhiteDrop:
9919           case BlackDrop:
9920             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9921             fromX = moveType == WhiteDrop ?
9922               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9923             (int) CharToPiece(ToLower(currentMoveString[0]));
9924             fromY = DROP_RANK;
9925             toX = currentMoveString[2] - AAA;
9926             toY = currentMoveString[3] - ONE;
9927             promoChar = NULLCHAR;
9928             break;
9929           case AmbiguousMove:
9930             /* bug? */
9931             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9932   if (appData.debugMode) {
9933     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9935     setbuf(debugFP, NULL);
9936   }
9937             DisplayError(buf, 0);
9938             return;
9939           case ImpossibleMove:
9940             /* bug? */
9941             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9942   if (appData.debugMode) {
9943     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9945     setbuf(debugFP, NULL);
9946   }
9947             DisplayError(buf, 0);
9948             return;
9949           case EndOfFile:
9950             if (boardIndex < backwardMostMove) {
9951                 /* Oops, gap.  How did that happen? */
9952                 DisplayError(_("Gap in move list"), 0);
9953                 return;
9954             }
9955             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9956             if (boardIndex > forwardMostMove) {
9957                 forwardMostMove = boardIndex;
9958             }
9959             return;
9960           case ElapsedTime:
9961             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9962                 strcat(parseList[boardIndex-1], " ");
9963                 strcat(parseList[boardIndex-1], yy_text);
9964             }
9965             continue;
9966           case Comment:
9967           case PGNTag:
9968           case NAG:
9969           default:
9970             /* ignore */
9971             continue;
9972           case WhiteWins:
9973           case BlackWins:
9974           case GameIsDrawn:
9975           case GameUnfinished:
9976             if (gameMode == IcsExamining) {
9977                 if (boardIndex < backwardMostMove) {
9978                     /* Oops, gap.  How did that happen? */
9979                     return;
9980                 }
9981                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9982                 return;
9983             }
9984             gameInfo.result = moveType;
9985             p = strchr(yy_text, '{');
9986             if (p == NULL) p = strchr(yy_text, '(');
9987             if (p == NULL) {
9988                 p = yy_text;
9989                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9990             } else {
9991                 q = strchr(p, *p == '{' ? '}' : ')');
9992                 if (q != NULL) *q = NULLCHAR;
9993                 p++;
9994             }
9995             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9996             gameInfo.resultDetails = StrSave(p);
9997             continue;
9998         }
9999         if (boardIndex >= forwardMostMove &&
10000             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10001             backwardMostMove = blackPlaysFirst ? 1 : 0;
10002             return;
10003         }
10004         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10005                                  fromY, fromX, toY, toX, promoChar,
10006                                  parseList[boardIndex]);
10007         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10008         /* currentMoveString is set as a side-effect of yylex */
10009         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10010         strcat(moveList[boardIndex], "\n");
10011         boardIndex++;
10012         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10013         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10014           case MT_NONE:
10015           case MT_STALEMATE:
10016           default:
10017             break;
10018           case MT_CHECK:
10019             if(!IS_SHOGI(gameInfo.variant))
10020                 strcat(parseList[boardIndex - 1], "+");
10021             break;
10022           case MT_CHECKMATE:
10023           case MT_STAINMATE:
10024             strcat(parseList[boardIndex - 1], "#");
10025             break;
10026         }
10027     }
10028 }
10029
10030
10031 /* Apply a move to the given board  */
10032 void
10033 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10034 {
10035   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10036   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10037
10038     /* [HGM] compute & store e.p. status and castling rights for new position */
10039     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10040
10041       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10042       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10043       board[EP_STATUS] = EP_NONE;
10044       board[EP_FILE] = board[EP_RANK] = 100;
10045
10046   if (fromY == DROP_RANK) {
10047         /* must be first */
10048         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10049             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10050             return;
10051         }
10052         piece = board[toY][toX] = (ChessSquare) fromX;
10053   } else {
10054 //      ChessSquare victim;
10055       int i;
10056
10057       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10058 //           victim = board[killY][killX],
10059            killed = board[killY][killX],
10060            board[killY][killX] = EmptySquare,
10061            board[EP_STATUS] = EP_CAPTURE;
10062            if( kill2X >= 0 && kill2Y >= 0)
10063              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10064       }
10065
10066       if( board[toY][toX] != EmptySquare ) {
10067            board[EP_STATUS] = EP_CAPTURE;
10068            if( (fromX != toX || fromY != toY) && // not igui!
10069                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10070                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10071                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10072            }
10073       }
10074
10075       pawn = board[fromY][fromX];
10076       if( pawn == WhiteLance || pawn == BlackLance ) {
10077            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10078                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10079                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10080            }
10081       }
10082       if( pawn == WhitePawn ) {
10083            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10084                board[EP_STATUS] = EP_PAWN_MOVE;
10085            if( toY-fromY>=2) {
10086                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10087                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10088                         gameInfo.variant != VariantBerolina || toX < fromX)
10089                       board[EP_STATUS] = toX | berolina;
10090                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10091                         gameInfo.variant != VariantBerolina || toX > fromX)
10092                       board[EP_STATUS] = toX;
10093            }
10094       } else
10095       if( pawn == BlackPawn ) {
10096            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10097                board[EP_STATUS] = EP_PAWN_MOVE;
10098            if( toY-fromY<= -2) {
10099                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10100                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10101                         gameInfo.variant != VariantBerolina || toX < fromX)
10102                       board[EP_STATUS] = toX | berolina;
10103                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10104                         gameInfo.variant != VariantBerolina || toX > fromX)
10105                       board[EP_STATUS] = toX;
10106            }
10107        }
10108
10109        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10110        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10111        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10112        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10113
10114        for(i=0; i<nrCastlingRights; i++) {
10115            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10116               board[CASTLING][i] == toX   && castlingRank[i] == toY
10117              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10118        }
10119
10120        if(gameInfo.variant == VariantSChess) { // update virginity
10121            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10122            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10123            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10124            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10125        }
10126
10127      if (fromX == toX && fromY == toY) return;
10128
10129      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10130      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10131      if(gameInfo.variant == VariantKnightmate)
10132          king += (int) WhiteUnicorn - (int) WhiteKing;
10133
10134     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10135        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10136         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10137         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10138         board[EP_STATUS] = EP_NONE; // capture was fake!
10139     } else
10140     /* Code added by Tord: */
10141     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10142     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10143         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10144       board[EP_STATUS] = EP_NONE; // capture was fake!
10145       board[fromY][fromX] = EmptySquare;
10146       board[toY][toX] = EmptySquare;
10147       if((toX > fromX) != (piece == WhiteRook)) {
10148         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10149       } else {
10150         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10151       }
10152     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10153                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10154       board[EP_STATUS] = EP_NONE;
10155       board[fromY][fromX] = EmptySquare;
10156       board[toY][toX] = EmptySquare;
10157       if((toX > fromX) != (piece == BlackRook)) {
10158         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10159       } else {
10160         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10161       }
10162     /* End of code added by Tord */
10163
10164     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10165         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10166         board[toY][toX] = piece;
10167     } else if (board[fromY][fromX] == king
10168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10169         && toY == fromY && toX > fromX+1) {
10170         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10171         board[fromY][toX-1] = board[fromY][rookX];
10172         board[fromY][rookX] = EmptySquare;
10173         board[fromY][fromX] = EmptySquare;
10174         board[toY][toX] = king;
10175     } else if (board[fromY][fromX] == king
10176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10177                && toY == fromY && toX < fromX-1) {
10178         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10179         board[fromY][toX+1] = board[fromY][rookX];
10180         board[fromY][rookX] = EmptySquare;
10181         board[fromY][fromX] = EmptySquare;
10182         board[toY][toX] = king;
10183     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10184                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10185                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10186                ) {
10187         /* white pawn promotion */
10188         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10189         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10190             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10191         board[fromY][fromX] = EmptySquare;
10192     } else if ((fromY >= BOARD_HEIGHT>>1)
10193                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10194                && (toX != fromX)
10195                && gameInfo.variant != VariantXiangqi
10196                && gameInfo.variant != VariantBerolina
10197                && (pawn == WhitePawn)
10198                && (board[toY][toX] == EmptySquare)) {
10199         board[fromY][fromX] = EmptySquare;
10200         board[toY][toX] = piece;
10201         if(toY == epRank - 128 + 1)
10202             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10203         else
10204             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10205     } else if ((fromY == BOARD_HEIGHT-4)
10206                && (toX == fromX)
10207                && gameInfo.variant == VariantBerolina
10208                && (board[fromY][fromX] == WhitePawn)
10209                && (board[toY][toX] == EmptySquare)) {
10210         board[fromY][fromX] = EmptySquare;
10211         board[toY][toX] = WhitePawn;
10212         if(oldEP & EP_BEROLIN_A) {
10213                 captured = board[fromY][fromX-1];
10214                 board[fromY][fromX-1] = EmptySquare;
10215         }else{  captured = board[fromY][fromX+1];
10216                 board[fromY][fromX+1] = EmptySquare;
10217         }
10218     } else if (board[fromY][fromX] == king
10219         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10220                && toY == fromY && toX > fromX+1) {
10221         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10222         board[fromY][toX-1] = board[fromY][rookX];
10223         board[fromY][rookX] = EmptySquare;
10224         board[fromY][fromX] = EmptySquare;
10225         board[toY][toX] = king;
10226     } else if (board[fromY][fromX] == king
10227         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10228                && toY == fromY && toX < fromX-1) {
10229         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10230         board[fromY][toX+1] = board[fromY][rookX];
10231         board[fromY][rookX] = EmptySquare;
10232         board[fromY][fromX] = EmptySquare;
10233         board[toY][toX] = king;
10234     } else if (fromY == 7 && fromX == 3
10235                && board[fromY][fromX] == BlackKing
10236                && toY == 7 && toX == 5) {
10237         board[fromY][fromX] = EmptySquare;
10238         board[toY][toX] = BlackKing;
10239         board[fromY][7] = EmptySquare;
10240         board[toY][4] = BlackRook;
10241     } else if (fromY == 7 && fromX == 3
10242                && board[fromY][fromX] == BlackKing
10243                && toY == 7 && toX == 1) {
10244         board[fromY][fromX] = EmptySquare;
10245         board[toY][toX] = BlackKing;
10246         board[fromY][0] = EmptySquare;
10247         board[toY][2] = BlackRook;
10248     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10249                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10250                && toY < promoRank && promoChar
10251                ) {
10252         /* black pawn promotion */
10253         board[toY][toX] = CharToPiece(ToLower(promoChar));
10254         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10255             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10256         board[fromY][fromX] = EmptySquare;
10257     } else if ((fromY < BOARD_HEIGHT>>1)
10258                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10259                && (toX != fromX)
10260                && gameInfo.variant != VariantXiangqi
10261                && gameInfo.variant != VariantBerolina
10262                && (pawn == BlackPawn)
10263                && (board[toY][toX] == EmptySquare)) {
10264         board[fromY][fromX] = EmptySquare;
10265         board[toY][toX] = piece;
10266         if(toY == epRank - 128 - 1)
10267             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10268         else
10269             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10270     } else if ((fromY == 3)
10271                && (toX == fromX)
10272                && gameInfo.variant == VariantBerolina
10273                && (board[fromY][fromX] == BlackPawn)
10274                && (board[toY][toX] == EmptySquare)) {
10275         board[fromY][fromX] = EmptySquare;
10276         board[toY][toX] = BlackPawn;
10277         if(oldEP & EP_BEROLIN_A) {
10278                 captured = board[fromY][fromX-1];
10279                 board[fromY][fromX-1] = EmptySquare;
10280         }else{  captured = board[fromY][fromX+1];
10281                 board[fromY][fromX+1] = EmptySquare;
10282         }
10283     } else {
10284         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10285         board[fromY][fromX] = EmptySquare;
10286         board[toY][toX] = piece;
10287     }
10288   }
10289
10290     if (gameInfo.holdingsWidth != 0) {
10291
10292       /* !!A lot more code needs to be written to support holdings  */
10293       /* [HGM] OK, so I have written it. Holdings are stored in the */
10294       /* penultimate board files, so they are automaticlly stored   */
10295       /* in the game history.                                       */
10296       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10297                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10298         /* Delete from holdings, by decreasing count */
10299         /* and erasing image if necessary            */
10300         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10301         if(p < (int) BlackPawn) { /* white drop */
10302              p -= (int)WhitePawn;
10303                  p = PieceToNumber((ChessSquare)p);
10304              if(p >= gameInfo.holdingsSize) p = 0;
10305              if(--board[p][BOARD_WIDTH-2] <= 0)
10306                   board[p][BOARD_WIDTH-1] = EmptySquare;
10307              if((int)board[p][BOARD_WIDTH-2] < 0)
10308                         board[p][BOARD_WIDTH-2] = 0;
10309         } else {                  /* black drop */
10310              p -= (int)BlackPawn;
10311                  p = PieceToNumber((ChessSquare)p);
10312              if(p >= gameInfo.holdingsSize) p = 0;
10313              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10314                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10315              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10316                         board[BOARD_HEIGHT-1-p][1] = 0;
10317         }
10318       }
10319       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10320           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10321         /* [HGM] holdings: Add to holdings, if holdings exist */
10322         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10323                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10324                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10325         }
10326         p = (int) captured;
10327         if (p >= (int) BlackPawn) {
10328           p -= (int)BlackPawn;
10329           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10330                   /* Restore shogi-promoted piece to its original  first */
10331                   captured = (ChessSquare) (DEMOTED captured);
10332                   p = DEMOTED p;
10333           }
10334           p = PieceToNumber((ChessSquare)p);
10335           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10336           board[p][BOARD_WIDTH-2]++;
10337           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10338         } else {
10339           p -= (int)WhitePawn;
10340           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10341                   captured = (ChessSquare) (DEMOTED captured);
10342                   p = DEMOTED p;
10343           }
10344           p = PieceToNumber((ChessSquare)p);
10345           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10346           board[BOARD_HEIGHT-1-p][1]++;
10347           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10348         }
10349       }
10350     } else if (gameInfo.variant == VariantAtomic) {
10351       if (captured != EmptySquare) {
10352         int y, x;
10353         for (y = toY-1; y <= toY+1; y++) {
10354           for (x = toX-1; x <= toX+1; x++) {
10355             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10356                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10357               board[y][x] = EmptySquare;
10358             }
10359           }
10360         }
10361         board[toY][toX] = EmptySquare;
10362       }
10363     }
10364
10365     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10366         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10367     } else
10368     if(promoChar == '+') {
10369         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10370         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10371         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10372           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10373     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10374         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10375         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10376            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10377         board[toY][toX] = newPiece;
10378     }
10379     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10380                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10381         // [HGM] superchess: take promotion piece out of holdings
10382         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10383         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10384             if(!--board[k][BOARD_WIDTH-2])
10385                 board[k][BOARD_WIDTH-1] = EmptySquare;
10386         } else {
10387             if(!--board[BOARD_HEIGHT-1-k][1])
10388                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10389         }
10390     }
10391 }
10392
10393 /* Updates forwardMostMove */
10394 void
10395 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10396 {
10397     int x = toX, y = toY;
10398     char *s = parseList[forwardMostMove];
10399     ChessSquare p = boards[forwardMostMove][toY][toX];
10400 //    forwardMostMove++; // [HGM] bare: moved downstream
10401
10402     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10403     (void) CoordsToAlgebraic(boards[forwardMostMove],
10404                              PosFlags(forwardMostMove),
10405                              fromY, fromX, y, x, promoChar,
10406                              s);
10407     if(killX >= 0 && killY >= 0)
10408         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10409
10410     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10411         int timeLeft; static int lastLoadFlag=0; int king, piece;
10412         piece = boards[forwardMostMove][fromY][fromX];
10413         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10414         if(gameInfo.variant == VariantKnightmate)
10415             king += (int) WhiteUnicorn - (int) WhiteKing;
10416         if(forwardMostMove == 0) {
10417             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10418                 fprintf(serverMoves, "%s;", UserName());
10419             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10420                 fprintf(serverMoves, "%s;", second.tidy);
10421             fprintf(serverMoves, "%s;", first.tidy);
10422             if(gameMode == MachinePlaysWhite)
10423                 fprintf(serverMoves, "%s;", UserName());
10424             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10425                 fprintf(serverMoves, "%s;", second.tidy);
10426         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10427         lastLoadFlag = loadFlag;
10428         // print base move
10429         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10430         // print castling suffix
10431         if( toY == fromY && piece == king ) {
10432             if(toX-fromX > 1)
10433                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10434             if(fromX-toX >1)
10435                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10436         }
10437         // e.p. suffix
10438         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10439              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10440              boards[forwardMostMove][toY][toX] == EmptySquare
10441              && fromX != toX && fromY != toY)
10442                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10443         // promotion suffix
10444         if(promoChar != NULLCHAR) {
10445             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10446                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10447                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10448             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10449         }
10450         if(!loadFlag) {
10451                 char buf[MOVE_LEN*2], *p; int len;
10452             fprintf(serverMoves, "/%d/%d",
10453                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10454             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10455             else                      timeLeft = blackTimeRemaining/1000;
10456             fprintf(serverMoves, "/%d", timeLeft);
10457                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10458                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10459                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10460                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10461             fprintf(serverMoves, "/%s", buf);
10462         }
10463         fflush(serverMoves);
10464     }
10465
10466     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10467         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10468       return;
10469     }
10470     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10471     if (commentList[forwardMostMove+1] != NULL) {
10472         free(commentList[forwardMostMove+1]);
10473         commentList[forwardMostMove+1] = NULL;
10474     }
10475     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10476     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10477     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10478     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10479     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10480     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10481     adjustedClock = FALSE;
10482     gameInfo.result = GameUnfinished;
10483     if (gameInfo.resultDetails != NULL) {
10484         free(gameInfo.resultDetails);
10485         gameInfo.resultDetails = NULL;
10486     }
10487     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10488                               moveList[forwardMostMove - 1]);
10489     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10490       case MT_NONE:
10491       case MT_STALEMATE:
10492       default:
10493         break;
10494       case MT_CHECK:
10495         if(!IS_SHOGI(gameInfo.variant))
10496             strcat(parseList[forwardMostMove - 1], "+");
10497         break;
10498       case MT_CHECKMATE:
10499       case MT_STAINMATE:
10500         strcat(parseList[forwardMostMove - 1], "#");
10501         break;
10502     }
10503 }
10504
10505 /* Updates currentMove if not pausing */
10506 void
10507 ShowMove (int fromX, int fromY, int toX, int toY)
10508 {
10509     int instant = (gameMode == PlayFromGameFile) ?
10510         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10511     if(appData.noGUI) return;
10512     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10513         if (!instant) {
10514             if (forwardMostMove == currentMove + 1) {
10515                 AnimateMove(boards[forwardMostMove - 1],
10516                             fromX, fromY, toX, toY);
10517             }
10518         }
10519         currentMove = forwardMostMove;
10520     }
10521
10522     killX = killY = -1; // [HGM] lion: used up
10523
10524     if (instant) return;
10525
10526     DisplayMove(currentMove - 1);
10527     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10528             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10529                 SetHighlights(fromX, fromY, toX, toY);
10530             }
10531     }
10532     DrawPosition(FALSE, boards[currentMove]);
10533     DisplayBothClocks();
10534     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10535 }
10536
10537 void
10538 SendEgtPath (ChessProgramState *cps)
10539 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10540         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10541
10542         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10543
10544         while(*p) {
10545             char c, *q = name+1, *r, *s;
10546
10547             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10548             while(*p && *p != ',') *q++ = *p++;
10549             *q++ = ':'; *q = 0;
10550             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10551                 strcmp(name, ",nalimov:") == 0 ) {
10552                 // take nalimov path from the menu-changeable option first, if it is defined
10553               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10554                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10555             } else
10556             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10557                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10558                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10559                 s = r = StrStr(s, ":") + 1; // beginning of path info
10560                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10561                 c = *r; *r = 0;             // temporarily null-terminate path info
10562                     *--q = 0;               // strip of trailig ':' from name
10563                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10564                 *r = c;
10565                 SendToProgram(buf,cps);     // send egtbpath command for this format
10566             }
10567             if(*p == ',') p++; // read away comma to position for next format name
10568         }
10569 }
10570
10571 static int
10572 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10573 {
10574       int width = 8, height = 8, holdings = 0;             // most common sizes
10575       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10576       // correct the deviations default for each variant
10577       if( v == VariantXiangqi ) width = 9,  height = 10;
10578       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10579       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10580       if( v == VariantCapablanca || v == VariantCapaRandom ||
10581           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10582                                 width = 10;
10583       if( v == VariantCourier ) width = 12;
10584       if( v == VariantSuper )                            holdings = 8;
10585       if( v == VariantGreat )   width = 10,              holdings = 8;
10586       if( v == VariantSChess )                           holdings = 7;
10587       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10588       if( v == VariantChuChess) width = 10, height = 10;
10589       if( v == VariantChu )     width = 12, height = 12;
10590       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10591              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10592              holdingsSize >= 0 && holdingsSize != holdings;
10593 }
10594
10595 char variantError[MSG_SIZ];
10596
10597 char *
10598 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10599 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10600       char *p, *variant = VariantName(v);
10601       static char b[MSG_SIZ];
10602       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10603            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10604                                                holdingsSize, variant); // cook up sized variant name
10605            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10606            if(StrStr(list, b) == NULL) {
10607                // specific sized variant not known, check if general sizing allowed
10608                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10609                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10610                             boardWidth, boardHeight, holdingsSize, engine);
10611                    return NULL;
10612                }
10613                /* [HGM] here we really should compare with the maximum supported board size */
10614            }
10615       } else snprintf(b, MSG_SIZ,"%s", variant);
10616       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10617       p = StrStr(list, b);
10618       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10619       if(p == NULL) {
10620           // occurs not at all in list, or only as sub-string
10621           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10622           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10623               int l = strlen(variantError);
10624               char *q;
10625               while(p != list && p[-1] != ',') p--;
10626               q = strchr(p, ',');
10627               if(q) *q = NULLCHAR;
10628               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10629               if(q) *q= ',';
10630           }
10631           return NULL;
10632       }
10633       return b;
10634 }
10635
10636 void
10637 InitChessProgram (ChessProgramState *cps, int setup)
10638 /* setup needed to setup FRC opening position */
10639 {
10640     char buf[MSG_SIZ], *b;
10641     if (appData.noChessProgram) return;
10642     hintRequested = FALSE;
10643     bookRequested = FALSE;
10644
10645     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10646     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10647     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10648     if(cps->memSize) { /* [HGM] memory */
10649       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10650         SendToProgram(buf, cps);
10651     }
10652     SendEgtPath(cps); /* [HGM] EGT */
10653     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10654       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10655         SendToProgram(buf, cps);
10656     }
10657
10658     setboardSpoiledMachineBlack = FALSE;
10659     SendToProgram(cps->initString, cps);
10660     if (gameInfo.variant != VariantNormal &&
10661         gameInfo.variant != VariantLoadable
10662         /* [HGM] also send variant if board size non-standard */
10663         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10664
10665       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10666                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10667       if (b == NULL) {
10668         VariantClass v;
10669         char c, *q = cps->variants, *p = strchr(q, ',');
10670         if(p) *p = NULLCHAR;
10671         v = StringToVariant(q);
10672         DisplayError(variantError, 0);
10673         if(v != VariantUnknown && cps == &first) {
10674             int w, h, s;
10675             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10676                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10677             ASSIGN(appData.variant, q);
10678             Reset(TRUE, FALSE);
10679         }
10680         if(p) *p = ',';
10681         return;
10682       }
10683
10684       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10685       SendToProgram(buf, cps);
10686     }
10687     currentlyInitializedVariant = gameInfo.variant;
10688
10689     /* [HGM] send opening position in FRC to first engine */
10690     if(setup) {
10691           SendToProgram("force\n", cps);
10692           SendBoard(cps, 0);
10693           /* engine is now in force mode! Set flag to wake it up after first move. */
10694           setboardSpoiledMachineBlack = 1;
10695     }
10696
10697     if (cps->sendICS) {
10698       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10699       SendToProgram(buf, cps);
10700     }
10701     cps->maybeThinking = FALSE;
10702     cps->offeredDraw = 0;
10703     if (!appData.icsActive) {
10704         SendTimeControl(cps, movesPerSession, timeControl,
10705                         timeIncrement, appData.searchDepth,
10706                         searchTime);
10707     }
10708     if (appData.showThinking
10709         // [HGM] thinking: four options require thinking output to be sent
10710         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10711                                 ) {
10712         SendToProgram("post\n", cps);
10713     }
10714     SendToProgram("hard\n", cps);
10715     if (!appData.ponderNextMove) {
10716         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10717            it without being sure what state we are in first.  "hard"
10718            is not a toggle, so that one is OK.
10719          */
10720         SendToProgram("easy\n", cps);
10721     }
10722     if (cps->usePing) {
10723       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10724       SendToProgram(buf, cps);
10725     }
10726     cps->initDone = TRUE;
10727     ClearEngineOutputPane(cps == &second);
10728 }
10729
10730
10731 void
10732 ResendOptions (ChessProgramState *cps)
10733 { // send the stored value of the options
10734   int i;
10735   char buf[MSG_SIZ];
10736   Option *opt = cps->option;
10737   for(i=0; i<cps->nrOptions; i++, opt++) {
10738       switch(opt->type) {
10739         case Spin:
10740         case Slider:
10741         case CheckBox:
10742             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10743           break;
10744         case ComboBox:
10745           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10746           break;
10747         default:
10748             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10749           break;
10750         case Button:
10751         case SaveButton:
10752           continue;
10753       }
10754       SendToProgram(buf, cps);
10755   }
10756 }
10757
10758 void
10759 StartChessProgram (ChessProgramState *cps)
10760 {
10761     char buf[MSG_SIZ];
10762     int err;
10763
10764     if (appData.noChessProgram) return;
10765     cps->initDone = FALSE;
10766
10767     if (strcmp(cps->host, "localhost") == 0) {
10768         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10769     } else if (*appData.remoteShell == NULLCHAR) {
10770         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10771     } else {
10772         if (*appData.remoteUser == NULLCHAR) {
10773           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10774                     cps->program);
10775         } else {
10776           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10777                     cps->host, appData.remoteUser, cps->program);
10778         }
10779         err = StartChildProcess(buf, "", &cps->pr);
10780     }
10781
10782     if (err != 0) {
10783       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10784         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10785         if(cps != &first) return;
10786         appData.noChessProgram = TRUE;
10787         ThawUI();
10788         SetNCPMode();
10789 //      DisplayFatalError(buf, err, 1);
10790 //      cps->pr = NoProc;
10791 //      cps->isr = NULL;
10792         return;
10793     }
10794
10795     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10796     if (cps->protocolVersion > 1) {
10797       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10798       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10799         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10800         cps->comboCnt = 0;  //                and values of combo boxes
10801       }
10802       SendToProgram(buf, cps);
10803       if(cps->reload) ResendOptions(cps);
10804     } else {
10805       SendToProgram("xboard\n", cps);
10806     }
10807 }
10808
10809 void
10810 TwoMachinesEventIfReady P((void))
10811 {
10812   static int curMess = 0;
10813   if (first.lastPing != first.lastPong) {
10814     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10815     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10816     return;
10817   }
10818   if (second.lastPing != second.lastPong) {
10819     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10820     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10821     return;
10822   }
10823   DisplayMessage("", ""); curMess = 0;
10824   TwoMachinesEvent();
10825 }
10826
10827 char *
10828 MakeName (char *template)
10829 {
10830     time_t clock;
10831     struct tm *tm;
10832     static char buf[MSG_SIZ];
10833     char *p = buf;
10834     int i;
10835
10836     clock = time((time_t *)NULL);
10837     tm = localtime(&clock);
10838
10839     while(*p++ = *template++) if(p[-1] == '%') {
10840         switch(*template++) {
10841           case 0:   *p = 0; return buf;
10842           case 'Y': i = tm->tm_year+1900; break;
10843           case 'y': i = tm->tm_year-100; break;
10844           case 'M': i = tm->tm_mon+1; break;
10845           case 'd': i = tm->tm_mday; break;
10846           case 'h': i = tm->tm_hour; break;
10847           case 'm': i = tm->tm_min; break;
10848           case 's': i = tm->tm_sec; break;
10849           default:  i = 0;
10850         }
10851         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10852     }
10853     return buf;
10854 }
10855
10856 int
10857 CountPlayers (char *p)
10858 {
10859     int n = 0;
10860     while(p = strchr(p, '\n')) p++, n++; // count participants
10861     return n;
10862 }
10863
10864 FILE *
10865 WriteTourneyFile (char *results, FILE *f)
10866 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10867     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10868     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10869         // create a file with tournament description
10870         fprintf(f, "-participants {%s}\n", appData.participants);
10871         fprintf(f, "-seedBase %d\n", appData.seedBase);
10872         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10873         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10874         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10875         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10876         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10877         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10878         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10879         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10880         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10881         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10882         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10883         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10884         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10885         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10886         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10887         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10888         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10889         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10890         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10891         fprintf(f, "-smpCores %d\n", appData.smpCores);
10892         if(searchTime > 0)
10893                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10894         else {
10895                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10896                 fprintf(f, "-tc %s\n", appData.timeControl);
10897                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10898         }
10899         fprintf(f, "-results \"%s\"\n", results);
10900     }
10901     return f;
10902 }
10903
10904 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10905
10906 void
10907 Substitute (char *participants, int expunge)
10908 {
10909     int i, changed, changes=0, nPlayers=0;
10910     char *p, *q, *r, buf[MSG_SIZ];
10911     if(participants == NULL) return;
10912     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10913     r = p = participants; q = appData.participants;
10914     while(*p && *p == *q) {
10915         if(*p == '\n') r = p+1, nPlayers++;
10916         p++; q++;
10917     }
10918     if(*p) { // difference
10919         while(*p && *p++ != '\n');
10920         while(*q && *q++ != '\n');
10921       changed = nPlayers;
10922         changes = 1 + (strcmp(p, q) != 0);
10923     }
10924     if(changes == 1) { // a single engine mnemonic was changed
10925         q = r; while(*q) nPlayers += (*q++ == '\n');
10926         p = buf; while(*r && (*p = *r++) != '\n') p++;
10927         *p = NULLCHAR;
10928         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10929         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10930         if(mnemonic[i]) { // The substitute is valid
10931             FILE *f;
10932             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10933                 flock(fileno(f), LOCK_EX);
10934                 ParseArgsFromFile(f);
10935                 fseek(f, 0, SEEK_SET);
10936                 FREE(appData.participants); appData.participants = participants;
10937                 if(expunge) { // erase results of replaced engine
10938                     int len = strlen(appData.results), w, b, dummy;
10939                     for(i=0; i<len; i++) {
10940                         Pairing(i, nPlayers, &w, &b, &dummy);
10941                         if((w == changed || b == changed) && appData.results[i] == '*') {
10942                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10943                             fclose(f);
10944                             return;
10945                         }
10946                     }
10947                     for(i=0; i<len; i++) {
10948                         Pairing(i, nPlayers, &w, &b, &dummy);
10949                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10950                     }
10951                 }
10952                 WriteTourneyFile(appData.results, f);
10953                 fclose(f); // release lock
10954                 return;
10955             }
10956         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10957     }
10958     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10959     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10960     free(participants);
10961     return;
10962 }
10963
10964 int
10965 CheckPlayers (char *participants)
10966 {
10967         int i;
10968         char buf[MSG_SIZ], *p;
10969         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10970         while(p = strchr(participants, '\n')) {
10971             *p = NULLCHAR;
10972             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10973             if(!mnemonic[i]) {
10974                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10975                 *p = '\n';
10976                 DisplayError(buf, 0);
10977                 return 1;
10978             }
10979             *p = '\n';
10980             participants = p + 1;
10981         }
10982         return 0;
10983 }
10984
10985 int
10986 CreateTourney (char *name)
10987 {
10988         FILE *f;
10989         if(matchMode && strcmp(name, appData.tourneyFile)) {
10990              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10991         }
10992         if(name[0] == NULLCHAR) {
10993             if(appData.participants[0])
10994                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10995             return 0;
10996         }
10997         f = fopen(name, "r");
10998         if(f) { // file exists
10999             ASSIGN(appData.tourneyFile, name);
11000             ParseArgsFromFile(f); // parse it
11001         } else {
11002             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11003             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11004                 DisplayError(_("Not enough participants"), 0);
11005                 return 0;
11006             }
11007             if(CheckPlayers(appData.participants)) return 0;
11008             ASSIGN(appData.tourneyFile, name);
11009             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11010             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11011         }
11012         fclose(f);
11013         appData.noChessProgram = FALSE;
11014         appData.clockMode = TRUE;
11015         SetGNUMode();
11016         return 1;
11017 }
11018
11019 int
11020 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11021 {
11022     char buf[MSG_SIZ], *p, *q;
11023     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11024     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11025     skip = !all && group[0]; // if group requested, we start in skip mode
11026     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11027         p = names; q = buf; header = 0;
11028         while(*p && *p != '\n') *q++ = *p++;
11029         *q = 0;
11030         if(*p == '\n') p++;
11031         if(buf[0] == '#') {
11032             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11033             depth++; // we must be entering a new group
11034             if(all) continue; // suppress printing group headers when complete list requested
11035             header = 1;
11036             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11037         }
11038         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11039         if(engineList[i]) free(engineList[i]);
11040         engineList[i] = strdup(buf);
11041         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11042         if(engineMnemonic[i]) free(engineMnemonic[i]);
11043         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11044             strcat(buf, " (");
11045             sscanf(q + 8, "%s", buf + strlen(buf));
11046             strcat(buf, ")");
11047         }
11048         engineMnemonic[i] = strdup(buf);
11049         i++;
11050     }
11051     engineList[i] = engineMnemonic[i] = NULL;
11052     return i;
11053 }
11054
11055 // following implemented as macro to avoid type limitations
11056 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11057
11058 void
11059 SwapEngines (int n)
11060 {   // swap settings for first engine and other engine (so far only some selected options)
11061     int h;
11062     char *p;
11063     if(n == 0) return;
11064     SWAP(directory, p)
11065     SWAP(chessProgram, p)
11066     SWAP(isUCI, h)
11067     SWAP(hasOwnBookUCI, h)
11068     SWAP(protocolVersion, h)
11069     SWAP(reuse, h)
11070     SWAP(scoreIsAbsolute, h)
11071     SWAP(timeOdds, h)
11072     SWAP(logo, p)
11073     SWAP(pgnName, p)
11074     SWAP(pvSAN, h)
11075     SWAP(engOptions, p)
11076     SWAP(engInitString, p)
11077     SWAP(computerString, p)
11078     SWAP(features, p)
11079     SWAP(fenOverride, p)
11080     SWAP(NPS, h)
11081     SWAP(accumulateTC, h)
11082     SWAP(drawDepth, h)
11083     SWAP(host, p)
11084     SWAP(pseudo, h)
11085 }
11086
11087 int
11088 GetEngineLine (char *s, int n)
11089 {
11090     int i;
11091     char buf[MSG_SIZ];
11092     extern char *icsNames;
11093     if(!s || !*s) return 0;
11094     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11095     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11096     if(!mnemonic[i]) return 0;
11097     if(n == 11) return 1; // just testing if there was a match
11098     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11099     if(n == 1) SwapEngines(n);
11100     ParseArgsFromString(buf);
11101     if(n == 1) SwapEngines(n);
11102     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11103         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11104         ParseArgsFromString(buf);
11105     }
11106     return 1;
11107 }
11108
11109 int
11110 SetPlayer (int player, char *p)
11111 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11112     int i;
11113     char buf[MSG_SIZ], *engineName;
11114     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11115     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11116     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11117     if(mnemonic[i]) {
11118         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11119         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11120         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11121         ParseArgsFromString(buf);
11122     } else { // no engine with this nickname is installed!
11123         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11124         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11125         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11126         ModeHighlight();
11127         DisplayError(buf, 0);
11128         return 0;
11129     }
11130     free(engineName);
11131     return i;
11132 }
11133
11134 char *recentEngines;
11135
11136 void
11137 RecentEngineEvent (int nr)
11138 {
11139     int n;
11140 //    SwapEngines(1); // bump first to second
11141 //    ReplaceEngine(&second, 1); // and load it there
11142     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11143     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11144     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11145         ReplaceEngine(&first, 0);
11146         FloatToFront(&appData.recentEngineList, command[n]);
11147     }
11148 }
11149
11150 int
11151 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11152 {   // determine players from game number
11153     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11154
11155     if(appData.tourneyType == 0) {
11156         roundsPerCycle = (nPlayers - 1) | 1;
11157         pairingsPerRound = nPlayers / 2;
11158     } else if(appData.tourneyType > 0) {
11159         roundsPerCycle = nPlayers - appData.tourneyType;
11160         pairingsPerRound = appData.tourneyType;
11161     }
11162     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11163     gamesPerCycle = gamesPerRound * roundsPerCycle;
11164     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11165     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11166     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11167     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11168     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11169     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11170
11171     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11172     if(appData.roundSync) *syncInterval = gamesPerRound;
11173
11174     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11175
11176     if(appData.tourneyType == 0) {
11177         if(curPairing == (nPlayers-1)/2 ) {
11178             *whitePlayer = curRound;
11179             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11180         } else {
11181             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11182             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11183             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11184             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11185         }
11186     } else if(appData.tourneyType > 1) {
11187         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11188         *whitePlayer = curRound + appData.tourneyType;
11189     } else if(appData.tourneyType > 0) {
11190         *whitePlayer = curPairing;
11191         *blackPlayer = curRound + appData.tourneyType;
11192     }
11193
11194     // take care of white/black alternation per round.
11195     // For cycles and games this is already taken care of by default, derived from matchGame!
11196     return curRound & 1;
11197 }
11198
11199 int
11200 NextTourneyGame (int nr, int *swapColors)
11201 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11202     char *p, *q;
11203     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11204     FILE *tf;
11205     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11206     tf = fopen(appData.tourneyFile, "r");
11207     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11208     ParseArgsFromFile(tf); fclose(tf);
11209     InitTimeControls(); // TC might be altered from tourney file
11210
11211     nPlayers = CountPlayers(appData.participants); // count participants
11212     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11213     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11214
11215     if(syncInterval) {
11216         p = q = appData.results;
11217         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11218         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11219             DisplayMessage(_("Waiting for other game(s)"),"");
11220             waitingForGame = TRUE;
11221             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11222             return 0;
11223         }
11224         waitingForGame = FALSE;
11225     }
11226
11227     if(appData.tourneyType < 0) {
11228         if(nr>=0 && !pairingReceived) {
11229             char buf[1<<16];
11230             if(pairing.pr == NoProc) {
11231                 if(!appData.pairingEngine[0]) {
11232                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11233                     return 0;
11234                 }
11235                 StartChessProgram(&pairing); // starts the pairing engine
11236             }
11237             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11238             SendToProgram(buf, &pairing);
11239             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11240             SendToProgram(buf, &pairing);
11241             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11242         }
11243         pairingReceived = 0;                              // ... so we continue here
11244         *swapColors = 0;
11245         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11246         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11247         matchGame = 1; roundNr = nr / syncInterval + 1;
11248     }
11249
11250     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11251
11252     // redefine engines, engine dir, etc.
11253     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11254     if(first.pr == NoProc) {
11255       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11256       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11257     }
11258     if(second.pr == NoProc) {
11259       SwapEngines(1);
11260       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11261       SwapEngines(1);         // and make that valid for second engine by swapping
11262       InitEngine(&second, 1);
11263     }
11264     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11265     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11266     return OK;
11267 }
11268
11269 void
11270 NextMatchGame ()
11271 {   // performs game initialization that does not invoke engines, and then tries to start the game
11272     int res, firstWhite, swapColors = 0;
11273     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11274     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
11275         char buf[MSG_SIZ];
11276         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11277         if(strcmp(buf, currentDebugFile)) { // name has changed
11278             FILE *f = fopen(buf, "w");
11279             if(f) { // if opening the new file failed, just keep using the old one
11280                 ASSIGN(currentDebugFile, buf);
11281                 fclose(debugFP);
11282                 debugFP = f;
11283             }
11284             if(appData.serverFileName) {
11285                 if(serverFP) fclose(serverFP);
11286                 serverFP = fopen(appData.serverFileName, "w");
11287                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11288                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11289             }
11290         }
11291     }
11292     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11293     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11294     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11295     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11296     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11297     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11298     Reset(FALSE, first.pr != NoProc);
11299     res = LoadGameOrPosition(matchGame); // setup game
11300     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11301     if(!res) return; // abort when bad game/pos file
11302     TwoMachinesEvent();
11303 }
11304
11305 void
11306 UserAdjudicationEvent (int result)
11307 {
11308     ChessMove gameResult = GameIsDrawn;
11309
11310     if( result > 0 ) {
11311         gameResult = WhiteWins;
11312     }
11313     else if( result < 0 ) {
11314         gameResult = BlackWins;
11315     }
11316
11317     if( gameMode == TwoMachinesPlay ) {
11318         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11319     }
11320 }
11321
11322
11323 // [HGM] save: calculate checksum of game to make games easily identifiable
11324 int
11325 StringCheckSum (char *s)
11326 {
11327         int i = 0;
11328         if(s==NULL) return 0;
11329         while(*s) i = i*259 + *s++;
11330         return i;
11331 }
11332
11333 int
11334 GameCheckSum ()
11335 {
11336         int i, sum=0;
11337         for(i=backwardMostMove; i<forwardMostMove; i++) {
11338                 sum += pvInfoList[i].depth;
11339                 sum += StringCheckSum(parseList[i]);
11340                 sum += StringCheckSum(commentList[i]);
11341                 sum *= 261;
11342         }
11343         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11344         return sum + StringCheckSum(commentList[i]);
11345 } // end of save patch
11346
11347 void
11348 GameEnds (ChessMove result, char *resultDetails, int whosays)
11349 {
11350     GameMode nextGameMode;
11351     int isIcsGame;
11352     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11353
11354     if(endingGame) return; /* [HGM] crash: forbid recursion */
11355     endingGame = 1;
11356     if(twoBoards) { // [HGM] dual: switch back to one board
11357         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11358         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11359     }
11360     if (appData.debugMode) {
11361       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11362               result, resultDetails ? resultDetails : "(null)", whosays);
11363     }
11364
11365     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11366
11367     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11368
11369     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11370         /* If we are playing on ICS, the server decides when the
11371            game is over, but the engine can offer to draw, claim
11372            a draw, or resign.
11373          */
11374 #if ZIPPY
11375         if (appData.zippyPlay && first.initDone) {
11376             if (result == GameIsDrawn) {
11377                 /* In case draw still needs to be claimed */
11378                 SendToICS(ics_prefix);
11379                 SendToICS("draw\n");
11380             } else if (StrCaseStr(resultDetails, "resign")) {
11381                 SendToICS(ics_prefix);
11382                 SendToICS("resign\n");
11383             }
11384         }
11385 #endif
11386         endingGame = 0; /* [HGM] crash */
11387         return;
11388     }
11389
11390     /* If we're loading the game from a file, stop */
11391     if (whosays == GE_FILE) {
11392       (void) StopLoadGameTimer();
11393       gameFileFP = NULL;
11394     }
11395
11396     /* Cancel draw offers */
11397     first.offeredDraw = second.offeredDraw = 0;
11398
11399     /* If this is an ICS game, only ICS can really say it's done;
11400        if not, anyone can. */
11401     isIcsGame = (gameMode == IcsPlayingWhite ||
11402                  gameMode == IcsPlayingBlack ||
11403                  gameMode == IcsObserving    ||
11404                  gameMode == IcsExamining);
11405
11406     if (!isIcsGame || whosays == GE_ICS) {
11407         /* OK -- not an ICS game, or ICS said it was done */
11408         StopClocks();
11409         if (!isIcsGame && !appData.noChessProgram)
11410           SetUserThinkingEnables();
11411
11412         /* [HGM] if a machine claims the game end we verify this claim */
11413         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11414             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11415                 char claimer;
11416                 ChessMove trueResult = (ChessMove) -1;
11417
11418                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11419                                             first.twoMachinesColor[0] :
11420                                             second.twoMachinesColor[0] ;
11421
11422                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11423                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11424                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11425                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11426                 } else
11427                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11428                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11429                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11430                 } else
11431                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11432                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11433                 }
11434
11435                 // now verify win claims, but not in drop games, as we don't understand those yet
11436                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11437                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11438                     (result == WhiteWins && claimer == 'w' ||
11439                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11440                       if (appData.debugMode) {
11441                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11442                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11443                       }
11444                       if(result != trueResult) {
11445                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11446                               result = claimer == 'w' ? BlackWins : WhiteWins;
11447                               resultDetails = buf;
11448                       }
11449                 } else
11450                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11451                     && (forwardMostMove <= backwardMostMove ||
11452                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11453                         (claimer=='b')==(forwardMostMove&1))
11454                                                                                   ) {
11455                       /* [HGM] verify: draws that were not flagged are false claims */
11456                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11457                       result = claimer == 'w' ? BlackWins : WhiteWins;
11458                       resultDetails = buf;
11459                 }
11460                 /* (Claiming a loss is accepted no questions asked!) */
11461             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11462                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11463                 result = GameUnfinished;
11464                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11465             }
11466             /* [HGM] bare: don't allow bare King to win */
11467             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11468                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11469                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11470                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11471                && result != GameIsDrawn)
11472             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11473                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11474                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11475                         if(p >= 0 && p <= (int)WhiteKing) k++;
11476                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11477                 }
11478                 if (appData.debugMode) {
11479                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11480                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11481                 }
11482                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11483                         result = GameIsDrawn;
11484                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11485                         resultDetails = buf;
11486                 }
11487             }
11488         }
11489
11490
11491         if(serverMoves != NULL && !loadFlag) { char c = '=';
11492             if(result==WhiteWins) c = '+';
11493             if(result==BlackWins) c = '-';
11494             if(resultDetails != NULL)
11495                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11496         }
11497         if (resultDetails != NULL) {
11498             gameInfo.result = result;
11499             gameInfo.resultDetails = StrSave(resultDetails);
11500
11501             /* display last move only if game was not loaded from file */
11502             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11503                 DisplayMove(currentMove - 1);
11504
11505             if (forwardMostMove != 0) {
11506                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11507                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11508                                                                 ) {
11509                     if (*appData.saveGameFile != NULLCHAR) {
11510                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11511                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11512                         else
11513                         SaveGameToFile(appData.saveGameFile, TRUE);
11514                     } else if (appData.autoSaveGames) {
11515                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11516                     }
11517                     if (*appData.savePositionFile != NULLCHAR) {
11518                         SavePositionToFile(appData.savePositionFile);
11519                     }
11520                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11521                 }
11522             }
11523
11524             /* Tell program how game ended in case it is learning */
11525             /* [HGM] Moved this to after saving the PGN, just in case */
11526             /* engine died and we got here through time loss. In that */
11527             /* case we will get a fatal error writing the pipe, which */
11528             /* would otherwise lose us the PGN.                       */
11529             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11530             /* output during GameEnds should never be fatal anymore   */
11531             if (gameMode == MachinePlaysWhite ||
11532                 gameMode == MachinePlaysBlack ||
11533                 gameMode == TwoMachinesPlay ||
11534                 gameMode == IcsPlayingWhite ||
11535                 gameMode == IcsPlayingBlack ||
11536                 gameMode == BeginningOfGame) {
11537                 char buf[MSG_SIZ];
11538                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11539                         resultDetails);
11540                 if (first.pr != NoProc) {
11541                     SendToProgram(buf, &first);
11542                 }
11543                 if (second.pr != NoProc &&
11544                     gameMode == TwoMachinesPlay) {
11545                     SendToProgram(buf, &second);
11546                 }
11547             }
11548         }
11549
11550         if (appData.icsActive) {
11551             if (appData.quietPlay &&
11552                 (gameMode == IcsPlayingWhite ||
11553                  gameMode == IcsPlayingBlack)) {
11554                 SendToICS(ics_prefix);
11555                 SendToICS("set shout 1\n");
11556             }
11557             nextGameMode = IcsIdle;
11558             ics_user_moved = FALSE;
11559             /* clean up premove.  It's ugly when the game has ended and the
11560              * premove highlights are still on the board.
11561              */
11562             if (gotPremove) {
11563               gotPremove = FALSE;
11564               ClearPremoveHighlights();
11565               DrawPosition(FALSE, boards[currentMove]);
11566             }
11567             if (whosays == GE_ICS) {
11568                 switch (result) {
11569                 case WhiteWins:
11570                     if (gameMode == IcsPlayingWhite)
11571                         PlayIcsWinSound();
11572                     else if(gameMode == IcsPlayingBlack)
11573                         PlayIcsLossSound();
11574                     break;
11575                 case BlackWins:
11576                     if (gameMode == IcsPlayingBlack)
11577                         PlayIcsWinSound();
11578                     else if(gameMode == IcsPlayingWhite)
11579                         PlayIcsLossSound();
11580                     break;
11581                 case GameIsDrawn:
11582                     PlayIcsDrawSound();
11583                     break;
11584                 default:
11585                     PlayIcsUnfinishedSound();
11586                 }
11587             }
11588             if(appData.quitNext) { ExitEvent(0); return; }
11589         } else if (gameMode == EditGame ||
11590                    gameMode == PlayFromGameFile ||
11591                    gameMode == AnalyzeMode ||
11592                    gameMode == AnalyzeFile) {
11593             nextGameMode = gameMode;
11594         } else {
11595             nextGameMode = EndOfGame;
11596         }
11597         pausing = FALSE;
11598         ModeHighlight();
11599     } else {
11600         nextGameMode = gameMode;
11601     }
11602
11603     if (appData.noChessProgram) {
11604         gameMode = nextGameMode;
11605         ModeHighlight();
11606         endingGame = 0; /* [HGM] crash */
11607         return;
11608     }
11609
11610     if (first.reuse) {
11611         /* Put first chess program into idle state */
11612         if (first.pr != NoProc &&
11613             (gameMode == MachinePlaysWhite ||
11614              gameMode == MachinePlaysBlack ||
11615              gameMode == TwoMachinesPlay ||
11616              gameMode == IcsPlayingWhite ||
11617              gameMode == IcsPlayingBlack ||
11618              gameMode == BeginningOfGame)) {
11619             SendToProgram("force\n", &first);
11620             if (first.usePing) {
11621               char buf[MSG_SIZ];
11622               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11623               SendToProgram(buf, &first);
11624             }
11625         }
11626     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11627         /* Kill off first chess program */
11628         if (first.isr != NULL)
11629           RemoveInputSource(first.isr);
11630         first.isr = NULL;
11631
11632         if (first.pr != NoProc) {
11633             ExitAnalyzeMode();
11634             DoSleep( appData.delayBeforeQuit );
11635             SendToProgram("quit\n", &first);
11636             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11637             first.reload = TRUE;
11638         }
11639         first.pr = NoProc;
11640     }
11641     if (second.reuse) {
11642         /* Put second chess program into idle state */
11643         if (second.pr != NoProc &&
11644             gameMode == TwoMachinesPlay) {
11645             SendToProgram("force\n", &second);
11646             if (second.usePing) {
11647               char buf[MSG_SIZ];
11648               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11649               SendToProgram(buf, &second);
11650             }
11651         }
11652     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11653         /* Kill off second chess program */
11654         if (second.isr != NULL)
11655           RemoveInputSource(second.isr);
11656         second.isr = NULL;
11657
11658         if (second.pr != NoProc) {
11659             DoSleep( appData.delayBeforeQuit );
11660             SendToProgram("quit\n", &second);
11661             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11662             second.reload = TRUE;
11663         }
11664         second.pr = NoProc;
11665     }
11666
11667     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11668         char resChar = '=';
11669         switch (result) {
11670         case WhiteWins:
11671           resChar = '+';
11672           if (first.twoMachinesColor[0] == 'w') {
11673             first.matchWins++;
11674           } else {
11675             second.matchWins++;
11676           }
11677           break;
11678         case BlackWins:
11679           resChar = '-';
11680           if (first.twoMachinesColor[0] == 'b') {
11681             first.matchWins++;
11682           } else {
11683             second.matchWins++;
11684           }
11685           break;
11686         case GameUnfinished:
11687           resChar = ' ';
11688         default:
11689           break;
11690         }
11691
11692         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11693         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11694             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11695             ReserveGame(nextGame, resChar); // sets nextGame
11696             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11697             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11698         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11699
11700         if (nextGame <= appData.matchGames && !abortMatch) {
11701             gameMode = nextGameMode;
11702             matchGame = nextGame; // this will be overruled in tourney mode!
11703             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11704             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11705             endingGame = 0; /* [HGM] crash */
11706             return;
11707         } else {
11708             gameMode = nextGameMode;
11709             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11710                      first.tidy, second.tidy,
11711                      first.matchWins, second.matchWins,
11712                      appData.matchGames - (first.matchWins + second.matchWins));
11713             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11714             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11715             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11716             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11717                 first.twoMachinesColor = "black\n";
11718                 second.twoMachinesColor = "white\n";
11719             } else {
11720                 first.twoMachinesColor = "white\n";
11721                 second.twoMachinesColor = "black\n";
11722             }
11723         }
11724     }
11725     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11726         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11727       ExitAnalyzeMode();
11728     gameMode = nextGameMode;
11729     ModeHighlight();
11730     endingGame = 0;  /* [HGM] crash */
11731     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11732         if(matchMode == TRUE) { // match through command line: exit with or without popup
11733             if(ranking) {
11734                 ToNrEvent(forwardMostMove);
11735                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11736                 else ExitEvent(0);
11737             } else DisplayFatalError(buf, 0, 0);
11738         } else { // match through menu; just stop, with or without popup
11739             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11740             ModeHighlight();
11741             if(ranking){
11742                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11743             } else DisplayNote(buf);
11744       }
11745       if(ranking) free(ranking);
11746     }
11747 }
11748
11749 /* Assumes program was just initialized (initString sent).
11750    Leaves program in force mode. */
11751 void
11752 FeedMovesToProgram (ChessProgramState *cps, int upto)
11753 {
11754     int i;
11755
11756     if (appData.debugMode)
11757       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11758               startedFromSetupPosition ? "position and " : "",
11759               backwardMostMove, upto, cps->which);
11760     if(currentlyInitializedVariant != gameInfo.variant) {
11761       char buf[MSG_SIZ];
11762         // [HGM] variantswitch: make engine aware of new variant
11763         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11764                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11765                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11766         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11767         SendToProgram(buf, cps);
11768         currentlyInitializedVariant = gameInfo.variant;
11769     }
11770     SendToProgram("force\n", cps);
11771     if (startedFromSetupPosition) {
11772         SendBoard(cps, backwardMostMove);
11773     if (appData.debugMode) {
11774         fprintf(debugFP, "feedMoves\n");
11775     }
11776     }
11777     for (i = backwardMostMove; i < upto; i++) {
11778         SendMoveToProgram(i, cps);
11779     }
11780 }
11781
11782
11783 int
11784 ResurrectChessProgram ()
11785 {
11786      /* The chess program may have exited.
11787         If so, restart it and feed it all the moves made so far. */
11788     static int doInit = 0;
11789
11790     if (appData.noChessProgram) return 1;
11791
11792     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11793         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11794         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11795         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11796     } else {
11797         if (first.pr != NoProc) return 1;
11798         StartChessProgram(&first);
11799     }
11800     InitChessProgram(&first, FALSE);
11801     FeedMovesToProgram(&first, currentMove);
11802
11803     if (!first.sendTime) {
11804         /* can't tell gnuchess what its clock should read,
11805            so we bow to its notion. */
11806         ResetClocks();
11807         timeRemaining[0][currentMove] = whiteTimeRemaining;
11808         timeRemaining[1][currentMove] = blackTimeRemaining;
11809     }
11810
11811     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11812                 appData.icsEngineAnalyze) && first.analysisSupport) {
11813       SendToProgram("analyze\n", &first);
11814       first.analyzing = TRUE;
11815     }
11816     return 1;
11817 }
11818
11819 /*
11820  * Button procedures
11821  */
11822 void
11823 Reset (int redraw, int init)
11824 {
11825     int i;
11826
11827     if (appData.debugMode) {
11828         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11829                 redraw, init, gameMode);
11830     }
11831     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11832     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11833     CleanupTail(); // [HGM] vari: delete any stored variations
11834     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11835     pausing = pauseExamInvalid = FALSE;
11836     startedFromSetupPosition = blackPlaysFirst = FALSE;
11837     firstMove = TRUE;
11838     whiteFlag = blackFlag = FALSE;
11839     userOfferedDraw = FALSE;
11840     hintRequested = bookRequested = FALSE;
11841     first.maybeThinking = FALSE;
11842     second.maybeThinking = FALSE;
11843     first.bookSuspend = FALSE; // [HGM] book
11844     second.bookSuspend = FALSE;
11845     thinkOutput[0] = NULLCHAR;
11846     lastHint[0] = NULLCHAR;
11847     ClearGameInfo(&gameInfo);
11848     gameInfo.variant = StringToVariant(appData.variant);
11849     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11850     ics_user_moved = ics_clock_paused = FALSE;
11851     ics_getting_history = H_FALSE;
11852     ics_gamenum = -1;
11853     white_holding[0] = black_holding[0] = NULLCHAR;
11854     ClearProgramStats();
11855     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11856
11857     ResetFrontEnd();
11858     ClearHighlights();
11859     flipView = appData.flipView;
11860     ClearPremoveHighlights();
11861     gotPremove = FALSE;
11862     alarmSounded = FALSE;
11863     killX = killY = -1; // [HGM] lion
11864
11865     GameEnds(EndOfFile, NULL, GE_PLAYER);
11866     if(appData.serverMovesName != NULL) {
11867         /* [HGM] prepare to make moves file for broadcasting */
11868         clock_t t = clock();
11869         if(serverMoves != NULL) fclose(serverMoves);
11870         serverMoves = fopen(appData.serverMovesName, "r");
11871         if(serverMoves != NULL) {
11872             fclose(serverMoves);
11873             /* delay 15 sec before overwriting, so all clients can see end */
11874             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11875         }
11876         serverMoves = fopen(appData.serverMovesName, "w");
11877     }
11878
11879     ExitAnalyzeMode();
11880     gameMode = BeginningOfGame;
11881     ModeHighlight();
11882     if(appData.icsActive) gameInfo.variant = VariantNormal;
11883     currentMove = forwardMostMove = backwardMostMove = 0;
11884     MarkTargetSquares(1);
11885     InitPosition(redraw);
11886     for (i = 0; i < MAX_MOVES; i++) {
11887         if (commentList[i] != NULL) {
11888             free(commentList[i]);
11889             commentList[i] = NULL;
11890         }
11891     }
11892     ResetClocks();
11893     timeRemaining[0][0] = whiteTimeRemaining;
11894     timeRemaining[1][0] = blackTimeRemaining;
11895
11896     if (first.pr == NoProc) {
11897         StartChessProgram(&first);
11898     }
11899     if (init) {
11900             InitChessProgram(&first, startedFromSetupPosition);
11901     }
11902     DisplayTitle("");
11903     DisplayMessage("", "");
11904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11905     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11906     ClearMap();        // [HGM] exclude: invalidate map
11907 }
11908
11909 void
11910 AutoPlayGameLoop ()
11911 {
11912     for (;;) {
11913         if (!AutoPlayOneMove())
11914           return;
11915         if (matchMode || appData.timeDelay == 0)
11916           continue;
11917         if (appData.timeDelay < 0)
11918           return;
11919         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11920         break;
11921     }
11922 }
11923
11924 void
11925 AnalyzeNextGame()
11926 {
11927     ReloadGame(1); // next game
11928 }
11929
11930 int
11931 AutoPlayOneMove ()
11932 {
11933     int fromX, fromY, toX, toY;
11934
11935     if (appData.debugMode) {
11936       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11937     }
11938
11939     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11940       return FALSE;
11941
11942     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11943       pvInfoList[currentMove].depth = programStats.depth;
11944       pvInfoList[currentMove].score = programStats.score;
11945       pvInfoList[currentMove].time  = 0;
11946       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11947       else { // append analysis of final position as comment
11948         char buf[MSG_SIZ];
11949         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11950         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11951       }
11952       programStats.depth = 0;
11953     }
11954
11955     if (currentMove >= forwardMostMove) {
11956       if(gameMode == AnalyzeFile) {
11957           if(appData.loadGameIndex == -1) {
11958             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11959           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11960           } else {
11961           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11962         }
11963       }
11964 //      gameMode = EndOfGame;
11965 //      ModeHighlight();
11966
11967       /* [AS] Clear current move marker at the end of a game */
11968       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11969
11970       return FALSE;
11971     }
11972
11973     toX = moveList[currentMove][2] - AAA;
11974     toY = moveList[currentMove][3] - ONE;
11975
11976     if (moveList[currentMove][1] == '@') {
11977         if (appData.highlightLastMove) {
11978             SetHighlights(-1, -1, toX, toY);
11979         }
11980     } else {
11981         int viaX = moveList[currentMove][5] - AAA;
11982         int viaY = moveList[currentMove][6] - ONE;
11983         fromX = moveList[currentMove][0] - AAA;
11984         fromY = moveList[currentMove][1] - ONE;
11985
11986         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11987
11988         if(moveList[currentMove][4] == ';') { // multi-leg
11989             ChessSquare piece = boards[currentMove][viaY][viaX];
11990             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11991             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11992             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11993             boards[currentMove][viaY][viaX] = piece;
11994         } else
11995         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11996
11997         if (appData.highlightLastMove) {
11998             SetHighlights(fromX, fromY, toX, toY);
11999         }
12000     }
12001     DisplayMove(currentMove);
12002     SendMoveToProgram(currentMove++, &first);
12003     DisplayBothClocks();
12004     DrawPosition(FALSE, boards[currentMove]);
12005     // [HGM] PV info: always display, routine tests if empty
12006     DisplayComment(currentMove - 1, commentList[currentMove]);
12007     return TRUE;
12008 }
12009
12010
12011 int
12012 LoadGameOneMove (ChessMove readAhead)
12013 {
12014     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12015     char promoChar = NULLCHAR;
12016     ChessMove moveType;
12017     char move[MSG_SIZ];
12018     char *p, *q;
12019
12020     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12021         gameMode != AnalyzeMode && gameMode != Training) {
12022         gameFileFP = NULL;
12023         return FALSE;
12024     }
12025
12026     yyboardindex = forwardMostMove;
12027     if (readAhead != EndOfFile) {
12028       moveType = readAhead;
12029     } else {
12030       if (gameFileFP == NULL)
12031           return FALSE;
12032       moveType = (ChessMove) Myylex();
12033     }
12034
12035     done = FALSE;
12036     switch (moveType) {
12037       case Comment:
12038         if (appData.debugMode)
12039           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12040         p = yy_text;
12041
12042         /* append the comment but don't display it */
12043         AppendComment(currentMove, p, FALSE);
12044         return TRUE;
12045
12046       case WhiteCapturesEnPassant:
12047       case BlackCapturesEnPassant:
12048       case WhitePromotion:
12049       case BlackPromotion:
12050       case WhiteNonPromotion:
12051       case BlackNonPromotion:
12052       case NormalMove:
12053       case FirstLeg:
12054       case WhiteKingSideCastle:
12055       case WhiteQueenSideCastle:
12056       case BlackKingSideCastle:
12057       case BlackQueenSideCastle:
12058       case WhiteKingSideCastleWild:
12059       case WhiteQueenSideCastleWild:
12060       case BlackKingSideCastleWild:
12061       case BlackQueenSideCastleWild:
12062       /* PUSH Fabien */
12063       case WhiteHSideCastleFR:
12064       case WhiteASideCastleFR:
12065       case BlackHSideCastleFR:
12066       case BlackASideCastleFR:
12067       /* POP Fabien */
12068         if (appData.debugMode)
12069           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12070         fromX = currentMoveString[0] - AAA;
12071         fromY = currentMoveString[1] - ONE;
12072         toX = currentMoveString[2] - AAA;
12073         toY = currentMoveString[3] - ONE;
12074         promoChar = currentMoveString[4];
12075         if(promoChar == ';') promoChar = NULLCHAR;
12076         break;
12077
12078       case WhiteDrop:
12079       case BlackDrop:
12080         if (appData.debugMode)
12081           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12082         fromX = moveType == WhiteDrop ?
12083           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12084         (int) CharToPiece(ToLower(currentMoveString[0]));
12085         fromY = DROP_RANK;
12086         toX = currentMoveString[2] - AAA;
12087         toY = currentMoveString[3] - ONE;
12088         break;
12089
12090       case WhiteWins:
12091       case BlackWins:
12092       case GameIsDrawn:
12093       case GameUnfinished:
12094         if (appData.debugMode)
12095           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12096         p = strchr(yy_text, '{');
12097         if (p == NULL) p = strchr(yy_text, '(');
12098         if (p == NULL) {
12099             p = yy_text;
12100             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12101         } else {
12102             q = strchr(p, *p == '{' ? '}' : ')');
12103             if (q != NULL) *q = NULLCHAR;
12104             p++;
12105         }
12106         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12107         GameEnds(moveType, p, GE_FILE);
12108         done = TRUE;
12109         if (cmailMsgLoaded) {
12110             ClearHighlights();
12111             flipView = WhiteOnMove(currentMove);
12112             if (moveType == GameUnfinished) flipView = !flipView;
12113             if (appData.debugMode)
12114               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12115         }
12116         break;
12117
12118       case EndOfFile:
12119         if (appData.debugMode)
12120           fprintf(debugFP, "Parser hit end of file\n");
12121         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12122           case MT_NONE:
12123           case MT_CHECK:
12124             break;
12125           case MT_CHECKMATE:
12126           case MT_STAINMATE:
12127             if (WhiteOnMove(currentMove)) {
12128                 GameEnds(BlackWins, "Black mates", GE_FILE);
12129             } else {
12130                 GameEnds(WhiteWins, "White mates", GE_FILE);
12131             }
12132             break;
12133           case MT_STALEMATE:
12134             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12135             break;
12136         }
12137         done = TRUE;
12138         break;
12139
12140       case MoveNumberOne:
12141         if (lastLoadGameStart == GNUChessGame) {
12142             /* GNUChessGames have numbers, but they aren't move numbers */
12143             if (appData.debugMode)
12144               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12145                       yy_text, (int) moveType);
12146             return LoadGameOneMove(EndOfFile); /* tail recursion */
12147         }
12148         /* else fall thru */
12149
12150       case XBoardGame:
12151       case GNUChessGame:
12152       case PGNTag:
12153         /* Reached start of next game in file */
12154         if (appData.debugMode)
12155           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12156         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12157           case MT_NONE:
12158           case MT_CHECK:
12159             break;
12160           case MT_CHECKMATE:
12161           case MT_STAINMATE:
12162             if (WhiteOnMove(currentMove)) {
12163                 GameEnds(BlackWins, "Black mates", GE_FILE);
12164             } else {
12165                 GameEnds(WhiteWins, "White mates", GE_FILE);
12166             }
12167             break;
12168           case MT_STALEMATE:
12169             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12170             break;
12171         }
12172         done = TRUE;
12173         break;
12174
12175       case PositionDiagram:     /* should not happen; ignore */
12176       case ElapsedTime:         /* ignore */
12177       case NAG:                 /* ignore */
12178         if (appData.debugMode)
12179           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12180                   yy_text, (int) moveType);
12181         return LoadGameOneMove(EndOfFile); /* tail recursion */
12182
12183       case IllegalMove:
12184         if (appData.testLegality) {
12185             if (appData.debugMode)
12186               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12187             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12188                     (forwardMostMove / 2) + 1,
12189                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12190             DisplayError(move, 0);
12191             done = TRUE;
12192         } else {
12193             if (appData.debugMode)
12194               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12195                       yy_text, currentMoveString);
12196             if(currentMoveString[1] == '@') {
12197                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12198                 fromY = DROP_RANK;
12199             } else {
12200                 fromX = currentMoveString[0] - AAA;
12201                 fromY = currentMoveString[1] - ONE;
12202             }
12203             toX = currentMoveString[2] - AAA;
12204             toY = currentMoveString[3] - ONE;
12205             promoChar = currentMoveString[4];
12206         }
12207         break;
12208
12209       case AmbiguousMove:
12210         if (appData.debugMode)
12211           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12212         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12213                 (forwardMostMove / 2) + 1,
12214                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12215         DisplayError(move, 0);
12216         done = TRUE;
12217         break;
12218
12219       default:
12220       case ImpossibleMove:
12221         if (appData.debugMode)
12222           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12223         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12224                 (forwardMostMove / 2) + 1,
12225                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12226         DisplayError(move, 0);
12227         done = TRUE;
12228         break;
12229     }
12230
12231     if (done) {
12232         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12233             DrawPosition(FALSE, boards[currentMove]);
12234             DisplayBothClocks();
12235             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12236               DisplayComment(currentMove - 1, commentList[currentMove]);
12237         }
12238         (void) StopLoadGameTimer();
12239         gameFileFP = NULL;
12240         cmailOldMove = forwardMostMove;
12241         return FALSE;
12242     } else {
12243         /* currentMoveString is set as a side-effect of yylex */
12244
12245         thinkOutput[0] = NULLCHAR;
12246         MakeMove(fromX, fromY, toX, toY, promoChar);
12247         killX = killY = -1; // [HGM] lion: used up
12248         currentMove = forwardMostMove;
12249         return TRUE;
12250     }
12251 }
12252
12253 /* Load the nth game from the given file */
12254 int
12255 LoadGameFromFile (char *filename, int n, char *title, int useList)
12256 {
12257     FILE *f;
12258     char buf[MSG_SIZ];
12259
12260     if (strcmp(filename, "-") == 0) {
12261         f = stdin;
12262         title = "stdin";
12263     } else {
12264         f = fopen(filename, "rb");
12265         if (f == NULL) {
12266           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12267             DisplayError(buf, errno);
12268             return FALSE;
12269         }
12270     }
12271     if (fseek(f, 0, 0) == -1) {
12272         /* f is not seekable; probably a pipe */
12273         useList = FALSE;
12274     }
12275     if (useList && n == 0) {
12276         int error = GameListBuild(f);
12277         if (error) {
12278             DisplayError(_("Cannot build game list"), error);
12279         } else if (!ListEmpty(&gameList) &&
12280                    ((ListGame *) gameList.tailPred)->number > 1) {
12281             GameListPopUp(f, title);
12282             return TRUE;
12283         }
12284         GameListDestroy();
12285         n = 1;
12286     }
12287     if (n == 0) n = 1;
12288     return LoadGame(f, n, title, FALSE);
12289 }
12290
12291
12292 void
12293 MakeRegisteredMove ()
12294 {
12295     int fromX, fromY, toX, toY;
12296     char promoChar;
12297     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12298         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12299           case CMAIL_MOVE:
12300           case CMAIL_DRAW:
12301             if (appData.debugMode)
12302               fprintf(debugFP, "Restoring %s for game %d\n",
12303                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12304
12305             thinkOutput[0] = NULLCHAR;
12306             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12307             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12308             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12309             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12310             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12311             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12312             MakeMove(fromX, fromY, toX, toY, promoChar);
12313             ShowMove(fromX, fromY, toX, toY);
12314
12315             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12316               case MT_NONE:
12317               case MT_CHECK:
12318                 break;
12319
12320               case MT_CHECKMATE:
12321               case MT_STAINMATE:
12322                 if (WhiteOnMove(currentMove)) {
12323                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12324                 } else {
12325                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12326                 }
12327                 break;
12328
12329               case MT_STALEMATE:
12330                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12331                 break;
12332             }
12333
12334             break;
12335
12336           case CMAIL_RESIGN:
12337             if (WhiteOnMove(currentMove)) {
12338                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12339             } else {
12340                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12341             }
12342             break;
12343
12344           case CMAIL_ACCEPT:
12345             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12346             break;
12347
12348           default:
12349             break;
12350         }
12351     }
12352
12353     return;
12354 }
12355
12356 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12357 int
12358 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12359 {
12360     int retVal;
12361
12362     if (gameNumber > nCmailGames) {
12363         DisplayError(_("No more games in this message"), 0);
12364         return FALSE;
12365     }
12366     if (f == lastLoadGameFP) {
12367         int offset = gameNumber - lastLoadGameNumber;
12368         if (offset == 0) {
12369             cmailMsg[0] = NULLCHAR;
12370             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12371                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12372                 nCmailMovesRegistered--;
12373             }
12374             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12375             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12376                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12377             }
12378         } else {
12379             if (! RegisterMove()) return FALSE;
12380         }
12381     }
12382
12383     retVal = LoadGame(f, gameNumber, title, useList);
12384
12385     /* Make move registered during previous look at this game, if any */
12386     MakeRegisteredMove();
12387
12388     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12389         commentList[currentMove]
12390           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12391         DisplayComment(currentMove - 1, commentList[currentMove]);
12392     }
12393
12394     return retVal;
12395 }
12396
12397 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12398 int
12399 ReloadGame (int offset)
12400 {
12401     int gameNumber = lastLoadGameNumber + offset;
12402     if (lastLoadGameFP == NULL) {
12403         DisplayError(_("No game has been loaded yet"), 0);
12404         return FALSE;
12405     }
12406     if (gameNumber <= 0) {
12407         DisplayError(_("Can't back up any further"), 0);
12408         return FALSE;
12409     }
12410     if (cmailMsgLoaded) {
12411         return CmailLoadGame(lastLoadGameFP, gameNumber,
12412                              lastLoadGameTitle, lastLoadGameUseList);
12413     } else {
12414         return LoadGame(lastLoadGameFP, gameNumber,
12415                         lastLoadGameTitle, lastLoadGameUseList);
12416     }
12417 }
12418
12419 int keys[EmptySquare+1];
12420
12421 int
12422 PositionMatches (Board b1, Board b2)
12423 {
12424     int r, f, sum=0;
12425     switch(appData.searchMode) {
12426         case 1: return CompareWithRights(b1, b2);
12427         case 2:
12428             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12429                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12430             }
12431             return TRUE;
12432         case 3:
12433             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12434               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12435                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12436             }
12437             return sum==0;
12438         case 4:
12439             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12440                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12441             }
12442             return sum==0;
12443     }
12444     return TRUE;
12445 }
12446
12447 #define Q_PROMO  4
12448 #define Q_EP     3
12449 #define Q_BCASTL 2
12450 #define Q_WCASTL 1
12451
12452 int pieceList[256], quickBoard[256];
12453 ChessSquare pieceType[256] = { EmptySquare };
12454 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12455 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12456 int soughtTotal, turn;
12457 Boolean epOK, flipSearch;
12458
12459 typedef struct {
12460     unsigned char piece, to;
12461 } Move;
12462
12463 #define DSIZE (250000)
12464
12465 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12466 Move *moveDatabase = initialSpace;
12467 unsigned int movePtr, dataSize = DSIZE;
12468
12469 int
12470 MakePieceList (Board board, int *counts)
12471 {
12472     int r, f, n=Q_PROMO, total=0;
12473     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12474     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12475         int sq = f + (r<<4);
12476         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12477             quickBoard[sq] = ++n;
12478             pieceList[n] = sq;
12479             pieceType[n] = board[r][f];
12480             counts[board[r][f]]++;
12481             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12482             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12483             total++;
12484         }
12485     }
12486     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12487     return total;
12488 }
12489
12490 void
12491 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12492 {
12493     int sq = fromX + (fromY<<4);
12494     int piece = quickBoard[sq], rook;
12495     quickBoard[sq] = 0;
12496     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12497     if(piece == pieceList[1] && fromY == toY) {
12498       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12499         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12500         moveDatabase[movePtr++].piece = Q_WCASTL;
12501         quickBoard[sq] = piece;
12502         piece = quickBoard[from]; quickBoard[from] = 0;
12503         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12504       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12505         quickBoard[sq] = 0; // remove Rook
12506         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12507         moveDatabase[movePtr++].piece = Q_WCASTL;
12508         quickBoard[sq] = pieceList[1]; // put King
12509         piece = rook;
12510         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12511       }
12512     } else
12513     if(piece == pieceList[2] && fromY == toY) {
12514       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12515         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12516         moveDatabase[movePtr++].piece = Q_BCASTL;
12517         quickBoard[sq] = piece;
12518         piece = quickBoard[from]; quickBoard[from] = 0;
12519         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12520       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12521         quickBoard[sq] = 0; // remove Rook
12522         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12523         moveDatabase[movePtr++].piece = Q_BCASTL;
12524         quickBoard[sq] = pieceList[2]; // put King
12525         piece = rook;
12526         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12527       }
12528     } else
12529     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12530         quickBoard[(fromY<<4)+toX] = 0;
12531         moveDatabase[movePtr].piece = Q_EP;
12532         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12533         moveDatabase[movePtr].to = sq;
12534     } else
12535     if(promoPiece != pieceType[piece]) {
12536         moveDatabase[movePtr++].piece = Q_PROMO;
12537         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12538     }
12539     moveDatabase[movePtr].piece = piece;
12540     quickBoard[sq] = piece;
12541     movePtr++;
12542 }
12543
12544 int
12545 PackGame (Board board)
12546 {
12547     Move *newSpace = NULL;
12548     moveDatabase[movePtr].piece = 0; // terminate previous game
12549     if(movePtr > dataSize) {
12550         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12551         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12552         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12553         if(newSpace) {
12554             int i;
12555             Move *p = moveDatabase, *q = newSpace;
12556             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12557             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12558             moveDatabase = newSpace;
12559         } else { // calloc failed, we must be out of memory. Too bad...
12560             dataSize = 0; // prevent calloc events for all subsequent games
12561             return 0;     // and signal this one isn't cached
12562         }
12563     }
12564     movePtr++;
12565     MakePieceList(board, counts);
12566     return movePtr;
12567 }
12568
12569 int
12570 QuickCompare (Board board, int *minCounts, int *maxCounts)
12571 {   // compare according to search mode
12572     int r, f;
12573     switch(appData.searchMode)
12574     {
12575       case 1: // exact position match
12576         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12577         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12578             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12579         }
12580         break;
12581       case 2: // can have extra material on empty squares
12582         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12583             if(board[r][f] == EmptySquare) continue;
12584             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12585         }
12586         break;
12587       case 3: // material with exact Pawn structure
12588         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12589             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12590             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12591         } // fall through to material comparison
12592       case 4: // exact material
12593         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12594         break;
12595       case 6: // material range with given imbalance
12596         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12597         // fall through to range comparison
12598       case 5: // material range
12599         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12600     }
12601     return TRUE;
12602 }
12603
12604 int
12605 QuickScan (Board board, Move *move)
12606 {   // reconstruct game,and compare all positions in it
12607     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12608     do {
12609         int piece = move->piece;
12610         int to = move->to, from = pieceList[piece];
12611         if(found < 0) { // if already found just scan to game end for final piece count
12612           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12613            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12614            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12615                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12616             ) {
12617             static int lastCounts[EmptySquare+1];
12618             int i;
12619             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12620             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12621           } else stretch = 0;
12622           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12623           if(found >= 0 && !appData.minPieces) return found;
12624         }
12625         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12626           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12627           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12628             piece = (++move)->piece;
12629             from = pieceList[piece];
12630             counts[pieceType[piece]]--;
12631             pieceType[piece] = (ChessSquare) move->to;
12632             counts[move->to]++;
12633           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12634             counts[pieceType[quickBoard[to]]]--;
12635             quickBoard[to] = 0; total--;
12636             move++;
12637             continue;
12638           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12639             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12640             from  = pieceList[piece]; // so this must be King
12641             quickBoard[from] = 0;
12642             pieceList[piece] = to;
12643             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12644             quickBoard[from] = 0; // rook
12645             quickBoard[to] = piece;
12646             to = move->to; piece = move->piece;
12647             goto aftercastle;
12648           }
12649         }
12650         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12651         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12652         quickBoard[from] = 0;
12653       aftercastle:
12654         quickBoard[to] = piece;
12655         pieceList[piece] = to;
12656         cnt++; turn ^= 3;
12657         move++;
12658     } while(1);
12659 }
12660
12661 void
12662 InitSearch ()
12663 {
12664     int r, f;
12665     flipSearch = FALSE;
12666     CopyBoard(soughtBoard, boards[currentMove]);
12667     soughtTotal = MakePieceList(soughtBoard, maxSought);
12668     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12669     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12670     CopyBoard(reverseBoard, boards[currentMove]);
12671     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12672         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12673         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12674         reverseBoard[r][f] = piece;
12675     }
12676     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12677     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12678     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12679                  || (boards[currentMove][CASTLING][2] == NoRights ||
12680                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12681                  && (boards[currentMove][CASTLING][5] == NoRights ||
12682                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12683       ) {
12684         flipSearch = TRUE;
12685         CopyBoard(flipBoard, soughtBoard);
12686         CopyBoard(rotateBoard, reverseBoard);
12687         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12688             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12689             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12690         }
12691     }
12692     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12693     if(appData.searchMode >= 5) {
12694         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12695         MakePieceList(soughtBoard, minSought);
12696         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12697     }
12698     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12699         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12700 }
12701
12702 GameInfo dummyInfo;
12703 static int creatingBook;
12704
12705 int
12706 GameContainsPosition (FILE *f, ListGame *lg)
12707 {
12708     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12709     int fromX, fromY, toX, toY;
12710     char promoChar;
12711     static int initDone=FALSE;
12712
12713     // weed out games based on numerical tag comparison
12714     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12715     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12716     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12717     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12718     if(!initDone) {
12719         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12720         initDone = TRUE;
12721     }
12722     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12723     else CopyBoard(boards[scratch], initialPosition); // default start position
12724     if(lg->moves) {
12725         turn = btm + 1;
12726         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12727         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12728     }
12729     if(btm) plyNr++;
12730     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12731     fseek(f, lg->offset, 0);
12732     yynewfile(f);
12733     while(1) {
12734         yyboardindex = scratch;
12735         quickFlag = plyNr+1;
12736         next = Myylex();
12737         quickFlag = 0;
12738         switch(next) {
12739             case PGNTag:
12740                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12741             default:
12742                 continue;
12743
12744             case XBoardGame:
12745             case GNUChessGame:
12746                 if(plyNr) return -1; // after we have seen moves, this is for new game
12747               continue;
12748
12749             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12750             case ImpossibleMove:
12751             case WhiteWins: // game ends here with these four
12752             case BlackWins:
12753             case GameIsDrawn:
12754             case GameUnfinished:
12755                 return -1;
12756
12757             case IllegalMove:
12758                 if(appData.testLegality) return -1;
12759             case WhiteCapturesEnPassant:
12760             case BlackCapturesEnPassant:
12761             case WhitePromotion:
12762             case BlackPromotion:
12763             case WhiteNonPromotion:
12764             case BlackNonPromotion:
12765             case NormalMove:
12766             case FirstLeg:
12767             case WhiteKingSideCastle:
12768             case WhiteQueenSideCastle:
12769             case BlackKingSideCastle:
12770             case BlackQueenSideCastle:
12771             case WhiteKingSideCastleWild:
12772             case WhiteQueenSideCastleWild:
12773             case BlackKingSideCastleWild:
12774             case BlackQueenSideCastleWild:
12775             case WhiteHSideCastleFR:
12776             case WhiteASideCastleFR:
12777             case BlackHSideCastleFR:
12778             case BlackASideCastleFR:
12779                 fromX = currentMoveString[0] - AAA;
12780                 fromY = currentMoveString[1] - ONE;
12781                 toX = currentMoveString[2] - AAA;
12782                 toY = currentMoveString[3] - ONE;
12783                 promoChar = currentMoveString[4];
12784                 break;
12785             case WhiteDrop:
12786             case BlackDrop:
12787                 fromX = next == WhiteDrop ?
12788                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12789                   (int) CharToPiece(ToLower(currentMoveString[0]));
12790                 fromY = DROP_RANK;
12791                 toX = currentMoveString[2] - AAA;
12792                 toY = currentMoveString[3] - ONE;
12793                 promoChar = 0;
12794                 break;
12795         }
12796         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12797         plyNr++;
12798         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12799         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12800         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12801         if(appData.findMirror) {
12802             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12803             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12804         }
12805     }
12806 }
12807
12808 /* Load the nth game from open file f */
12809 int
12810 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12811 {
12812     ChessMove cm;
12813     char buf[MSG_SIZ];
12814     int gn = gameNumber;
12815     ListGame *lg = NULL;
12816     int numPGNTags = 0;
12817     int err, pos = -1;
12818     GameMode oldGameMode;
12819     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12820     char oldName[MSG_SIZ];
12821
12822     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12823
12824     if (appData.debugMode)
12825         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12826
12827     if (gameMode == Training )
12828         SetTrainingModeOff();
12829
12830     oldGameMode = gameMode;
12831     if (gameMode != BeginningOfGame) {
12832       Reset(FALSE, TRUE);
12833     }
12834     killX = killY = -1; // [HGM] lion: in case we did not Reset
12835
12836     gameFileFP = f;
12837     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12838         fclose(lastLoadGameFP);
12839     }
12840
12841     if (useList) {
12842         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12843
12844         if (lg) {
12845             fseek(f, lg->offset, 0);
12846             GameListHighlight(gameNumber);
12847             pos = lg->position;
12848             gn = 1;
12849         }
12850         else {
12851             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12852               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12853             else
12854             DisplayError(_("Game number out of range"), 0);
12855             return FALSE;
12856         }
12857     } else {
12858         GameListDestroy();
12859         if (fseek(f, 0, 0) == -1) {
12860             if (f == lastLoadGameFP ?
12861                 gameNumber == lastLoadGameNumber + 1 :
12862                 gameNumber == 1) {
12863                 gn = 1;
12864             } else {
12865                 DisplayError(_("Can't seek on game file"), 0);
12866                 return FALSE;
12867             }
12868         }
12869     }
12870     lastLoadGameFP = f;
12871     lastLoadGameNumber = gameNumber;
12872     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12873     lastLoadGameUseList = useList;
12874
12875     yynewfile(f);
12876
12877     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12878       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12879                 lg->gameInfo.black);
12880             DisplayTitle(buf);
12881     } else if (*title != NULLCHAR) {
12882         if (gameNumber > 1) {
12883           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12884             DisplayTitle(buf);
12885         } else {
12886             DisplayTitle(title);
12887         }
12888     }
12889
12890     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12891         gameMode = PlayFromGameFile;
12892         ModeHighlight();
12893     }
12894
12895     currentMove = forwardMostMove = backwardMostMove = 0;
12896     CopyBoard(boards[0], initialPosition);
12897     StopClocks();
12898
12899     /*
12900      * Skip the first gn-1 games in the file.
12901      * Also skip over anything that precedes an identifiable
12902      * start of game marker, to avoid being confused by
12903      * garbage at the start of the file.  Currently
12904      * recognized start of game markers are the move number "1",
12905      * the pattern "gnuchess .* game", the pattern
12906      * "^[#;%] [^ ]* game file", and a PGN tag block.
12907      * A game that starts with one of the latter two patterns
12908      * will also have a move number 1, possibly
12909      * following a position diagram.
12910      * 5-4-02: Let's try being more lenient and allowing a game to
12911      * start with an unnumbered move.  Does that break anything?
12912      */
12913     cm = lastLoadGameStart = EndOfFile;
12914     while (gn > 0) {
12915         yyboardindex = forwardMostMove;
12916         cm = (ChessMove) Myylex();
12917         switch (cm) {
12918           case EndOfFile:
12919             if (cmailMsgLoaded) {
12920                 nCmailGames = CMAIL_MAX_GAMES - gn;
12921             } else {
12922                 Reset(TRUE, TRUE);
12923                 DisplayError(_("Game not found in file"), 0);
12924             }
12925             return FALSE;
12926
12927           case GNUChessGame:
12928           case XBoardGame:
12929             gn--;
12930             lastLoadGameStart = cm;
12931             break;
12932
12933           case MoveNumberOne:
12934             switch (lastLoadGameStart) {
12935               case GNUChessGame:
12936               case XBoardGame:
12937               case PGNTag:
12938                 break;
12939               case MoveNumberOne:
12940               case EndOfFile:
12941                 gn--;           /* count this game */
12942                 lastLoadGameStart = cm;
12943                 break;
12944               default:
12945                 /* impossible */
12946                 break;
12947             }
12948             break;
12949
12950           case PGNTag:
12951             switch (lastLoadGameStart) {
12952               case GNUChessGame:
12953               case PGNTag:
12954               case MoveNumberOne:
12955               case EndOfFile:
12956                 gn--;           /* count this game */
12957                 lastLoadGameStart = cm;
12958                 break;
12959               case XBoardGame:
12960                 lastLoadGameStart = cm; /* game counted already */
12961                 break;
12962               default:
12963                 /* impossible */
12964                 break;
12965             }
12966             if (gn > 0) {
12967                 do {
12968                     yyboardindex = forwardMostMove;
12969                     cm = (ChessMove) Myylex();
12970                 } while (cm == PGNTag || cm == Comment);
12971             }
12972             break;
12973
12974           case WhiteWins:
12975           case BlackWins:
12976           case GameIsDrawn:
12977             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12978                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12979                     != CMAIL_OLD_RESULT) {
12980                     nCmailResults ++ ;
12981                     cmailResult[  CMAIL_MAX_GAMES
12982                                 - gn - 1] = CMAIL_OLD_RESULT;
12983                 }
12984             }
12985             break;
12986
12987           case NormalMove:
12988           case FirstLeg:
12989             /* Only a NormalMove can be at the start of a game
12990              * without a position diagram. */
12991             if (lastLoadGameStart == EndOfFile ) {
12992               gn--;
12993               lastLoadGameStart = MoveNumberOne;
12994             }
12995             break;
12996
12997           default:
12998             break;
12999         }
13000     }
13001
13002     if (appData.debugMode)
13003       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13004
13005     if (cm == XBoardGame) {
13006         /* Skip any header junk before position diagram and/or move 1 */
13007         for (;;) {
13008             yyboardindex = forwardMostMove;
13009             cm = (ChessMove) Myylex();
13010
13011             if (cm == EndOfFile ||
13012                 cm == GNUChessGame || cm == XBoardGame) {
13013                 /* Empty game; pretend end-of-file and handle later */
13014                 cm = EndOfFile;
13015                 break;
13016             }
13017
13018             if (cm == MoveNumberOne || cm == PositionDiagram ||
13019                 cm == PGNTag || cm == Comment)
13020               break;
13021         }
13022     } else if (cm == GNUChessGame) {
13023         if (gameInfo.event != NULL) {
13024             free(gameInfo.event);
13025         }
13026         gameInfo.event = StrSave(yy_text);
13027     }
13028
13029     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13030     while (cm == PGNTag) {
13031         if (appData.debugMode)
13032           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13033         err = ParsePGNTag(yy_text, &gameInfo);
13034         if (!err) numPGNTags++;
13035
13036         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13037         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13038             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13039             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13040             InitPosition(TRUE);
13041             oldVariant = gameInfo.variant;
13042             if (appData.debugMode)
13043               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13044         }
13045
13046
13047         if (gameInfo.fen != NULL) {
13048           Board initial_position;
13049           startedFromSetupPosition = TRUE;
13050           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13051             Reset(TRUE, TRUE);
13052             DisplayError(_("Bad FEN position in file"), 0);
13053             return FALSE;
13054           }
13055           CopyBoard(boards[0], initial_position);
13056           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13057             CopyBoard(initialPosition, initial_position);
13058           if (blackPlaysFirst) {
13059             currentMove = forwardMostMove = backwardMostMove = 1;
13060             CopyBoard(boards[1], initial_position);
13061             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13062             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13063             timeRemaining[0][1] = whiteTimeRemaining;
13064             timeRemaining[1][1] = blackTimeRemaining;
13065             if (commentList[0] != NULL) {
13066               commentList[1] = commentList[0];
13067               commentList[0] = NULL;
13068             }
13069           } else {
13070             currentMove = forwardMostMove = backwardMostMove = 0;
13071           }
13072           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13073           {   int i;
13074               initialRulePlies = FENrulePlies;
13075               for( i=0; i< nrCastlingRights; i++ )
13076                   initialRights[i] = initial_position[CASTLING][i];
13077           }
13078           yyboardindex = forwardMostMove;
13079           free(gameInfo.fen);
13080           gameInfo.fen = NULL;
13081         }
13082
13083         yyboardindex = forwardMostMove;
13084         cm = (ChessMove) Myylex();
13085
13086         /* Handle comments interspersed among the tags */
13087         while (cm == Comment) {
13088             char *p;
13089             if (appData.debugMode)
13090               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13091             p = yy_text;
13092             AppendComment(currentMove, p, FALSE);
13093             yyboardindex = forwardMostMove;
13094             cm = (ChessMove) Myylex();
13095         }
13096     }
13097
13098     /* don't rely on existence of Event tag since if game was
13099      * pasted from clipboard the Event tag may not exist
13100      */
13101     if (numPGNTags > 0){
13102         char *tags;
13103         if (gameInfo.variant == VariantNormal) {
13104           VariantClass v = StringToVariant(gameInfo.event);
13105           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13106           if(v < VariantShogi) gameInfo.variant = v;
13107         }
13108         if (!matchMode) {
13109           if( appData.autoDisplayTags ) {
13110             tags = PGNTags(&gameInfo);
13111             TagsPopUp(tags, CmailMsg());
13112             free(tags);
13113           }
13114         }
13115     } else {
13116         /* Make something up, but don't display it now */
13117         SetGameInfo();
13118         TagsPopDown();
13119     }
13120
13121     if (cm == PositionDiagram) {
13122         int i, j;
13123         char *p;
13124         Board initial_position;
13125
13126         if (appData.debugMode)
13127           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13128
13129         if (!startedFromSetupPosition) {
13130             p = yy_text;
13131             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13132               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13133                 switch (*p) {
13134                   case '{':
13135                   case '[':
13136                   case '-':
13137                   case ' ':
13138                   case '\t':
13139                   case '\n':
13140                   case '\r':
13141                     break;
13142                   default:
13143                     initial_position[i][j++] = CharToPiece(*p);
13144                     break;
13145                 }
13146             while (*p == ' ' || *p == '\t' ||
13147                    *p == '\n' || *p == '\r') p++;
13148
13149             if (strncmp(p, "black", strlen("black"))==0)
13150               blackPlaysFirst = TRUE;
13151             else
13152               blackPlaysFirst = FALSE;
13153             startedFromSetupPosition = TRUE;
13154
13155             CopyBoard(boards[0], initial_position);
13156             if (blackPlaysFirst) {
13157                 currentMove = forwardMostMove = backwardMostMove = 1;
13158                 CopyBoard(boards[1], initial_position);
13159                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13160                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13161                 timeRemaining[0][1] = whiteTimeRemaining;
13162                 timeRemaining[1][1] = blackTimeRemaining;
13163                 if (commentList[0] != NULL) {
13164                     commentList[1] = commentList[0];
13165                     commentList[0] = NULL;
13166                 }
13167             } else {
13168                 currentMove = forwardMostMove = backwardMostMove = 0;
13169             }
13170         }
13171         yyboardindex = forwardMostMove;
13172         cm = (ChessMove) Myylex();
13173     }
13174
13175   if(!creatingBook) {
13176     if (first.pr == NoProc) {
13177         StartChessProgram(&first);
13178     }
13179     InitChessProgram(&first, FALSE);
13180     if(gameInfo.variant == VariantUnknown && *oldName) {
13181         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13182         gameInfo.variant = v;
13183     }
13184     SendToProgram("force\n", &first);
13185     if (startedFromSetupPosition) {
13186         SendBoard(&first, forwardMostMove);
13187     if (appData.debugMode) {
13188         fprintf(debugFP, "Load Game\n");
13189     }
13190         DisplayBothClocks();
13191     }
13192   }
13193
13194     /* [HGM] server: flag to write setup moves in broadcast file as one */
13195     loadFlag = appData.suppressLoadMoves;
13196
13197     while (cm == Comment) {
13198         char *p;
13199         if (appData.debugMode)
13200           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13201         p = yy_text;
13202         AppendComment(currentMove, p, FALSE);
13203         yyboardindex = forwardMostMove;
13204         cm = (ChessMove) Myylex();
13205     }
13206
13207     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13208         cm == WhiteWins || cm == BlackWins ||
13209         cm == GameIsDrawn || cm == GameUnfinished) {
13210         DisplayMessage("", _("No moves in game"));
13211         if (cmailMsgLoaded) {
13212             if (appData.debugMode)
13213               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13214             ClearHighlights();
13215             flipView = FALSE;
13216         }
13217         DrawPosition(FALSE, boards[currentMove]);
13218         DisplayBothClocks();
13219         gameMode = EditGame;
13220         ModeHighlight();
13221         gameFileFP = NULL;
13222         cmailOldMove = 0;
13223         return TRUE;
13224     }
13225
13226     // [HGM] PV info: routine tests if comment empty
13227     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13228         DisplayComment(currentMove - 1, commentList[currentMove]);
13229     }
13230     if (!matchMode && appData.timeDelay != 0)
13231       DrawPosition(FALSE, boards[currentMove]);
13232
13233     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13234       programStats.ok_to_send = 1;
13235     }
13236
13237     /* if the first token after the PGN tags is a move
13238      * and not move number 1, retrieve it from the parser
13239      */
13240     if (cm != MoveNumberOne)
13241         LoadGameOneMove(cm);
13242
13243     /* load the remaining moves from the file */
13244     while (LoadGameOneMove(EndOfFile)) {
13245       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13246       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13247     }
13248
13249     /* rewind to the start of the game */
13250     currentMove = backwardMostMove;
13251
13252     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13253
13254     if (oldGameMode == AnalyzeFile) {
13255       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13256       AnalyzeFileEvent();
13257     } else
13258     if (oldGameMode == AnalyzeMode) {
13259       AnalyzeFileEvent();
13260     }
13261
13262     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13263         long int w, b; // [HGM] adjourn: restore saved clock times
13264         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13265         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13266             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13267             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13268         }
13269     }
13270
13271     if(creatingBook) return TRUE;
13272     if (!matchMode && pos > 0) {
13273         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13274     } else
13275     if (matchMode || appData.timeDelay == 0) {
13276       ToEndEvent();
13277     } else if (appData.timeDelay > 0) {
13278       AutoPlayGameLoop();
13279     }
13280
13281     if (appData.debugMode)
13282         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13283
13284     loadFlag = 0; /* [HGM] true game starts */
13285     return TRUE;
13286 }
13287
13288 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13289 int
13290 ReloadPosition (int offset)
13291 {
13292     int positionNumber = lastLoadPositionNumber + offset;
13293     if (lastLoadPositionFP == NULL) {
13294         DisplayError(_("No position has been loaded yet"), 0);
13295         return FALSE;
13296     }
13297     if (positionNumber <= 0) {
13298         DisplayError(_("Can't back up any further"), 0);
13299         return FALSE;
13300     }
13301     return LoadPosition(lastLoadPositionFP, positionNumber,
13302                         lastLoadPositionTitle);
13303 }
13304
13305 /* Load the nth position from the given file */
13306 int
13307 LoadPositionFromFile (char *filename, int n, char *title)
13308 {
13309     FILE *f;
13310     char buf[MSG_SIZ];
13311
13312     if (strcmp(filename, "-") == 0) {
13313         return LoadPosition(stdin, n, "stdin");
13314     } else {
13315         f = fopen(filename, "rb");
13316         if (f == NULL) {
13317             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13318             DisplayError(buf, errno);
13319             return FALSE;
13320         } else {
13321             return LoadPosition(f, n, title);
13322         }
13323     }
13324 }
13325
13326 /* Load the nth position from the given open file, and close it */
13327 int
13328 LoadPosition (FILE *f, int positionNumber, char *title)
13329 {
13330     char *p, line[MSG_SIZ];
13331     Board initial_position;
13332     int i, j, fenMode, pn;
13333
13334     if (gameMode == Training )
13335         SetTrainingModeOff();
13336
13337     if (gameMode != BeginningOfGame) {
13338         Reset(FALSE, TRUE);
13339     }
13340     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13341         fclose(lastLoadPositionFP);
13342     }
13343     if (positionNumber == 0) positionNumber = 1;
13344     lastLoadPositionFP = f;
13345     lastLoadPositionNumber = positionNumber;
13346     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13347     if (first.pr == NoProc && !appData.noChessProgram) {
13348       StartChessProgram(&first);
13349       InitChessProgram(&first, FALSE);
13350     }
13351     pn = positionNumber;
13352     if (positionNumber < 0) {
13353         /* Negative position number means to seek to that byte offset */
13354         if (fseek(f, -positionNumber, 0) == -1) {
13355             DisplayError(_("Can't seek on position file"), 0);
13356             return FALSE;
13357         };
13358         pn = 1;
13359     } else {
13360         if (fseek(f, 0, 0) == -1) {
13361             if (f == lastLoadPositionFP ?
13362                 positionNumber == lastLoadPositionNumber + 1 :
13363                 positionNumber == 1) {
13364                 pn = 1;
13365             } else {
13366                 DisplayError(_("Can't seek on position file"), 0);
13367                 return FALSE;
13368             }
13369         }
13370     }
13371     /* See if this file is FEN or old-style xboard */
13372     if (fgets(line, MSG_SIZ, f) == NULL) {
13373         DisplayError(_("Position not found in file"), 0);
13374         return FALSE;
13375     }
13376     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13377     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13378
13379     if (pn >= 2) {
13380         if (fenMode || line[0] == '#') pn--;
13381         while (pn > 0) {
13382             /* skip positions before number pn */
13383             if (fgets(line, MSG_SIZ, f) == NULL) {
13384                 Reset(TRUE, TRUE);
13385                 DisplayError(_("Position not found in file"), 0);
13386                 return FALSE;
13387             }
13388             if (fenMode || line[0] == '#') pn--;
13389         }
13390     }
13391
13392     if (fenMode) {
13393         char *p;
13394         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13395             DisplayError(_("Bad FEN position in file"), 0);
13396             return FALSE;
13397         }
13398         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13399             sscanf(p+3, "%s", bestMove);
13400         } else *bestMove = NULLCHAR;
13401     } else {
13402         (void) fgets(line, MSG_SIZ, f);
13403         (void) fgets(line, MSG_SIZ, f);
13404
13405         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13406             (void) fgets(line, MSG_SIZ, f);
13407             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13408                 if (*p == ' ')
13409                   continue;
13410                 initial_position[i][j++] = CharToPiece(*p);
13411             }
13412         }
13413
13414         blackPlaysFirst = FALSE;
13415         if (!feof(f)) {
13416             (void) fgets(line, MSG_SIZ, f);
13417             if (strncmp(line, "black", strlen("black"))==0)
13418               blackPlaysFirst = TRUE;
13419         }
13420     }
13421     startedFromSetupPosition = TRUE;
13422
13423     CopyBoard(boards[0], initial_position);
13424     if (blackPlaysFirst) {
13425         currentMove = forwardMostMove = backwardMostMove = 1;
13426         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13427         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13428         CopyBoard(boards[1], initial_position);
13429         DisplayMessage("", _("Black to play"));
13430     } else {
13431         currentMove = forwardMostMove = backwardMostMove = 0;
13432         DisplayMessage("", _("White to play"));
13433     }
13434     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13435     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13436         SendToProgram("force\n", &first);
13437         SendBoard(&first, forwardMostMove);
13438     }
13439     if (appData.debugMode) {
13440 int i, j;
13441   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13442   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13443         fprintf(debugFP, "Load Position\n");
13444     }
13445
13446     if (positionNumber > 1) {
13447       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13448         DisplayTitle(line);
13449     } else {
13450         DisplayTitle(title);
13451     }
13452     gameMode = EditGame;
13453     ModeHighlight();
13454     ResetClocks();
13455     timeRemaining[0][1] = whiteTimeRemaining;
13456     timeRemaining[1][1] = blackTimeRemaining;
13457     DrawPosition(FALSE, boards[currentMove]);
13458
13459     return TRUE;
13460 }
13461
13462
13463 void
13464 CopyPlayerNameIntoFileName (char **dest, char *src)
13465 {
13466     while (*src != NULLCHAR && *src != ',') {
13467         if (*src == ' ') {
13468             *(*dest)++ = '_';
13469             src++;
13470         } else {
13471             *(*dest)++ = *src++;
13472         }
13473     }
13474 }
13475
13476 char *
13477 DefaultFileName (char *ext)
13478 {
13479     static char def[MSG_SIZ];
13480     char *p;
13481
13482     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13483         p = def;
13484         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13485         *p++ = '-';
13486         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13487         *p++ = '.';
13488         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13489     } else {
13490         def[0] = NULLCHAR;
13491     }
13492     return def;
13493 }
13494
13495 /* Save the current game to the given file */
13496 int
13497 SaveGameToFile (char *filename, int append)
13498 {
13499     FILE *f;
13500     char buf[MSG_SIZ];
13501     int result, i, t,tot=0;
13502
13503     if (strcmp(filename, "-") == 0) {
13504         return SaveGame(stdout, 0, NULL);
13505     } else {
13506         for(i=0; i<10; i++) { // upto 10 tries
13507              f = fopen(filename, append ? "a" : "w");
13508              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13509              if(f || errno != 13) break;
13510              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13511              tot += t;
13512         }
13513         if (f == NULL) {
13514             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13515             DisplayError(buf, errno);
13516             return FALSE;
13517         } else {
13518             safeStrCpy(buf, lastMsg, MSG_SIZ);
13519             DisplayMessage(_("Waiting for access to save file"), "");
13520             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13521             DisplayMessage(_("Saving game"), "");
13522             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13523             result = SaveGame(f, 0, NULL);
13524             DisplayMessage(buf, "");
13525             return result;
13526         }
13527     }
13528 }
13529
13530 char *
13531 SavePart (char *str)
13532 {
13533     static char buf[MSG_SIZ];
13534     char *p;
13535
13536     p = strchr(str, ' ');
13537     if (p == NULL) return str;
13538     strncpy(buf, str, p - str);
13539     buf[p - str] = NULLCHAR;
13540     return buf;
13541 }
13542
13543 #define PGN_MAX_LINE 75
13544
13545 #define PGN_SIDE_WHITE  0
13546 #define PGN_SIDE_BLACK  1
13547
13548 static int
13549 FindFirstMoveOutOfBook (int side)
13550 {
13551     int result = -1;
13552
13553     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13554         int index = backwardMostMove;
13555         int has_book_hit = 0;
13556
13557         if( (index % 2) != side ) {
13558             index++;
13559         }
13560
13561         while( index < forwardMostMove ) {
13562             /* Check to see if engine is in book */
13563             int depth = pvInfoList[index].depth;
13564             int score = pvInfoList[index].score;
13565             int in_book = 0;
13566
13567             if( depth <= 2 ) {
13568                 in_book = 1;
13569             }
13570             else if( score == 0 && depth == 63 ) {
13571                 in_book = 1; /* Zappa */
13572             }
13573             else if( score == 2 && depth == 99 ) {
13574                 in_book = 1; /* Abrok */
13575             }
13576
13577             has_book_hit += in_book;
13578
13579             if( ! in_book ) {
13580                 result = index;
13581
13582                 break;
13583             }
13584
13585             index += 2;
13586         }
13587     }
13588
13589     return result;
13590 }
13591
13592 void
13593 GetOutOfBookInfo (char * buf)
13594 {
13595     int oob[2];
13596     int i;
13597     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13598
13599     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13600     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13601
13602     *buf = '\0';
13603
13604     if( oob[0] >= 0 || oob[1] >= 0 ) {
13605         for( i=0; i<2; i++ ) {
13606             int idx = oob[i];
13607
13608             if( idx >= 0 ) {
13609                 if( i > 0 && oob[0] >= 0 ) {
13610                     strcat( buf, "   " );
13611                 }
13612
13613                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13614                 sprintf( buf+strlen(buf), "%s%.2f",
13615                     pvInfoList[idx].score >= 0 ? "+" : "",
13616                     pvInfoList[idx].score / 100.0 );
13617             }
13618         }
13619     }
13620 }
13621
13622 /* Save game in PGN style */
13623 static void
13624 SaveGamePGN2 (FILE *f)
13625 {
13626     int i, offset, linelen, newblock;
13627 //    char *movetext;
13628     char numtext[32];
13629     int movelen, numlen, blank;
13630     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13631
13632     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13633
13634     PrintPGNTags(f, &gameInfo);
13635
13636     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13637
13638     if (backwardMostMove > 0 || startedFromSetupPosition) {
13639         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13640         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13641         fprintf(f, "\n{--------------\n");
13642         PrintPosition(f, backwardMostMove);
13643         fprintf(f, "--------------}\n");
13644         free(fen);
13645     }
13646     else {
13647         /* [AS] Out of book annotation */
13648         if( appData.saveOutOfBookInfo ) {
13649             char buf[64];
13650
13651             GetOutOfBookInfo( buf );
13652
13653             if( buf[0] != '\0' ) {
13654                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13655             }
13656         }
13657
13658         fprintf(f, "\n");
13659     }
13660
13661     i = backwardMostMove;
13662     linelen = 0;
13663     newblock = TRUE;
13664
13665     while (i < forwardMostMove) {
13666         /* Print comments preceding this move */
13667         if (commentList[i] != NULL) {
13668             if (linelen > 0) fprintf(f, "\n");
13669             fprintf(f, "%s", commentList[i]);
13670             linelen = 0;
13671             newblock = TRUE;
13672         }
13673
13674         /* Format move number */
13675         if ((i % 2) == 0)
13676           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13677         else
13678           if (newblock)
13679             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13680           else
13681             numtext[0] = NULLCHAR;
13682
13683         numlen = strlen(numtext);
13684         newblock = FALSE;
13685
13686         /* Print move number */
13687         blank = linelen > 0 && numlen > 0;
13688         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13689             fprintf(f, "\n");
13690             linelen = 0;
13691             blank = 0;
13692         }
13693         if (blank) {
13694             fprintf(f, " ");
13695             linelen++;
13696         }
13697         fprintf(f, "%s", numtext);
13698         linelen += numlen;
13699
13700         /* Get move */
13701         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13702         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13703
13704         /* Print move */
13705         blank = linelen > 0 && movelen > 0;
13706         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13707             fprintf(f, "\n");
13708             linelen = 0;
13709             blank = 0;
13710         }
13711         if (blank) {
13712             fprintf(f, " ");
13713             linelen++;
13714         }
13715         fprintf(f, "%s", move_buffer);
13716         linelen += movelen;
13717
13718         /* [AS] Add PV info if present */
13719         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13720             /* [HGM] add time */
13721             char buf[MSG_SIZ]; int seconds;
13722
13723             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13724
13725             if( seconds <= 0)
13726               buf[0] = 0;
13727             else
13728               if( seconds < 30 )
13729                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13730               else
13731                 {
13732                   seconds = (seconds + 4)/10; // round to full seconds
13733                   if( seconds < 60 )
13734                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13735                   else
13736                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13737                 }
13738
13739             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13740                       pvInfoList[i].score >= 0 ? "+" : "",
13741                       pvInfoList[i].score / 100.0,
13742                       pvInfoList[i].depth,
13743                       buf );
13744
13745             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13746
13747             /* Print score/depth */
13748             blank = linelen > 0 && movelen > 0;
13749             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13750                 fprintf(f, "\n");
13751                 linelen = 0;
13752                 blank = 0;
13753             }
13754             if (blank) {
13755                 fprintf(f, " ");
13756                 linelen++;
13757             }
13758             fprintf(f, "%s", move_buffer);
13759             linelen += movelen;
13760         }
13761
13762         i++;
13763     }
13764
13765     /* Start a new line */
13766     if (linelen > 0) fprintf(f, "\n");
13767
13768     /* Print comments after last move */
13769     if (commentList[i] != NULL) {
13770         fprintf(f, "%s\n", commentList[i]);
13771     }
13772
13773     /* Print result */
13774     if (gameInfo.resultDetails != NULL &&
13775         gameInfo.resultDetails[0] != NULLCHAR) {
13776         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13777         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13778            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13779             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13780         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13781     } else {
13782         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13783     }
13784 }
13785
13786 /* Save game in PGN style and close the file */
13787 int
13788 SaveGamePGN (FILE *f)
13789 {
13790     SaveGamePGN2(f);
13791     fclose(f);
13792     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13793     return TRUE;
13794 }
13795
13796 /* Save game in old style and close the file */
13797 int
13798 SaveGameOldStyle (FILE *f)
13799 {
13800     int i, offset;
13801     time_t tm;
13802
13803     tm = time((time_t *) NULL);
13804
13805     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13806     PrintOpponents(f);
13807
13808     if (backwardMostMove > 0 || startedFromSetupPosition) {
13809         fprintf(f, "\n[--------------\n");
13810         PrintPosition(f, backwardMostMove);
13811         fprintf(f, "--------------]\n");
13812     } else {
13813         fprintf(f, "\n");
13814     }
13815
13816     i = backwardMostMove;
13817     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13818
13819     while (i < forwardMostMove) {
13820         if (commentList[i] != NULL) {
13821             fprintf(f, "[%s]\n", commentList[i]);
13822         }
13823
13824         if ((i % 2) == 1) {
13825             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13826             i++;
13827         } else {
13828             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13829             i++;
13830             if (commentList[i] != NULL) {
13831                 fprintf(f, "\n");
13832                 continue;
13833             }
13834             if (i >= forwardMostMove) {
13835                 fprintf(f, "\n");
13836                 break;
13837             }
13838             fprintf(f, "%s\n", parseList[i]);
13839             i++;
13840         }
13841     }
13842
13843     if (commentList[i] != NULL) {
13844         fprintf(f, "[%s]\n", commentList[i]);
13845     }
13846
13847     /* This isn't really the old style, but it's close enough */
13848     if (gameInfo.resultDetails != NULL &&
13849         gameInfo.resultDetails[0] != NULLCHAR) {
13850         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13851                 gameInfo.resultDetails);
13852     } else {
13853         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13854     }
13855
13856     fclose(f);
13857     return TRUE;
13858 }
13859
13860 /* Save the current game to open file f and close the file */
13861 int
13862 SaveGame (FILE *f, int dummy, char *dummy2)
13863 {
13864     if (gameMode == EditPosition) EditPositionDone(TRUE);
13865     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13866     if (appData.oldSaveStyle)
13867       return SaveGameOldStyle(f);
13868     else
13869       return SaveGamePGN(f);
13870 }
13871
13872 /* Save the current position to the given file */
13873 int
13874 SavePositionToFile (char *filename)
13875 {
13876     FILE *f;
13877     char buf[MSG_SIZ];
13878
13879     if (strcmp(filename, "-") == 0) {
13880         return SavePosition(stdout, 0, NULL);
13881     } else {
13882         f = fopen(filename, "a");
13883         if (f == NULL) {
13884             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13885             DisplayError(buf, errno);
13886             return FALSE;
13887         } else {
13888             safeStrCpy(buf, lastMsg, MSG_SIZ);
13889             DisplayMessage(_("Waiting for access to save file"), "");
13890             flock(fileno(f), LOCK_EX); // [HGM] lock
13891             DisplayMessage(_("Saving position"), "");
13892             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13893             SavePosition(f, 0, NULL);
13894             DisplayMessage(buf, "");
13895             return TRUE;
13896         }
13897     }
13898 }
13899
13900 /* Save the current position to the given open file and close the file */
13901 int
13902 SavePosition (FILE *f, int dummy, char *dummy2)
13903 {
13904     time_t tm;
13905     char *fen;
13906
13907     if (gameMode == EditPosition) EditPositionDone(TRUE);
13908     if (appData.oldSaveStyle) {
13909         tm = time((time_t *) NULL);
13910
13911         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13912         PrintOpponents(f);
13913         fprintf(f, "[--------------\n");
13914         PrintPosition(f, currentMove);
13915         fprintf(f, "--------------]\n");
13916     } else {
13917         fen = PositionToFEN(currentMove, NULL, 1);
13918         fprintf(f, "%s\n", fen);
13919         free(fen);
13920     }
13921     fclose(f);
13922     return TRUE;
13923 }
13924
13925 void
13926 ReloadCmailMsgEvent (int unregister)
13927 {
13928 #if !WIN32
13929     static char *inFilename = NULL;
13930     static char *outFilename;
13931     int i;
13932     struct stat inbuf, outbuf;
13933     int status;
13934
13935     /* Any registered moves are unregistered if unregister is set, */
13936     /* i.e. invoked by the signal handler */
13937     if (unregister) {
13938         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13939             cmailMoveRegistered[i] = FALSE;
13940             if (cmailCommentList[i] != NULL) {
13941                 free(cmailCommentList[i]);
13942                 cmailCommentList[i] = NULL;
13943             }
13944         }
13945         nCmailMovesRegistered = 0;
13946     }
13947
13948     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13949         cmailResult[i] = CMAIL_NOT_RESULT;
13950     }
13951     nCmailResults = 0;
13952
13953     if (inFilename == NULL) {
13954         /* Because the filenames are static they only get malloced once  */
13955         /* and they never get freed                                      */
13956         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13957         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13958
13959         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13960         sprintf(outFilename, "%s.out", appData.cmailGameName);
13961     }
13962
13963     status = stat(outFilename, &outbuf);
13964     if (status < 0) {
13965         cmailMailedMove = FALSE;
13966     } else {
13967         status = stat(inFilename, &inbuf);
13968         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13969     }
13970
13971     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13972        counts the games, notes how each one terminated, etc.
13973
13974        It would be nice to remove this kludge and instead gather all
13975        the information while building the game list.  (And to keep it
13976        in the game list nodes instead of having a bunch of fixed-size
13977        parallel arrays.)  Note this will require getting each game's
13978        termination from the PGN tags, as the game list builder does
13979        not process the game moves.  --mann
13980        */
13981     cmailMsgLoaded = TRUE;
13982     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13983
13984     /* Load first game in the file or popup game menu */
13985     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13986
13987 #endif /* !WIN32 */
13988     return;
13989 }
13990
13991 int
13992 RegisterMove ()
13993 {
13994     FILE *f;
13995     char string[MSG_SIZ];
13996
13997     if (   cmailMailedMove
13998         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13999         return TRUE;            /* Allow free viewing  */
14000     }
14001
14002     /* Unregister move to ensure that we don't leave RegisterMove        */
14003     /* with the move registered when the conditions for registering no   */
14004     /* longer hold                                                       */
14005     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14006         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14007         nCmailMovesRegistered --;
14008
14009         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14010           {
14011               free(cmailCommentList[lastLoadGameNumber - 1]);
14012               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14013           }
14014     }
14015
14016     if (cmailOldMove == -1) {
14017         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14018         return FALSE;
14019     }
14020
14021     if (currentMove > cmailOldMove + 1) {
14022         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14023         return FALSE;
14024     }
14025
14026     if (currentMove < cmailOldMove) {
14027         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14028         return FALSE;
14029     }
14030
14031     if (forwardMostMove > currentMove) {
14032         /* Silently truncate extra moves */
14033         TruncateGame();
14034     }
14035
14036     if (   (currentMove == cmailOldMove + 1)
14037         || (   (currentMove == cmailOldMove)
14038             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14039                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14040         if (gameInfo.result != GameUnfinished) {
14041             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14042         }
14043
14044         if (commentList[currentMove] != NULL) {
14045             cmailCommentList[lastLoadGameNumber - 1]
14046               = StrSave(commentList[currentMove]);
14047         }
14048         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14049
14050         if (appData.debugMode)
14051           fprintf(debugFP, "Saving %s for game %d\n",
14052                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14053
14054         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14055
14056         f = fopen(string, "w");
14057         if (appData.oldSaveStyle) {
14058             SaveGameOldStyle(f); /* also closes the file */
14059
14060             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14061             f = fopen(string, "w");
14062             SavePosition(f, 0, NULL); /* also closes the file */
14063         } else {
14064             fprintf(f, "{--------------\n");
14065             PrintPosition(f, currentMove);
14066             fprintf(f, "--------------}\n\n");
14067
14068             SaveGame(f, 0, NULL); /* also closes the file*/
14069         }
14070
14071         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14072         nCmailMovesRegistered ++;
14073     } else if (nCmailGames == 1) {
14074         DisplayError(_("You have not made a move yet"), 0);
14075         return FALSE;
14076     }
14077
14078     return TRUE;
14079 }
14080
14081 void
14082 MailMoveEvent ()
14083 {
14084 #if !WIN32
14085     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14086     FILE *commandOutput;
14087     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14088     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14089     int nBuffers;
14090     int i;
14091     int archived;
14092     char *arcDir;
14093
14094     if (! cmailMsgLoaded) {
14095         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14096         return;
14097     }
14098
14099     if (nCmailGames == nCmailResults) {
14100         DisplayError(_("No unfinished games"), 0);
14101         return;
14102     }
14103
14104 #if CMAIL_PROHIBIT_REMAIL
14105     if (cmailMailedMove) {
14106       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);
14107         DisplayError(msg, 0);
14108         return;
14109     }
14110 #endif
14111
14112     if (! (cmailMailedMove || RegisterMove())) return;
14113
14114     if (   cmailMailedMove
14115         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14116       snprintf(string, MSG_SIZ, partCommandString,
14117                appData.debugMode ? " -v" : "", appData.cmailGameName);
14118         commandOutput = popen(string, "r");
14119
14120         if (commandOutput == NULL) {
14121             DisplayError(_("Failed to invoke cmail"), 0);
14122         } else {
14123             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14124                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14125             }
14126             if (nBuffers > 1) {
14127                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14128                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14129                 nBytes = MSG_SIZ - 1;
14130             } else {
14131                 (void) memcpy(msg, buffer, nBytes);
14132             }
14133             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14134
14135             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14136                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14137
14138                 archived = TRUE;
14139                 for (i = 0; i < nCmailGames; i ++) {
14140                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14141                         archived = FALSE;
14142                     }
14143                 }
14144                 if (   archived
14145                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14146                         != NULL)) {
14147                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14148                            arcDir,
14149                            appData.cmailGameName,
14150                            gameInfo.date);
14151                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14152                     cmailMsgLoaded = FALSE;
14153                 }
14154             }
14155
14156             DisplayInformation(msg);
14157             pclose(commandOutput);
14158         }
14159     } else {
14160         if ((*cmailMsg) != '\0') {
14161             DisplayInformation(cmailMsg);
14162         }
14163     }
14164
14165     return;
14166 #endif /* !WIN32 */
14167 }
14168
14169 char *
14170 CmailMsg ()
14171 {
14172 #if WIN32
14173     return NULL;
14174 #else
14175     int  prependComma = 0;
14176     char number[5];
14177     char string[MSG_SIZ];       /* Space for game-list */
14178     int  i;
14179
14180     if (!cmailMsgLoaded) return "";
14181
14182     if (cmailMailedMove) {
14183       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14184     } else {
14185         /* Create a list of games left */
14186       snprintf(string, MSG_SIZ, "[");
14187         for (i = 0; i < nCmailGames; i ++) {
14188             if (! (   cmailMoveRegistered[i]
14189                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14190                 if (prependComma) {
14191                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14192                 } else {
14193                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14194                     prependComma = 1;
14195                 }
14196
14197                 strcat(string, number);
14198             }
14199         }
14200         strcat(string, "]");
14201
14202         if (nCmailMovesRegistered + nCmailResults == 0) {
14203             switch (nCmailGames) {
14204               case 1:
14205                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14206                 break;
14207
14208               case 2:
14209                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14210                 break;
14211
14212               default:
14213                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14214                          nCmailGames);
14215                 break;
14216             }
14217         } else {
14218             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14219               case 1:
14220                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14221                          string);
14222                 break;
14223
14224               case 0:
14225                 if (nCmailResults == nCmailGames) {
14226                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14227                 } else {
14228                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14229                 }
14230                 break;
14231
14232               default:
14233                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14234                          string);
14235             }
14236         }
14237     }
14238     return cmailMsg;
14239 #endif /* WIN32 */
14240 }
14241
14242 void
14243 ResetGameEvent ()
14244 {
14245     if (gameMode == Training)
14246       SetTrainingModeOff();
14247
14248     Reset(TRUE, TRUE);
14249     cmailMsgLoaded = FALSE;
14250     if (appData.icsActive) {
14251       SendToICS(ics_prefix);
14252       SendToICS("refresh\n");
14253     }
14254 }
14255
14256 void
14257 ExitEvent (int status)
14258 {
14259     exiting++;
14260     if (exiting > 2) {
14261       /* Give up on clean exit */
14262       exit(status);
14263     }
14264     if (exiting > 1) {
14265       /* Keep trying for clean exit */
14266       return;
14267     }
14268
14269     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14270     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14271
14272     if (telnetISR != NULL) {
14273       RemoveInputSource(telnetISR);
14274     }
14275     if (icsPR != NoProc) {
14276       DestroyChildProcess(icsPR, TRUE);
14277     }
14278
14279     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14280     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14281
14282     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14283     /* make sure this other one finishes before killing it!                  */
14284     if(endingGame) { int count = 0;
14285         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14286         while(endingGame && count++ < 10) DoSleep(1);
14287         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14288     }
14289
14290     /* Kill off chess programs */
14291     if (first.pr != NoProc) {
14292         ExitAnalyzeMode();
14293
14294         DoSleep( appData.delayBeforeQuit );
14295         SendToProgram("quit\n", &first);
14296         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14297     }
14298     if (second.pr != NoProc) {
14299         DoSleep( appData.delayBeforeQuit );
14300         SendToProgram("quit\n", &second);
14301         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14302     }
14303     if (first.isr != NULL) {
14304         RemoveInputSource(first.isr);
14305     }
14306     if (second.isr != NULL) {
14307         RemoveInputSource(second.isr);
14308     }
14309
14310     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14311     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14312
14313     ShutDownFrontEnd();
14314     exit(status);
14315 }
14316
14317 void
14318 PauseEngine (ChessProgramState *cps)
14319 {
14320     SendToProgram("pause\n", cps);
14321     cps->pause = 2;
14322 }
14323
14324 void
14325 UnPauseEngine (ChessProgramState *cps)
14326 {
14327     SendToProgram("resume\n", cps);
14328     cps->pause = 1;
14329 }
14330
14331 void
14332 PauseEvent ()
14333 {
14334     if (appData.debugMode)
14335         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14336     if (pausing) {
14337         pausing = FALSE;
14338         ModeHighlight();
14339         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14340             StartClocks();
14341             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14342                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14343                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14344             }
14345             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14346             HandleMachineMove(stashedInputMove, stalledEngine);
14347             stalledEngine = NULL;
14348             return;
14349         }
14350         if (gameMode == MachinePlaysWhite ||
14351             gameMode == TwoMachinesPlay   ||
14352             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14353             if(first.pause)  UnPauseEngine(&first);
14354             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14355             if(second.pause) UnPauseEngine(&second);
14356             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14357             StartClocks();
14358         } else {
14359             DisplayBothClocks();
14360         }
14361         if (gameMode == PlayFromGameFile) {
14362             if (appData.timeDelay >= 0)
14363                 AutoPlayGameLoop();
14364         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14365             Reset(FALSE, TRUE);
14366             SendToICS(ics_prefix);
14367             SendToICS("refresh\n");
14368         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14369             ForwardInner(forwardMostMove);
14370         }
14371         pauseExamInvalid = FALSE;
14372     } else {
14373         switch (gameMode) {
14374           default:
14375             return;
14376           case IcsExamining:
14377             pauseExamForwardMostMove = forwardMostMove;
14378             pauseExamInvalid = FALSE;
14379             /* fall through */
14380           case IcsObserving:
14381           case IcsPlayingWhite:
14382           case IcsPlayingBlack:
14383             pausing = TRUE;
14384             ModeHighlight();
14385             return;
14386           case PlayFromGameFile:
14387             (void) StopLoadGameTimer();
14388             pausing = TRUE;
14389             ModeHighlight();
14390             break;
14391           case BeginningOfGame:
14392             if (appData.icsActive) return;
14393             /* else fall through */
14394           case MachinePlaysWhite:
14395           case MachinePlaysBlack:
14396           case TwoMachinesPlay:
14397             if (forwardMostMove == 0)
14398               return;           /* don't pause if no one has moved */
14399             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14400                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14401                 if(onMove->pause) {           // thinking engine can be paused
14402                     PauseEngine(onMove);      // do it
14403                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14404                         PauseEngine(onMove->other);
14405                     else
14406                         SendToProgram("easy\n", onMove->other);
14407                     StopClocks();
14408                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14409             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14410                 if(first.pause) {
14411                     PauseEngine(&first);
14412                     StopClocks();
14413                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14414             } else { // human on move, pause pondering by either method
14415                 if(first.pause)
14416                     PauseEngine(&first);
14417                 else if(appData.ponderNextMove)
14418                     SendToProgram("easy\n", &first);
14419                 StopClocks();
14420             }
14421             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14422           case AnalyzeMode:
14423             pausing = TRUE;
14424             ModeHighlight();
14425             break;
14426         }
14427     }
14428 }
14429
14430 void
14431 EditCommentEvent ()
14432 {
14433     char title[MSG_SIZ];
14434
14435     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14436       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14437     } else {
14438       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14439                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14440                parseList[currentMove - 1]);
14441     }
14442
14443     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14444 }
14445
14446
14447 void
14448 EditTagsEvent ()
14449 {
14450     char *tags = PGNTags(&gameInfo);
14451     bookUp = FALSE;
14452     EditTagsPopUp(tags, NULL);
14453     free(tags);
14454 }
14455
14456 void
14457 ToggleSecond ()
14458 {
14459   if(second.analyzing) {
14460     SendToProgram("exit\n", &second);
14461     second.analyzing = FALSE;
14462   } else {
14463     if (second.pr == NoProc) StartChessProgram(&second);
14464     InitChessProgram(&second, FALSE);
14465     FeedMovesToProgram(&second, currentMove);
14466
14467     SendToProgram("analyze\n", &second);
14468     second.analyzing = TRUE;
14469   }
14470 }
14471
14472 /* Toggle ShowThinking */
14473 void
14474 ToggleShowThinking()
14475 {
14476   appData.showThinking = !appData.showThinking;
14477   ShowThinkingEvent();
14478 }
14479
14480 int
14481 AnalyzeModeEvent ()
14482 {
14483     char buf[MSG_SIZ];
14484
14485     if (!first.analysisSupport) {
14486       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14487       DisplayError(buf, 0);
14488       return 0;
14489     }
14490     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14491     if (appData.icsActive) {
14492         if (gameMode != IcsObserving) {
14493           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14494             DisplayError(buf, 0);
14495             /* secure check */
14496             if (appData.icsEngineAnalyze) {
14497                 if (appData.debugMode)
14498                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14499                 ExitAnalyzeMode();
14500                 ModeHighlight();
14501             }
14502             return 0;
14503         }
14504         /* if enable, user wants to disable icsEngineAnalyze */
14505         if (appData.icsEngineAnalyze) {
14506                 ExitAnalyzeMode();
14507                 ModeHighlight();
14508                 return 0;
14509         }
14510         appData.icsEngineAnalyze = TRUE;
14511         if (appData.debugMode)
14512             fprintf(debugFP, "ICS engine analyze starting... \n");
14513     }
14514
14515     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14516     if (appData.noChessProgram || gameMode == AnalyzeMode)
14517       return 0;
14518
14519     if (gameMode != AnalyzeFile) {
14520         if (!appData.icsEngineAnalyze) {
14521                EditGameEvent();
14522                if (gameMode != EditGame) return 0;
14523         }
14524         if (!appData.showThinking) ToggleShowThinking();
14525         ResurrectChessProgram();
14526         SendToProgram("analyze\n", &first);
14527         first.analyzing = TRUE;
14528         /*first.maybeThinking = TRUE;*/
14529         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14530         EngineOutputPopUp();
14531     }
14532     if (!appData.icsEngineAnalyze) {
14533         gameMode = AnalyzeMode;
14534         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14535     }
14536     pausing = FALSE;
14537     ModeHighlight();
14538     SetGameInfo();
14539
14540     StartAnalysisClock();
14541     GetTimeMark(&lastNodeCountTime);
14542     lastNodeCount = 0;
14543     return 1;
14544 }
14545
14546 void
14547 AnalyzeFileEvent ()
14548 {
14549     if (appData.noChessProgram || gameMode == AnalyzeFile)
14550       return;
14551
14552     if (!first.analysisSupport) {
14553       char buf[MSG_SIZ];
14554       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14555       DisplayError(buf, 0);
14556       return;
14557     }
14558
14559     if (gameMode != AnalyzeMode) {
14560         keepInfo = 1; // mere annotating should not alter PGN tags
14561         EditGameEvent();
14562         keepInfo = 0;
14563         if (gameMode != EditGame) return;
14564         if (!appData.showThinking) ToggleShowThinking();
14565         ResurrectChessProgram();
14566         SendToProgram("analyze\n", &first);
14567         first.analyzing = TRUE;
14568         /*first.maybeThinking = TRUE;*/
14569         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14570         EngineOutputPopUp();
14571     }
14572     gameMode = AnalyzeFile;
14573     pausing = FALSE;
14574     ModeHighlight();
14575
14576     StartAnalysisClock();
14577     GetTimeMark(&lastNodeCountTime);
14578     lastNodeCount = 0;
14579     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14580     AnalysisPeriodicEvent(1);
14581 }
14582
14583 void
14584 MachineWhiteEvent ()
14585 {
14586     char buf[MSG_SIZ];
14587     char *bookHit = NULL;
14588
14589     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14590       return;
14591
14592
14593     if (gameMode == PlayFromGameFile ||
14594         gameMode == TwoMachinesPlay  ||
14595         gameMode == Training         ||
14596         gameMode == AnalyzeMode      ||
14597         gameMode == EndOfGame)
14598         EditGameEvent();
14599
14600     if (gameMode == EditPosition)
14601         EditPositionDone(TRUE);
14602
14603     if (!WhiteOnMove(currentMove)) {
14604         DisplayError(_("It is not White's turn"), 0);
14605         return;
14606     }
14607
14608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14609       ExitAnalyzeMode();
14610
14611     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14612         gameMode == AnalyzeFile)
14613         TruncateGame();
14614
14615     ResurrectChessProgram();    /* in case it isn't running */
14616     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14617         gameMode = MachinePlaysWhite;
14618         ResetClocks();
14619     } else
14620     gameMode = MachinePlaysWhite;
14621     pausing = FALSE;
14622     ModeHighlight();
14623     SetGameInfo();
14624     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14625     DisplayTitle(buf);
14626     if (first.sendName) {
14627       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14628       SendToProgram(buf, &first);
14629     }
14630     if (first.sendTime) {
14631       if (first.useColors) {
14632         SendToProgram("black\n", &first); /*gnu kludge*/
14633       }
14634       SendTimeRemaining(&first, TRUE);
14635     }
14636     if (first.useColors) {
14637       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14638     }
14639     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14640     SetMachineThinkingEnables();
14641     first.maybeThinking = TRUE;
14642     StartClocks();
14643     firstMove = FALSE;
14644
14645     if (appData.autoFlipView && !flipView) {
14646       flipView = !flipView;
14647       DrawPosition(FALSE, NULL);
14648       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14649     }
14650
14651     if(bookHit) { // [HGM] book: simulate book reply
14652         static char bookMove[MSG_SIZ]; // a bit generous?
14653
14654         programStats.nodes = programStats.depth = programStats.time =
14655         programStats.score = programStats.got_only_move = 0;
14656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14657
14658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14659         strcat(bookMove, bookHit);
14660         HandleMachineMove(bookMove, &first);
14661     }
14662 }
14663
14664 void
14665 MachineBlackEvent ()
14666 {
14667   char buf[MSG_SIZ];
14668   char *bookHit = NULL;
14669
14670     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14671         return;
14672
14673
14674     if (gameMode == PlayFromGameFile ||
14675         gameMode == TwoMachinesPlay  ||
14676         gameMode == Training         ||
14677         gameMode == AnalyzeMode      ||
14678         gameMode == EndOfGame)
14679         EditGameEvent();
14680
14681     if (gameMode == EditPosition)
14682         EditPositionDone(TRUE);
14683
14684     if (WhiteOnMove(currentMove)) {
14685         DisplayError(_("It is not Black's turn"), 0);
14686         return;
14687     }
14688
14689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14690       ExitAnalyzeMode();
14691
14692     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14693         gameMode == AnalyzeFile)
14694         TruncateGame();
14695
14696     ResurrectChessProgram();    /* in case it isn't running */
14697     gameMode = MachinePlaysBlack;
14698     pausing = FALSE;
14699     ModeHighlight();
14700     SetGameInfo();
14701     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14702     DisplayTitle(buf);
14703     if (first.sendName) {
14704       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14705       SendToProgram(buf, &first);
14706     }
14707     if (first.sendTime) {
14708       if (first.useColors) {
14709         SendToProgram("white\n", &first); /*gnu kludge*/
14710       }
14711       SendTimeRemaining(&first, FALSE);
14712     }
14713     if (first.useColors) {
14714       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14715     }
14716     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14717     SetMachineThinkingEnables();
14718     first.maybeThinking = TRUE;
14719     StartClocks();
14720
14721     if (appData.autoFlipView && flipView) {
14722       flipView = !flipView;
14723       DrawPosition(FALSE, NULL);
14724       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14725     }
14726     if(bookHit) { // [HGM] book: simulate book reply
14727         static char bookMove[MSG_SIZ]; // a bit generous?
14728
14729         programStats.nodes = programStats.depth = programStats.time =
14730         programStats.score = programStats.got_only_move = 0;
14731         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14732
14733         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14734         strcat(bookMove, bookHit);
14735         HandleMachineMove(bookMove, &first);
14736     }
14737 }
14738
14739
14740 void
14741 DisplayTwoMachinesTitle ()
14742 {
14743     char buf[MSG_SIZ];
14744     if (appData.matchGames > 0) {
14745         if(appData.tourneyFile[0]) {
14746           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14747                    gameInfo.white, _("vs."), gameInfo.black,
14748                    nextGame+1, appData.matchGames+1,
14749                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14750         } else
14751         if (first.twoMachinesColor[0] == 'w') {
14752           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14753                    gameInfo.white, _("vs."),  gameInfo.black,
14754                    first.matchWins, second.matchWins,
14755                    matchGame - 1 - (first.matchWins + second.matchWins));
14756         } else {
14757           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14758                    gameInfo.white, _("vs."), gameInfo.black,
14759                    second.matchWins, first.matchWins,
14760                    matchGame - 1 - (first.matchWins + second.matchWins));
14761         }
14762     } else {
14763       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14764     }
14765     DisplayTitle(buf);
14766 }
14767
14768 void
14769 SettingsMenuIfReady ()
14770 {
14771   if (second.lastPing != second.lastPong) {
14772     DisplayMessage("", _("Waiting for second chess program"));
14773     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14774     return;
14775   }
14776   ThawUI();
14777   DisplayMessage("", "");
14778   SettingsPopUp(&second);
14779 }
14780
14781 int
14782 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14783 {
14784     char buf[MSG_SIZ];
14785     if (cps->pr == NoProc) {
14786         StartChessProgram(cps);
14787         if (cps->protocolVersion == 1) {
14788           retry();
14789           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14790         } else {
14791           /* kludge: allow timeout for initial "feature" command */
14792           if(retry != TwoMachinesEventIfReady) FreezeUI();
14793           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14794           DisplayMessage("", buf);
14795           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14796         }
14797         return 1;
14798     }
14799     return 0;
14800 }
14801
14802 void
14803 TwoMachinesEvent P((void))
14804 {
14805     int i;
14806     char buf[MSG_SIZ];
14807     ChessProgramState *onmove;
14808     char *bookHit = NULL;
14809     static int stalling = 0;
14810     TimeMark now;
14811     long wait;
14812
14813     if (appData.noChessProgram) return;
14814
14815     switch (gameMode) {
14816       case TwoMachinesPlay:
14817         return;
14818       case MachinePlaysWhite:
14819       case MachinePlaysBlack:
14820         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14821             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14822             return;
14823         }
14824         /* fall through */
14825       case BeginningOfGame:
14826       case PlayFromGameFile:
14827       case EndOfGame:
14828         EditGameEvent();
14829         if (gameMode != EditGame) return;
14830         break;
14831       case EditPosition:
14832         EditPositionDone(TRUE);
14833         break;
14834       case AnalyzeMode:
14835       case AnalyzeFile:
14836         ExitAnalyzeMode();
14837         break;
14838       case EditGame:
14839       default:
14840         break;
14841     }
14842
14843 //    forwardMostMove = currentMove;
14844     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14845     startingEngine = TRUE;
14846
14847     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14848
14849     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14850     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14851       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14852       return;
14853     }
14854     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14855
14856     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14857                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14858         startingEngine = matchMode = FALSE;
14859         DisplayError("second engine does not play this", 0);
14860         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14861         EditGameEvent(); // switch back to EditGame mode
14862         return;
14863     }
14864
14865     if(!stalling) {
14866       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14867       SendToProgram("force\n", &second);
14868       stalling = 1;
14869       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14870       return;
14871     }
14872     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14873     if(appData.matchPause>10000 || appData.matchPause<10)
14874                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14875     wait = SubtractTimeMarks(&now, &pauseStart);
14876     if(wait < appData.matchPause) {
14877         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14878         return;
14879     }
14880     // we are now committed to starting the game
14881     stalling = 0;
14882     DisplayMessage("", "");
14883     if (startedFromSetupPosition) {
14884         SendBoard(&second, backwardMostMove);
14885     if (appData.debugMode) {
14886         fprintf(debugFP, "Two Machines\n");
14887     }
14888     }
14889     for (i = backwardMostMove; i < forwardMostMove; i++) {
14890         SendMoveToProgram(i, &second);
14891     }
14892
14893     gameMode = TwoMachinesPlay;
14894     pausing = startingEngine = FALSE;
14895     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14896     SetGameInfo();
14897     DisplayTwoMachinesTitle();
14898     firstMove = TRUE;
14899     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14900         onmove = &first;
14901     } else {
14902         onmove = &second;
14903     }
14904     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14905     SendToProgram(first.computerString, &first);
14906     if (first.sendName) {
14907       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14908       SendToProgram(buf, &first);
14909     }
14910     SendToProgram(second.computerString, &second);
14911     if (second.sendName) {
14912       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14913       SendToProgram(buf, &second);
14914     }
14915
14916     ResetClocks();
14917     if (!first.sendTime || !second.sendTime) {
14918         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14919         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14920     }
14921     if (onmove->sendTime) {
14922       if (onmove->useColors) {
14923         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14924       }
14925       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14926     }
14927     if (onmove->useColors) {
14928       SendToProgram(onmove->twoMachinesColor, onmove);
14929     }
14930     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14931 //    SendToProgram("go\n", onmove);
14932     onmove->maybeThinking = TRUE;
14933     SetMachineThinkingEnables();
14934
14935     StartClocks();
14936
14937     if(bookHit) { // [HGM] book: simulate book reply
14938         static char bookMove[MSG_SIZ]; // a bit generous?
14939
14940         programStats.nodes = programStats.depth = programStats.time =
14941         programStats.score = programStats.got_only_move = 0;
14942         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14943
14944         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14945         strcat(bookMove, bookHit);
14946         savedMessage = bookMove; // args for deferred call
14947         savedState = onmove;
14948         ScheduleDelayedEvent(DeferredBookMove, 1);
14949     }
14950 }
14951
14952 void
14953 TrainingEvent ()
14954 {
14955     if (gameMode == Training) {
14956       SetTrainingModeOff();
14957       gameMode = PlayFromGameFile;
14958       DisplayMessage("", _("Training mode off"));
14959     } else {
14960       gameMode = Training;
14961       animateTraining = appData.animate;
14962
14963       /* make sure we are not already at the end of the game */
14964       if (currentMove < forwardMostMove) {
14965         SetTrainingModeOn();
14966         DisplayMessage("", _("Training mode on"));
14967       } else {
14968         gameMode = PlayFromGameFile;
14969         DisplayError(_("Already at end of game"), 0);
14970       }
14971     }
14972     ModeHighlight();
14973 }
14974
14975 void
14976 IcsClientEvent ()
14977 {
14978     if (!appData.icsActive) return;
14979     switch (gameMode) {
14980       case IcsPlayingWhite:
14981       case IcsPlayingBlack:
14982       case IcsObserving:
14983       case IcsIdle:
14984       case BeginningOfGame:
14985       case IcsExamining:
14986         return;
14987
14988       case EditGame:
14989         break;
14990
14991       case EditPosition:
14992         EditPositionDone(TRUE);
14993         break;
14994
14995       case AnalyzeMode:
14996       case AnalyzeFile:
14997         ExitAnalyzeMode();
14998         break;
14999
15000       default:
15001         EditGameEvent();
15002         break;
15003     }
15004
15005     gameMode = IcsIdle;
15006     ModeHighlight();
15007     return;
15008 }
15009
15010 void
15011 EditGameEvent ()
15012 {
15013     int i;
15014
15015     switch (gameMode) {
15016       case Training:
15017         SetTrainingModeOff();
15018         break;
15019       case MachinePlaysWhite:
15020       case MachinePlaysBlack:
15021       case BeginningOfGame:
15022         SendToProgram("force\n", &first);
15023         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15024             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15025                 char buf[MSG_SIZ];
15026                 abortEngineThink = TRUE;
15027                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15028                 SendToProgram(buf, &first);
15029                 DisplayMessage("Aborting engine think", "");
15030                 FreezeUI();
15031             }
15032         }
15033         SetUserThinkingEnables();
15034         break;
15035       case PlayFromGameFile:
15036         (void) StopLoadGameTimer();
15037         if (gameFileFP != NULL) {
15038             gameFileFP = NULL;
15039         }
15040         break;
15041       case EditPosition:
15042         EditPositionDone(TRUE);
15043         break;
15044       case AnalyzeMode:
15045       case AnalyzeFile:
15046         ExitAnalyzeMode();
15047         SendToProgram("force\n", &first);
15048         break;
15049       case TwoMachinesPlay:
15050         GameEnds(EndOfFile, NULL, GE_PLAYER);
15051         ResurrectChessProgram();
15052         SetUserThinkingEnables();
15053         break;
15054       case EndOfGame:
15055         ResurrectChessProgram();
15056         break;
15057       case IcsPlayingBlack:
15058       case IcsPlayingWhite:
15059         DisplayError(_("Warning: You are still playing a game"), 0);
15060         break;
15061       case IcsObserving:
15062         DisplayError(_("Warning: You are still observing a game"), 0);
15063         break;
15064       case IcsExamining:
15065         DisplayError(_("Warning: You are still examining a game"), 0);
15066         break;
15067       case IcsIdle:
15068         break;
15069       case EditGame:
15070       default:
15071         return;
15072     }
15073
15074     pausing = FALSE;
15075     StopClocks();
15076     first.offeredDraw = second.offeredDraw = 0;
15077
15078     if (gameMode == PlayFromGameFile) {
15079         whiteTimeRemaining = timeRemaining[0][currentMove];
15080         blackTimeRemaining = timeRemaining[1][currentMove];
15081         DisplayTitle("");
15082     }
15083
15084     if (gameMode == MachinePlaysWhite ||
15085         gameMode == MachinePlaysBlack ||
15086         gameMode == TwoMachinesPlay ||
15087         gameMode == EndOfGame) {
15088         i = forwardMostMove;
15089         while (i > currentMove) {
15090             SendToProgram("undo\n", &first);
15091             i--;
15092         }
15093         if(!adjustedClock) {
15094         whiteTimeRemaining = timeRemaining[0][currentMove];
15095         blackTimeRemaining = timeRemaining[1][currentMove];
15096         DisplayBothClocks();
15097         }
15098         if (whiteFlag || blackFlag) {
15099             whiteFlag = blackFlag = 0;
15100         }
15101         DisplayTitle("");
15102     }
15103
15104     gameMode = EditGame;
15105     ModeHighlight();
15106     SetGameInfo();
15107 }
15108
15109
15110 void
15111 EditPositionEvent ()
15112 {
15113     if (gameMode == EditPosition) {
15114         EditGameEvent();
15115         return;
15116     }
15117
15118     EditGameEvent();
15119     if (gameMode != EditGame) return;
15120
15121     gameMode = EditPosition;
15122     ModeHighlight();
15123     SetGameInfo();
15124     if (currentMove > 0)
15125       CopyBoard(boards[0], boards[currentMove]);
15126
15127     blackPlaysFirst = !WhiteOnMove(currentMove);
15128     ResetClocks();
15129     currentMove = forwardMostMove = backwardMostMove = 0;
15130     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15131     DisplayMove(-1);
15132     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15133 }
15134
15135 void
15136 ExitAnalyzeMode ()
15137 {
15138     /* [DM] icsEngineAnalyze - possible call from other functions */
15139     if (appData.icsEngineAnalyze) {
15140         appData.icsEngineAnalyze = FALSE;
15141
15142         DisplayMessage("",_("Close ICS engine analyze..."));
15143     }
15144     if (first.analysisSupport && first.analyzing) {
15145       SendToBoth("exit\n");
15146       first.analyzing = second.analyzing = FALSE;
15147     }
15148     thinkOutput[0] = NULLCHAR;
15149 }
15150
15151 void
15152 EditPositionDone (Boolean fakeRights)
15153 {
15154     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15155
15156     startedFromSetupPosition = TRUE;
15157     InitChessProgram(&first, FALSE);
15158     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15159       boards[0][EP_STATUS] = EP_NONE;
15160       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15161       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15162         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15163         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15164       } else boards[0][CASTLING][2] = NoRights;
15165       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15166         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15167         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15168       } else boards[0][CASTLING][5] = NoRights;
15169       if(gameInfo.variant == VariantSChess) {
15170         int i;
15171         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15172           boards[0][VIRGIN][i] = 0;
15173           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15174           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15175         }
15176       }
15177     }
15178     SendToProgram("force\n", &first);
15179     if (blackPlaysFirst) {
15180         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15181         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15182         currentMove = forwardMostMove = backwardMostMove = 1;
15183         CopyBoard(boards[1], boards[0]);
15184     } else {
15185         currentMove = forwardMostMove = backwardMostMove = 0;
15186     }
15187     SendBoard(&first, forwardMostMove);
15188     if (appData.debugMode) {
15189         fprintf(debugFP, "EditPosDone\n");
15190     }
15191     DisplayTitle("");
15192     DisplayMessage("", "");
15193     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15194     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15195     gameMode = EditGame;
15196     ModeHighlight();
15197     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15198     ClearHighlights(); /* [AS] */
15199 }
15200
15201 /* Pause for `ms' milliseconds */
15202 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15203 void
15204 TimeDelay (long ms)
15205 {
15206     TimeMark m1, m2;
15207
15208     GetTimeMark(&m1);
15209     do {
15210         GetTimeMark(&m2);
15211     } while (SubtractTimeMarks(&m2, &m1) < ms);
15212 }
15213
15214 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15215 void
15216 SendMultiLineToICS (char *buf)
15217 {
15218     char temp[MSG_SIZ+1], *p;
15219     int len;
15220
15221     len = strlen(buf);
15222     if (len > MSG_SIZ)
15223       len = MSG_SIZ;
15224
15225     strncpy(temp, buf, len);
15226     temp[len] = 0;
15227
15228     p = temp;
15229     while (*p) {
15230         if (*p == '\n' || *p == '\r')
15231           *p = ' ';
15232         ++p;
15233     }
15234
15235     strcat(temp, "\n");
15236     SendToICS(temp);
15237     SendToPlayer(temp, strlen(temp));
15238 }
15239
15240 void
15241 SetWhiteToPlayEvent ()
15242 {
15243     if (gameMode == EditPosition) {
15244         blackPlaysFirst = FALSE;
15245         DisplayBothClocks();    /* works because currentMove is 0 */
15246     } else if (gameMode == IcsExamining) {
15247         SendToICS(ics_prefix);
15248         SendToICS("tomove white\n");
15249     }
15250 }
15251
15252 void
15253 SetBlackToPlayEvent ()
15254 {
15255     if (gameMode == EditPosition) {
15256         blackPlaysFirst = TRUE;
15257         currentMove = 1;        /* kludge */
15258         DisplayBothClocks();
15259         currentMove = 0;
15260     } else if (gameMode == IcsExamining) {
15261         SendToICS(ics_prefix);
15262         SendToICS("tomove black\n");
15263     }
15264 }
15265
15266 void
15267 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15268 {
15269     char buf[MSG_SIZ];
15270     ChessSquare piece = boards[0][y][x];
15271     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15272     static int lastVariant;
15273
15274     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15275
15276     switch (selection) {
15277       case ClearBoard:
15278         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15279         MarkTargetSquares(1);
15280         CopyBoard(currentBoard, boards[0]);
15281         CopyBoard(menuBoard, initialPosition);
15282         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15283             SendToICS(ics_prefix);
15284             SendToICS("bsetup clear\n");
15285         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15286             SendToICS(ics_prefix);
15287             SendToICS("clearboard\n");
15288         } else {
15289             int nonEmpty = 0;
15290             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15291                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15292                 for (y = 0; y < BOARD_HEIGHT; y++) {
15293                     if (gameMode == IcsExamining) {
15294                         if (boards[currentMove][y][x] != EmptySquare) {
15295                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15296                                     AAA + x, ONE + y);
15297                             SendToICS(buf);
15298                         }
15299                     } else if(boards[0][y][x] != DarkSquare) {
15300                         if(boards[0][y][x] != p) nonEmpty++;
15301                         boards[0][y][x] = p;
15302                     }
15303                 }
15304             }
15305             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15306                 int r;
15307                 for(r = 0; r < BOARD_HEIGHT; r++) {
15308                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15309                     ChessSquare p = menuBoard[r][x];
15310                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15311                   }
15312                 }
15313                 DisplayMessage("Clicking clock again restores position", "");
15314                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15315                 if(!nonEmpty) { // asked to clear an empty board
15316                     CopyBoard(boards[0], menuBoard);
15317                 } else
15318                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15319                     CopyBoard(boards[0], initialPosition);
15320                 } else
15321                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15322                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15323                     CopyBoard(boards[0], erasedBoard);
15324                 } else
15325                     CopyBoard(erasedBoard, currentBoard);
15326
15327             }
15328         }
15329         if (gameMode == EditPosition) {
15330             DrawPosition(FALSE, boards[0]);
15331         }
15332         break;
15333
15334       case WhitePlay:
15335         SetWhiteToPlayEvent();
15336         break;
15337
15338       case BlackPlay:
15339         SetBlackToPlayEvent();
15340         break;
15341
15342       case EmptySquare:
15343         if (gameMode == IcsExamining) {
15344             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15345             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15346             SendToICS(buf);
15347         } else {
15348             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15349                 if(x == BOARD_LEFT-2) {
15350                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15351                     boards[0][y][1] = 0;
15352                 } else
15353                 if(x == BOARD_RGHT+1) {
15354                     if(y >= gameInfo.holdingsSize) break;
15355                     boards[0][y][BOARD_WIDTH-2] = 0;
15356                 } else break;
15357             }
15358             boards[0][y][x] = EmptySquare;
15359             DrawPosition(FALSE, boards[0]);
15360         }
15361         break;
15362
15363       case PromotePiece:
15364         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15365            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15366             selection = (ChessSquare) (PROMOTED piece);
15367         } else if(piece == EmptySquare) selection = WhiteSilver;
15368         else selection = (ChessSquare)((int)piece - 1);
15369         goto defaultlabel;
15370
15371       case DemotePiece:
15372         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15373            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15374             selection = (ChessSquare) (DEMOTED piece);
15375         } else if(piece == EmptySquare) selection = BlackSilver;
15376         else selection = (ChessSquare)((int)piece + 1);
15377         goto defaultlabel;
15378
15379       case WhiteQueen:
15380       case BlackQueen:
15381         if(gameInfo.variant == VariantShatranj ||
15382            gameInfo.variant == VariantXiangqi  ||
15383            gameInfo.variant == VariantCourier  ||
15384            gameInfo.variant == VariantASEAN    ||
15385            gameInfo.variant == VariantMakruk     )
15386             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15387         goto defaultlabel;
15388
15389       case WhiteKing:
15390       case BlackKing:
15391         if(gameInfo.variant == VariantXiangqi)
15392             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15393         if(gameInfo.variant == VariantKnightmate)
15394             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15395       default:
15396         defaultlabel:
15397         if (gameMode == IcsExamining) {
15398             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15399             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15400                      PieceToChar(selection), AAA + x, ONE + y);
15401             SendToICS(buf);
15402         } else {
15403             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15404                 int n;
15405                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15406                     n = PieceToNumber(selection - BlackPawn);
15407                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15408                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15409                     boards[0][BOARD_HEIGHT-1-n][1]++;
15410                 } else
15411                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15412                     n = PieceToNumber(selection);
15413                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15414                     boards[0][n][BOARD_WIDTH-1] = selection;
15415                     boards[0][n][BOARD_WIDTH-2]++;
15416                 }
15417             } else
15418             boards[0][y][x] = selection;
15419             DrawPosition(TRUE, boards[0]);
15420             ClearHighlights();
15421             fromX = fromY = -1;
15422         }
15423         break;
15424     }
15425 }
15426
15427
15428 void
15429 DropMenuEvent (ChessSquare selection, int x, int y)
15430 {
15431     ChessMove moveType;
15432
15433     switch (gameMode) {
15434       case IcsPlayingWhite:
15435       case MachinePlaysBlack:
15436         if (!WhiteOnMove(currentMove)) {
15437             DisplayMoveError(_("It is Black's turn"));
15438             return;
15439         }
15440         moveType = WhiteDrop;
15441         break;
15442       case IcsPlayingBlack:
15443       case MachinePlaysWhite:
15444         if (WhiteOnMove(currentMove)) {
15445             DisplayMoveError(_("It is White's turn"));
15446             return;
15447         }
15448         moveType = BlackDrop;
15449         break;
15450       case EditGame:
15451         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15452         break;
15453       default:
15454         return;
15455     }
15456
15457     if (moveType == BlackDrop && selection < BlackPawn) {
15458       selection = (ChessSquare) ((int) selection
15459                                  + (int) BlackPawn - (int) WhitePawn);
15460     }
15461     if (boards[currentMove][y][x] != EmptySquare) {
15462         DisplayMoveError(_("That square is occupied"));
15463         return;
15464     }
15465
15466     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15467 }
15468
15469 void
15470 AcceptEvent ()
15471 {
15472     /* Accept a pending offer of any kind from opponent */
15473
15474     if (appData.icsActive) {
15475         SendToICS(ics_prefix);
15476         SendToICS("accept\n");
15477     } else if (cmailMsgLoaded) {
15478         if (currentMove == cmailOldMove &&
15479             commentList[cmailOldMove] != NULL &&
15480             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15481                    "Black offers a draw" : "White offers a draw")) {
15482             TruncateGame();
15483             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15484             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15485         } else {
15486             DisplayError(_("There is no pending offer on this move"), 0);
15487             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15488         }
15489     } else {
15490         /* Not used for offers from chess program */
15491     }
15492 }
15493
15494 void
15495 DeclineEvent ()
15496 {
15497     /* Decline a pending offer of any kind from opponent */
15498
15499     if (appData.icsActive) {
15500         SendToICS(ics_prefix);
15501         SendToICS("decline\n");
15502     } else if (cmailMsgLoaded) {
15503         if (currentMove == cmailOldMove &&
15504             commentList[cmailOldMove] != NULL &&
15505             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15506                    "Black offers a draw" : "White offers a draw")) {
15507 #ifdef NOTDEF
15508             AppendComment(cmailOldMove, "Draw declined", TRUE);
15509             DisplayComment(cmailOldMove - 1, "Draw declined");
15510 #endif /*NOTDEF*/
15511         } else {
15512             DisplayError(_("There is no pending offer on this move"), 0);
15513         }
15514     } else {
15515         /* Not used for offers from chess program */
15516     }
15517 }
15518
15519 void
15520 RematchEvent ()
15521 {
15522     /* Issue ICS rematch command */
15523     if (appData.icsActive) {
15524         SendToICS(ics_prefix);
15525         SendToICS("rematch\n");
15526     }
15527 }
15528
15529 void
15530 CallFlagEvent ()
15531 {
15532     /* Call your opponent's flag (claim a win on time) */
15533     if (appData.icsActive) {
15534         SendToICS(ics_prefix);
15535         SendToICS("flag\n");
15536     } else {
15537         switch (gameMode) {
15538           default:
15539             return;
15540           case MachinePlaysWhite:
15541             if (whiteFlag) {
15542                 if (blackFlag)
15543                   GameEnds(GameIsDrawn, "Both players ran out of time",
15544                            GE_PLAYER);
15545                 else
15546                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15547             } else {
15548                 DisplayError(_("Your opponent is not out of time"), 0);
15549             }
15550             break;
15551           case MachinePlaysBlack:
15552             if (blackFlag) {
15553                 if (whiteFlag)
15554                   GameEnds(GameIsDrawn, "Both players ran out of time",
15555                            GE_PLAYER);
15556                 else
15557                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15558             } else {
15559                 DisplayError(_("Your opponent is not out of time"), 0);
15560             }
15561             break;
15562         }
15563     }
15564 }
15565
15566 void
15567 ClockClick (int which)
15568 {       // [HGM] code moved to back-end from winboard.c
15569         if(which) { // black clock
15570           if (gameMode == EditPosition || gameMode == IcsExamining) {
15571             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15572             SetBlackToPlayEvent();
15573           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15574                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15575           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15576           } else if (shiftKey) {
15577             AdjustClock(which, -1);
15578           } else if (gameMode == IcsPlayingWhite ||
15579                      gameMode == MachinePlaysBlack) {
15580             CallFlagEvent();
15581           }
15582         } else { // white clock
15583           if (gameMode == EditPosition || gameMode == IcsExamining) {
15584             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15585             SetWhiteToPlayEvent();
15586           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15587                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15588           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15589           } else if (shiftKey) {
15590             AdjustClock(which, -1);
15591           } else if (gameMode == IcsPlayingBlack ||
15592                    gameMode == MachinePlaysWhite) {
15593             CallFlagEvent();
15594           }
15595         }
15596 }
15597
15598 void
15599 DrawEvent ()
15600 {
15601     /* Offer draw or accept pending draw offer from opponent */
15602
15603     if (appData.icsActive) {
15604         /* Note: tournament rules require draw offers to be
15605            made after you make your move but before you punch
15606            your clock.  Currently ICS doesn't let you do that;
15607            instead, you immediately punch your clock after making
15608            a move, but you can offer a draw at any time. */
15609
15610         SendToICS(ics_prefix);
15611         SendToICS("draw\n");
15612         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15613     } else if (cmailMsgLoaded) {
15614         if (currentMove == cmailOldMove &&
15615             commentList[cmailOldMove] != NULL &&
15616             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15617                    "Black offers a draw" : "White offers a draw")) {
15618             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15619             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15620         } else if (currentMove == cmailOldMove + 1) {
15621             char *offer = WhiteOnMove(cmailOldMove) ?
15622               "White offers a draw" : "Black offers a draw";
15623             AppendComment(currentMove, offer, TRUE);
15624             DisplayComment(currentMove - 1, offer);
15625             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15626         } else {
15627             DisplayError(_("You must make your move before offering a draw"), 0);
15628             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15629         }
15630     } else if (first.offeredDraw) {
15631         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15632     } else {
15633         if (first.sendDrawOffers) {
15634             SendToProgram("draw\n", &first);
15635             userOfferedDraw = TRUE;
15636         }
15637     }
15638 }
15639
15640 void
15641 AdjournEvent ()
15642 {
15643     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15644
15645     if (appData.icsActive) {
15646         SendToICS(ics_prefix);
15647         SendToICS("adjourn\n");
15648     } else {
15649         /* Currently GNU Chess doesn't offer or accept Adjourns */
15650     }
15651 }
15652
15653
15654 void
15655 AbortEvent ()
15656 {
15657     /* Offer Abort or accept pending Abort offer from opponent */
15658
15659     if (appData.icsActive) {
15660         SendToICS(ics_prefix);
15661         SendToICS("abort\n");
15662     } else {
15663         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15664     }
15665 }
15666
15667 void
15668 ResignEvent ()
15669 {
15670     /* Resign.  You can do this even if it's not your turn. */
15671
15672     if (appData.icsActive) {
15673         SendToICS(ics_prefix);
15674         SendToICS("resign\n");
15675     } else {
15676         switch (gameMode) {
15677           case MachinePlaysWhite:
15678             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15679             break;
15680           case MachinePlaysBlack:
15681             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15682             break;
15683           case EditGame:
15684             if (cmailMsgLoaded) {
15685                 TruncateGame();
15686                 if (WhiteOnMove(cmailOldMove)) {
15687                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15688                 } else {
15689                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15690                 }
15691                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15692             }
15693             break;
15694           default:
15695             break;
15696         }
15697     }
15698 }
15699
15700
15701 void
15702 StopObservingEvent ()
15703 {
15704     /* Stop observing current games */
15705     SendToICS(ics_prefix);
15706     SendToICS("unobserve\n");
15707 }
15708
15709 void
15710 StopExaminingEvent ()
15711 {
15712     /* Stop observing current game */
15713     SendToICS(ics_prefix);
15714     SendToICS("unexamine\n");
15715 }
15716
15717 void
15718 ForwardInner (int target)
15719 {
15720     int limit; int oldSeekGraphUp = seekGraphUp;
15721
15722     if (appData.debugMode)
15723         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15724                 target, currentMove, forwardMostMove);
15725
15726     if (gameMode == EditPosition)
15727       return;
15728
15729     seekGraphUp = FALSE;
15730     MarkTargetSquares(1);
15731     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15732
15733     if (gameMode == PlayFromGameFile && !pausing)
15734       PauseEvent();
15735
15736     if (gameMode == IcsExamining && pausing)
15737       limit = pauseExamForwardMostMove;
15738     else
15739       limit = forwardMostMove;
15740
15741     if (target > limit) target = limit;
15742
15743     if (target > 0 && moveList[target - 1][0]) {
15744         int fromX, fromY, toX, toY;
15745         toX = moveList[target - 1][2] - AAA;
15746         toY = moveList[target - 1][3] - ONE;
15747         if (moveList[target - 1][1] == '@') {
15748             if (appData.highlightLastMove) {
15749                 SetHighlights(-1, -1, toX, toY);
15750             }
15751         } else {
15752             int viaX = moveList[target - 1][5] - AAA;
15753             int viaY = moveList[target - 1][6] - ONE;
15754             fromX = moveList[target - 1][0] - AAA;
15755             fromY = moveList[target - 1][1] - ONE;
15756             if (target == currentMove + 1) {
15757                 if(moveList[target - 1][4] == ';') { // multi-leg
15758                     ChessSquare piece = boards[currentMove][viaY][viaX];
15759                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15760                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15761                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15762                     boards[currentMove][viaY][viaX] = piece;
15763                 } else
15764                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15765             }
15766             if (appData.highlightLastMove) {
15767                 SetHighlights(fromX, fromY, toX, toY);
15768             }
15769         }
15770     }
15771     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15772         gameMode == Training || gameMode == PlayFromGameFile ||
15773         gameMode == AnalyzeFile) {
15774         while (currentMove < target) {
15775             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15776             SendMoveToProgram(currentMove++, &first);
15777         }
15778     } else {
15779         currentMove = target;
15780     }
15781
15782     if (gameMode == EditGame || gameMode == EndOfGame) {
15783         whiteTimeRemaining = timeRemaining[0][currentMove];
15784         blackTimeRemaining = timeRemaining[1][currentMove];
15785     }
15786     DisplayBothClocks();
15787     DisplayMove(currentMove - 1);
15788     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15789     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15790     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15791         DisplayComment(currentMove - 1, commentList[currentMove]);
15792     }
15793     ClearMap(); // [HGM] exclude: invalidate map
15794 }
15795
15796
15797 void
15798 ForwardEvent ()
15799 {
15800     if (gameMode == IcsExamining && !pausing) {
15801         SendToICS(ics_prefix);
15802         SendToICS("forward\n");
15803     } else {
15804         ForwardInner(currentMove + 1);
15805     }
15806 }
15807
15808 void
15809 ToEndEvent ()
15810 {
15811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15812         /* to optimze, we temporarily turn off analysis mode while we feed
15813          * the remaining moves to the engine. Otherwise we get analysis output
15814          * after each move.
15815          */
15816         if (first.analysisSupport) {
15817           SendToProgram("exit\nforce\n", &first);
15818           first.analyzing = FALSE;
15819         }
15820     }
15821
15822     if (gameMode == IcsExamining && !pausing) {
15823         SendToICS(ics_prefix);
15824         SendToICS("forward 999999\n");
15825     } else {
15826         ForwardInner(forwardMostMove);
15827     }
15828
15829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15830         /* we have fed all the moves, so reactivate analysis mode */
15831         SendToProgram("analyze\n", &first);
15832         first.analyzing = TRUE;
15833         /*first.maybeThinking = TRUE;*/
15834         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15835     }
15836 }
15837
15838 void
15839 BackwardInner (int target)
15840 {
15841     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15842
15843     if (appData.debugMode)
15844         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15845                 target, currentMove, forwardMostMove);
15846
15847     if (gameMode == EditPosition) return;
15848     seekGraphUp = FALSE;
15849     MarkTargetSquares(1);
15850     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15851     if (currentMove <= backwardMostMove) {
15852         ClearHighlights();
15853         DrawPosition(full_redraw, boards[currentMove]);
15854         return;
15855     }
15856     if (gameMode == PlayFromGameFile && !pausing)
15857       PauseEvent();
15858
15859     if (moveList[target][0]) {
15860         int fromX, fromY, toX, toY;
15861         toX = moveList[target][2] - AAA;
15862         toY = moveList[target][3] - ONE;
15863         if (moveList[target][1] == '@') {
15864             if (appData.highlightLastMove) {
15865                 SetHighlights(-1, -1, toX, toY);
15866             }
15867         } else {
15868             fromX = moveList[target][0] - AAA;
15869             fromY = moveList[target][1] - ONE;
15870             if (target == currentMove - 1) {
15871                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15872             }
15873             if (appData.highlightLastMove) {
15874                 SetHighlights(fromX, fromY, toX, toY);
15875             }
15876         }
15877     }
15878     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15879         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15880         while (currentMove > target) {
15881             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15882                 // null move cannot be undone. Reload program with move history before it.
15883                 int i;
15884                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15885                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15886                 }
15887                 SendBoard(&first, i);
15888               if(second.analyzing) SendBoard(&second, i);
15889                 for(currentMove=i; currentMove<target; currentMove++) {
15890                     SendMoveToProgram(currentMove, &first);
15891                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15892                 }
15893                 break;
15894             }
15895             SendToBoth("undo\n");
15896             currentMove--;
15897         }
15898     } else {
15899         currentMove = target;
15900     }
15901
15902     if (gameMode == EditGame || gameMode == EndOfGame) {
15903         whiteTimeRemaining = timeRemaining[0][currentMove];
15904         blackTimeRemaining = timeRemaining[1][currentMove];
15905     }
15906     DisplayBothClocks();
15907     DisplayMove(currentMove - 1);
15908     DrawPosition(full_redraw, boards[currentMove]);
15909     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15910     // [HGM] PV info: routine tests if comment empty
15911     DisplayComment(currentMove - 1, commentList[currentMove]);
15912     ClearMap(); // [HGM] exclude: invalidate map
15913 }
15914
15915 void
15916 BackwardEvent ()
15917 {
15918     if (gameMode == IcsExamining && !pausing) {
15919         SendToICS(ics_prefix);
15920         SendToICS("backward\n");
15921     } else {
15922         BackwardInner(currentMove - 1);
15923     }
15924 }
15925
15926 void
15927 ToStartEvent ()
15928 {
15929     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15930         /* to optimize, we temporarily turn off analysis mode while we undo
15931          * all the moves. Otherwise we get analysis output after each undo.
15932          */
15933         if (first.analysisSupport) {
15934           SendToProgram("exit\nforce\n", &first);
15935           first.analyzing = FALSE;
15936         }
15937     }
15938
15939     if (gameMode == IcsExamining && !pausing) {
15940         SendToICS(ics_prefix);
15941         SendToICS("backward 999999\n");
15942     } else {
15943         BackwardInner(backwardMostMove);
15944     }
15945
15946     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15947         /* we have fed all the moves, so reactivate analysis mode */
15948         SendToProgram("analyze\n", &first);
15949         first.analyzing = TRUE;
15950         /*first.maybeThinking = TRUE;*/
15951         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15952     }
15953 }
15954
15955 void
15956 ToNrEvent (int to)
15957 {
15958   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15959   if (to >= forwardMostMove) to = forwardMostMove;
15960   if (to <= backwardMostMove) to = backwardMostMove;
15961   if (to < currentMove) {
15962     BackwardInner(to);
15963   } else {
15964     ForwardInner(to);
15965   }
15966 }
15967
15968 void
15969 RevertEvent (Boolean annotate)
15970 {
15971     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15972         return;
15973     }
15974     if (gameMode != IcsExamining) {
15975         DisplayError(_("You are not examining a game"), 0);
15976         return;
15977     }
15978     if (pausing) {
15979         DisplayError(_("You can't revert while pausing"), 0);
15980         return;
15981     }
15982     SendToICS(ics_prefix);
15983     SendToICS("revert\n");
15984 }
15985
15986 void
15987 RetractMoveEvent ()
15988 {
15989     switch (gameMode) {
15990       case MachinePlaysWhite:
15991       case MachinePlaysBlack:
15992         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15993             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15994             return;
15995         }
15996         if (forwardMostMove < 2) return;
15997         currentMove = forwardMostMove = forwardMostMove - 2;
15998         whiteTimeRemaining = timeRemaining[0][currentMove];
15999         blackTimeRemaining = timeRemaining[1][currentMove];
16000         DisplayBothClocks();
16001         DisplayMove(currentMove - 1);
16002         ClearHighlights();/*!! could figure this out*/
16003         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16004         SendToProgram("remove\n", &first);
16005         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16006         break;
16007
16008       case BeginningOfGame:
16009       default:
16010         break;
16011
16012       case IcsPlayingWhite:
16013       case IcsPlayingBlack:
16014         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16015             SendToICS(ics_prefix);
16016             SendToICS("takeback 2\n");
16017         } else {
16018             SendToICS(ics_prefix);
16019             SendToICS("takeback 1\n");
16020         }
16021         break;
16022     }
16023 }
16024
16025 void
16026 MoveNowEvent ()
16027 {
16028     ChessProgramState *cps;
16029
16030     switch (gameMode) {
16031       case MachinePlaysWhite:
16032         if (!WhiteOnMove(forwardMostMove)) {
16033             DisplayError(_("It is your turn"), 0);
16034             return;
16035         }
16036         cps = &first;
16037         break;
16038       case MachinePlaysBlack:
16039         if (WhiteOnMove(forwardMostMove)) {
16040             DisplayError(_("It is your turn"), 0);
16041             return;
16042         }
16043         cps = &first;
16044         break;
16045       case TwoMachinesPlay:
16046         if (WhiteOnMove(forwardMostMove) ==
16047             (first.twoMachinesColor[0] == 'w')) {
16048             cps = &first;
16049         } else {
16050             cps = &second;
16051         }
16052         break;
16053       case BeginningOfGame:
16054       default:
16055         return;
16056     }
16057     SendToProgram("?\n", cps);
16058 }
16059
16060 void
16061 TruncateGameEvent ()
16062 {
16063     EditGameEvent();
16064     if (gameMode != EditGame) return;
16065     TruncateGame();
16066 }
16067
16068 void
16069 TruncateGame ()
16070 {
16071     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16072     if (forwardMostMove > currentMove) {
16073         if (gameInfo.resultDetails != NULL) {
16074             free(gameInfo.resultDetails);
16075             gameInfo.resultDetails = NULL;
16076             gameInfo.result = GameUnfinished;
16077         }
16078         forwardMostMove = currentMove;
16079         HistorySet(parseList, backwardMostMove, forwardMostMove,
16080                    currentMove-1);
16081     }
16082 }
16083
16084 void
16085 HintEvent ()
16086 {
16087     if (appData.noChessProgram) return;
16088     switch (gameMode) {
16089       case MachinePlaysWhite:
16090         if (WhiteOnMove(forwardMostMove)) {
16091             DisplayError(_("Wait until your turn."), 0);
16092             return;
16093         }
16094         break;
16095       case BeginningOfGame:
16096       case MachinePlaysBlack:
16097         if (!WhiteOnMove(forwardMostMove)) {
16098             DisplayError(_("Wait until your turn."), 0);
16099             return;
16100         }
16101         break;
16102       default:
16103         DisplayError(_("No hint available"), 0);
16104         return;
16105     }
16106     SendToProgram("hint\n", &first);
16107     hintRequested = TRUE;
16108 }
16109
16110 int
16111 SaveSelected (FILE *g, int dummy, char *dummy2)
16112 {
16113     ListGame * lg = (ListGame *) gameList.head;
16114     int nItem, cnt=0;
16115     FILE *f;
16116
16117     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16118         DisplayError(_("Game list not loaded or empty"), 0);
16119         return 0;
16120     }
16121
16122     creatingBook = TRUE; // suppresses stuff during load game
16123
16124     /* Get list size */
16125     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16126         if(lg->position >= 0) { // selected?
16127             LoadGame(f, nItem, "", TRUE);
16128             SaveGamePGN2(g); // leaves g open
16129             cnt++; DoEvents();
16130         }
16131         lg = (ListGame *) lg->node.succ;
16132     }
16133
16134     fclose(g);
16135     creatingBook = FALSE;
16136
16137     return cnt;
16138 }
16139
16140 void
16141 CreateBookEvent ()
16142 {
16143     ListGame * lg = (ListGame *) gameList.head;
16144     FILE *f, *g;
16145     int nItem;
16146     static int secondTime = FALSE;
16147
16148     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16149         DisplayError(_("Game list not loaded or empty"), 0);
16150         return;
16151     }
16152
16153     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16154         fclose(g);
16155         secondTime++;
16156         DisplayNote(_("Book file exists! Try again for overwrite."));
16157         return;
16158     }
16159
16160     creatingBook = TRUE;
16161     secondTime = FALSE;
16162
16163     /* Get list size */
16164     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16165         if(lg->position >= 0) {
16166             LoadGame(f, nItem, "", TRUE);
16167             AddGameToBook(TRUE);
16168             DoEvents();
16169         }
16170         lg = (ListGame *) lg->node.succ;
16171     }
16172
16173     creatingBook = FALSE;
16174     FlushBook();
16175 }
16176
16177 void
16178 BookEvent ()
16179 {
16180     if (appData.noChessProgram) return;
16181     switch (gameMode) {
16182       case MachinePlaysWhite:
16183         if (WhiteOnMove(forwardMostMove)) {
16184             DisplayError(_("Wait until your turn."), 0);
16185             return;
16186         }
16187         break;
16188       case BeginningOfGame:
16189       case MachinePlaysBlack:
16190         if (!WhiteOnMove(forwardMostMove)) {
16191             DisplayError(_("Wait until your turn."), 0);
16192             return;
16193         }
16194         break;
16195       case EditPosition:
16196         EditPositionDone(TRUE);
16197         break;
16198       case TwoMachinesPlay:
16199         return;
16200       default:
16201         break;
16202     }
16203     SendToProgram("bk\n", &first);
16204     bookOutput[0] = NULLCHAR;
16205     bookRequested = TRUE;
16206 }
16207
16208 void
16209 AboutGameEvent ()
16210 {
16211     char *tags = PGNTags(&gameInfo);
16212     TagsPopUp(tags, CmailMsg());
16213     free(tags);
16214 }
16215
16216 /* end button procedures */
16217
16218 void
16219 PrintPosition (FILE *fp, int move)
16220 {
16221     int i, j;
16222
16223     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16224         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16225             char c = PieceToChar(boards[move][i][j]);
16226             fputc(c == 'x' ? '.' : c, fp);
16227             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16228         }
16229     }
16230     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16231       fprintf(fp, "white to play\n");
16232     else
16233       fprintf(fp, "black to play\n");
16234 }
16235
16236 void
16237 PrintOpponents (FILE *fp)
16238 {
16239     if (gameInfo.white != NULL) {
16240         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16241     } else {
16242         fprintf(fp, "\n");
16243     }
16244 }
16245
16246 /* Find last component of program's own name, using some heuristics */
16247 void
16248 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16249 {
16250     char *p, *q, c;
16251     int local = (strcmp(host, "localhost") == 0);
16252     while (!local && (p = strchr(prog, ';')) != NULL) {
16253         p++;
16254         while (*p == ' ') p++;
16255         prog = p;
16256     }
16257     if (*prog == '"' || *prog == '\'') {
16258         q = strchr(prog + 1, *prog);
16259     } else {
16260         q = strchr(prog, ' ');
16261     }
16262     if (q == NULL) q = prog + strlen(prog);
16263     p = q;
16264     while (p >= prog && *p != '/' && *p != '\\') p--;
16265     p++;
16266     if(p == prog && *p == '"') p++;
16267     c = *q; *q = 0;
16268     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16269     memcpy(buf, p, q - p);
16270     buf[q - p] = NULLCHAR;
16271     if (!local) {
16272         strcat(buf, "@");
16273         strcat(buf, host);
16274     }
16275 }
16276
16277 char *
16278 TimeControlTagValue ()
16279 {
16280     char buf[MSG_SIZ];
16281     if (!appData.clockMode) {
16282       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16283     } else if (movesPerSession > 0) {
16284       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16285     } else if (timeIncrement == 0) {
16286       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16287     } else {
16288       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16289     }
16290     return StrSave(buf);
16291 }
16292
16293 void
16294 SetGameInfo ()
16295 {
16296     /* This routine is used only for certain modes */
16297     VariantClass v = gameInfo.variant;
16298     ChessMove r = GameUnfinished;
16299     char *p = NULL;
16300
16301     if(keepInfo) return;
16302
16303     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16304         r = gameInfo.result;
16305         p = gameInfo.resultDetails;
16306         gameInfo.resultDetails = NULL;
16307     }
16308     ClearGameInfo(&gameInfo);
16309     gameInfo.variant = v;
16310
16311     switch (gameMode) {
16312       case MachinePlaysWhite:
16313         gameInfo.event = StrSave( appData.pgnEventHeader );
16314         gameInfo.site = StrSave(HostName());
16315         gameInfo.date = PGNDate();
16316         gameInfo.round = StrSave("-");
16317         gameInfo.white = StrSave(first.tidy);
16318         gameInfo.black = StrSave(UserName());
16319         gameInfo.timeControl = TimeControlTagValue();
16320         break;
16321
16322       case MachinePlaysBlack:
16323         gameInfo.event = StrSave( appData.pgnEventHeader );
16324         gameInfo.site = StrSave(HostName());
16325         gameInfo.date = PGNDate();
16326         gameInfo.round = StrSave("-");
16327         gameInfo.white = StrSave(UserName());
16328         gameInfo.black = StrSave(first.tidy);
16329         gameInfo.timeControl = TimeControlTagValue();
16330         break;
16331
16332       case TwoMachinesPlay:
16333         gameInfo.event = StrSave( appData.pgnEventHeader );
16334         gameInfo.site = StrSave(HostName());
16335         gameInfo.date = PGNDate();
16336         if (roundNr > 0) {
16337             char buf[MSG_SIZ];
16338             snprintf(buf, MSG_SIZ, "%d", roundNr);
16339             gameInfo.round = StrSave(buf);
16340         } else {
16341             gameInfo.round = StrSave("-");
16342         }
16343         if (first.twoMachinesColor[0] == 'w') {
16344             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16345             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16346         } else {
16347             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16348             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16349         }
16350         gameInfo.timeControl = TimeControlTagValue();
16351         break;
16352
16353       case EditGame:
16354         gameInfo.event = StrSave("Edited game");
16355         gameInfo.site = StrSave(HostName());
16356         gameInfo.date = PGNDate();
16357         gameInfo.round = StrSave("-");
16358         gameInfo.white = StrSave("-");
16359         gameInfo.black = StrSave("-");
16360         gameInfo.result = r;
16361         gameInfo.resultDetails = p;
16362         break;
16363
16364       case EditPosition:
16365         gameInfo.event = StrSave("Edited position");
16366         gameInfo.site = StrSave(HostName());
16367         gameInfo.date = PGNDate();
16368         gameInfo.round = StrSave("-");
16369         gameInfo.white = StrSave("-");
16370         gameInfo.black = StrSave("-");
16371         break;
16372
16373       case IcsPlayingWhite:
16374       case IcsPlayingBlack:
16375       case IcsObserving:
16376       case IcsExamining:
16377         break;
16378
16379       case PlayFromGameFile:
16380         gameInfo.event = StrSave("Game from non-PGN file");
16381         gameInfo.site = StrSave(HostName());
16382         gameInfo.date = PGNDate();
16383         gameInfo.round = StrSave("-");
16384         gameInfo.white = StrSave("?");
16385         gameInfo.black = StrSave("?");
16386         break;
16387
16388       default:
16389         break;
16390     }
16391 }
16392
16393 void
16394 ReplaceComment (int index, char *text)
16395 {
16396     int len;
16397     char *p;
16398     float score;
16399
16400     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16401        pvInfoList[index-1].depth == len &&
16402        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16403        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16404     while (*text == '\n') text++;
16405     len = strlen(text);
16406     while (len > 0 && text[len - 1] == '\n') len--;
16407
16408     if (commentList[index] != NULL)
16409       free(commentList[index]);
16410
16411     if (len == 0) {
16412         commentList[index] = NULL;
16413         return;
16414     }
16415   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16416       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16417       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16418     commentList[index] = (char *) malloc(len + 2);
16419     strncpy(commentList[index], text, len);
16420     commentList[index][len] = '\n';
16421     commentList[index][len + 1] = NULLCHAR;
16422   } else {
16423     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16424     char *p;
16425     commentList[index] = (char *) malloc(len + 7);
16426     safeStrCpy(commentList[index], "{\n", 3);
16427     safeStrCpy(commentList[index]+2, text, len+1);
16428     commentList[index][len+2] = NULLCHAR;
16429     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16430     strcat(commentList[index], "\n}\n");
16431   }
16432 }
16433
16434 void
16435 CrushCRs (char *text)
16436 {
16437   char *p = text;
16438   char *q = text;
16439   char ch;
16440
16441   do {
16442     ch = *p++;
16443     if (ch == '\r') continue;
16444     *q++ = ch;
16445   } while (ch != '\0');
16446 }
16447
16448 void
16449 AppendComment (int index, char *text, Boolean addBraces)
16450 /* addBraces  tells if we should add {} */
16451 {
16452     int oldlen, len;
16453     char *old;
16454
16455 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16456     if(addBraces == 3) addBraces = 0; else // force appending literally
16457     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16458
16459     CrushCRs(text);
16460     while (*text == '\n') text++;
16461     len = strlen(text);
16462     while (len > 0 && text[len - 1] == '\n') len--;
16463     text[len] = NULLCHAR;
16464
16465     if (len == 0) return;
16466
16467     if (commentList[index] != NULL) {
16468       Boolean addClosingBrace = addBraces;
16469         old = commentList[index];
16470         oldlen = strlen(old);
16471         while(commentList[index][oldlen-1] ==  '\n')
16472           commentList[index][--oldlen] = NULLCHAR;
16473         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16474         safeStrCpy(commentList[index], old, oldlen + len + 6);
16475         free(old);
16476         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16477         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16478           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16479           while (*text == '\n') { text++; len--; }
16480           commentList[index][--oldlen] = NULLCHAR;
16481       }
16482         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16483         else          strcat(commentList[index], "\n");
16484         strcat(commentList[index], text);
16485         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16486         else          strcat(commentList[index], "\n");
16487     } else {
16488         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16489         if(addBraces)
16490           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16491         else commentList[index][0] = NULLCHAR;
16492         strcat(commentList[index], text);
16493         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16494         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16495     }
16496 }
16497
16498 static char *
16499 FindStr (char * text, char * sub_text)
16500 {
16501     char * result = strstr( text, sub_text );
16502
16503     if( result != NULL ) {
16504         result += strlen( sub_text );
16505     }
16506
16507     return result;
16508 }
16509
16510 /* [AS] Try to extract PV info from PGN comment */
16511 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16512 char *
16513 GetInfoFromComment (int index, char * text)
16514 {
16515     char * sep = text, *p;
16516
16517     if( text != NULL && index > 0 ) {
16518         int score = 0;
16519         int depth = 0;
16520         int time = -1, sec = 0, deci;
16521         char * s_eval = FindStr( text, "[%eval " );
16522         char * s_emt = FindStr( text, "[%emt " );
16523 #if 0
16524         if( s_eval != NULL || s_emt != NULL ) {
16525 #else
16526         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16527 #endif
16528             /* New style */
16529             char delim;
16530
16531             if( s_eval != NULL ) {
16532                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16533                     return text;
16534                 }
16535
16536                 if( delim != ']' ) {
16537                     return text;
16538                 }
16539             }
16540
16541             if( s_emt != NULL ) {
16542             }
16543                 return text;
16544         }
16545         else {
16546             /* We expect something like: [+|-]nnn.nn/dd */
16547             int score_lo = 0;
16548
16549             if(*text != '{') return text; // [HGM] braces: must be normal comment
16550
16551             sep = strchr( text, '/' );
16552             if( sep == NULL || sep < (text+4) ) {
16553                 return text;
16554             }
16555
16556             p = text;
16557             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16558             if(p[1] == '(') { // comment starts with PV
16559                p = strchr(p, ')'); // locate end of PV
16560                if(p == NULL || sep < p+5) return text;
16561                // at this point we have something like "{(.*) +0.23/6 ..."
16562                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16563                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16564                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16565             }
16566             time = -1; sec = -1; deci = -1;
16567             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16568                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16569                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16570                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16571                 return text;
16572             }
16573
16574             if( score_lo < 0 || score_lo >= 100 ) {
16575                 return text;
16576             }
16577
16578             if(sec >= 0) time = 600*time + 10*sec; else
16579             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16580
16581             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16582
16583             /* [HGM] PV time: now locate end of PV info */
16584             while( *++sep >= '0' && *sep <= '9'); // strip depth
16585             if(time >= 0)
16586             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16587             if(sec >= 0)
16588             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16589             if(deci >= 0)
16590             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16591             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16592         }
16593
16594         if( depth <= 0 ) {
16595             return text;
16596         }
16597
16598         if( time < 0 ) {
16599             time = -1;
16600         }
16601
16602         pvInfoList[index-1].depth = depth;
16603         pvInfoList[index-1].score = score;
16604         pvInfoList[index-1].time  = 10*time; // centi-sec
16605         if(*sep == '}') *sep = 0; else *--sep = '{';
16606         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16607     }
16608     return sep;
16609 }
16610
16611 void
16612 SendToProgram (char *message, ChessProgramState *cps)
16613 {
16614     int count, outCount, error;
16615     char buf[MSG_SIZ];
16616
16617     if (cps->pr == NoProc) return;
16618     Attention(cps);
16619
16620     if (appData.debugMode) {
16621         TimeMark now;
16622         GetTimeMark(&now);
16623         fprintf(debugFP, "%ld >%-6s: %s",
16624                 SubtractTimeMarks(&now, &programStartTime),
16625                 cps->which, message);
16626         if(serverFP)
16627             fprintf(serverFP, "%ld >%-6s: %s",
16628                 SubtractTimeMarks(&now, &programStartTime),
16629                 cps->which, message), fflush(serverFP);
16630     }
16631
16632     count = strlen(message);
16633     outCount = OutputToProcess(cps->pr, message, count, &error);
16634     if (outCount < count && !exiting
16635                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16636       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16637       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16638         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16639             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16640                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16641                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16642                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16643             } else {
16644                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16645                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16646                 gameInfo.result = res;
16647             }
16648             gameInfo.resultDetails = StrSave(buf);
16649         }
16650         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16651         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16652     }
16653 }
16654
16655 void
16656 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16657 {
16658     char *end_str;
16659     char buf[MSG_SIZ];
16660     ChessProgramState *cps = (ChessProgramState *)closure;
16661
16662     if (isr != cps->isr) return; /* Killed intentionally */
16663     if (count <= 0) {
16664         if (count == 0) {
16665             RemoveInputSource(cps->isr);
16666             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16667                     _(cps->which), cps->program);
16668             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16669             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16670                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16671                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16672                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16673                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16674                 } else {
16675                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16676                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16677                     gameInfo.result = res;
16678                 }
16679                 gameInfo.resultDetails = StrSave(buf);
16680             }
16681             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16682             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16683         } else {
16684             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16685                     _(cps->which), cps->program);
16686             RemoveInputSource(cps->isr);
16687
16688             /* [AS] Program is misbehaving badly... kill it */
16689             if( count == -2 ) {
16690                 DestroyChildProcess( cps->pr, 9 );
16691                 cps->pr = NoProc;
16692             }
16693
16694             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16695         }
16696         return;
16697     }
16698
16699     if ((end_str = strchr(message, '\r')) != NULL)
16700       *end_str = NULLCHAR;
16701     if ((end_str = strchr(message, '\n')) != NULL)
16702       *end_str = NULLCHAR;
16703
16704     if (appData.debugMode) {
16705         TimeMark now; int print = 1;
16706         char *quote = ""; char c; int i;
16707
16708         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16709                 char start = message[0];
16710                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16711                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16712                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16713                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16714                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16715                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16716                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16717                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16718                    sscanf(message, "hint: %c", &c)!=1 &&
16719                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16720                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16721                     print = (appData.engineComments >= 2);
16722                 }
16723                 message[0] = start; // restore original message
16724         }
16725         if(print) {
16726                 GetTimeMark(&now);
16727                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16728                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16729                         quote,
16730                         message);
16731                 if(serverFP)
16732                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16733                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16734                         quote,
16735                         message), fflush(serverFP);
16736         }
16737     }
16738
16739     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16740     if (appData.icsEngineAnalyze) {
16741         if (strstr(message, "whisper") != NULL ||
16742              strstr(message, "kibitz") != NULL ||
16743             strstr(message, "tellics") != NULL) return;
16744     }
16745
16746     HandleMachineMove(message, cps);
16747 }
16748
16749
16750 void
16751 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16752 {
16753     char buf[MSG_SIZ];
16754     int seconds;
16755
16756     if( timeControl_2 > 0 ) {
16757         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16758             tc = timeControl_2;
16759         }
16760     }
16761     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16762     inc /= cps->timeOdds;
16763     st  /= cps->timeOdds;
16764
16765     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16766
16767     if (st > 0) {
16768       /* Set exact time per move, normally using st command */
16769       if (cps->stKludge) {
16770         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16771         seconds = st % 60;
16772         if (seconds == 0) {
16773           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16774         } else {
16775           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16776         }
16777       } else {
16778         snprintf(buf, MSG_SIZ, "st %d\n", st);
16779       }
16780     } else {
16781       /* Set conventional or incremental time control, using level command */
16782       if (seconds == 0) {
16783         /* Note old gnuchess bug -- minutes:seconds used to not work.
16784            Fixed in later versions, but still avoid :seconds
16785            when seconds is 0. */
16786         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16787       } else {
16788         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16789                  seconds, inc/1000.);
16790       }
16791     }
16792     SendToProgram(buf, cps);
16793
16794     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16795     /* Orthogonally, limit search to given depth */
16796     if (sd > 0) {
16797       if (cps->sdKludge) {
16798         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16799       } else {
16800         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16801       }
16802       SendToProgram(buf, cps);
16803     }
16804
16805     if(cps->nps >= 0) { /* [HGM] nps */
16806         if(cps->supportsNPS == FALSE)
16807           cps->nps = -1; // don't use if engine explicitly says not supported!
16808         else {
16809           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16810           SendToProgram(buf, cps);
16811         }
16812     }
16813 }
16814
16815 ChessProgramState *
16816 WhitePlayer ()
16817 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16818 {
16819     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16820        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16821         return &second;
16822     return &first;
16823 }
16824
16825 void
16826 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16827 {
16828     char message[MSG_SIZ];
16829     long time, otime;
16830
16831     /* Note: this routine must be called when the clocks are stopped
16832        or when they have *just* been set or switched; otherwise
16833        it will be off by the time since the current tick started.
16834     */
16835     if (machineWhite) {
16836         time = whiteTimeRemaining / 10;
16837         otime = blackTimeRemaining / 10;
16838     } else {
16839         time = blackTimeRemaining / 10;
16840         otime = whiteTimeRemaining / 10;
16841     }
16842     /* [HGM] translate opponent's time by time-odds factor */
16843     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16844
16845     if (time <= 0) time = 1;
16846     if (otime <= 0) otime = 1;
16847
16848     snprintf(message, MSG_SIZ, "time %ld\n", time);
16849     SendToProgram(message, cps);
16850
16851     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16852     SendToProgram(message, cps);
16853 }
16854
16855 char *
16856 EngineDefinedVariant (ChessProgramState *cps, int n)
16857 {   // return name of n-th unknown variant that engine supports
16858     static char buf[MSG_SIZ];
16859     char *p, *s = cps->variants;
16860     if(!s) return NULL;
16861     do { // parse string from variants feature
16862       VariantClass v;
16863         p = strchr(s, ',');
16864         if(p) *p = NULLCHAR;
16865       v = StringToVariant(s);
16866       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16867         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16868             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16869                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16870                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16871                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16872             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16873         }
16874         if(p) *p++ = ',';
16875         if(n < 0) return buf;
16876     } while(s = p);
16877     return NULL;
16878 }
16879
16880 int
16881 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16882 {
16883   char buf[MSG_SIZ];
16884   int len = strlen(name);
16885   int val;
16886
16887   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16888     (*p) += len + 1;
16889     sscanf(*p, "%d", &val);
16890     *loc = (val != 0);
16891     while (**p && **p != ' ')
16892       (*p)++;
16893     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16894     SendToProgram(buf, cps);
16895     return TRUE;
16896   }
16897   return FALSE;
16898 }
16899
16900 int
16901 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16902 {
16903   char buf[MSG_SIZ];
16904   int len = strlen(name);
16905   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16906     (*p) += len + 1;
16907     sscanf(*p, "%d", loc);
16908     while (**p && **p != ' ') (*p)++;
16909     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16910     SendToProgram(buf, cps);
16911     return TRUE;
16912   }
16913   return FALSE;
16914 }
16915
16916 int
16917 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16918 {
16919   char buf[MSG_SIZ];
16920   int len = strlen(name);
16921   if (strncmp((*p), name, len) == 0
16922       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16923     (*p) += len + 2;
16924     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16925     sscanf(*p, "%[^\"]", *loc);
16926     while (**p && **p != '\"') (*p)++;
16927     if (**p == '\"') (*p)++;
16928     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16929     SendToProgram(buf, cps);
16930     return TRUE;
16931   }
16932   return FALSE;
16933 }
16934
16935 int
16936 ParseOption (Option *opt, ChessProgramState *cps)
16937 // [HGM] options: process the string that defines an engine option, and determine
16938 // name, type, default value, and allowed value range
16939 {
16940         char *p, *q, buf[MSG_SIZ];
16941         int n, min = (-1)<<31, max = 1<<31, def;
16942
16943         if(p = strstr(opt->name, " -spin ")) {
16944             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16945             if(max < min) max = min; // enforce consistency
16946             if(def < min) def = min;
16947             if(def > max) def = max;
16948             opt->value = def;
16949             opt->min = min;
16950             opt->max = max;
16951             opt->type = Spin;
16952         } else if((p = strstr(opt->name, " -slider "))) {
16953             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16954             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16955             if(max < min) max = min; // enforce consistency
16956             if(def < min) def = min;
16957             if(def > max) def = max;
16958             opt->value = def;
16959             opt->min = min;
16960             opt->max = max;
16961             opt->type = Spin; // Slider;
16962         } else if((p = strstr(opt->name, " -string "))) {
16963             opt->textValue = p+9;
16964             opt->type = TextBox;
16965         } else if((p = strstr(opt->name, " -file "))) {
16966             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16967             opt->textValue = p+7;
16968             opt->type = FileName; // FileName;
16969         } else if((p = strstr(opt->name, " -path "))) {
16970             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16971             opt->textValue = p+7;
16972             opt->type = PathName; // PathName;
16973         } else if(p = strstr(opt->name, " -check ")) {
16974             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16975             opt->value = (def != 0);
16976             opt->type = CheckBox;
16977         } else if(p = strstr(opt->name, " -combo ")) {
16978             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16979             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16980             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16981             opt->value = n = 0;
16982             while(q = StrStr(q, " /// ")) {
16983                 n++; *q = 0;    // count choices, and null-terminate each of them
16984                 q += 5;
16985                 if(*q == '*') { // remember default, which is marked with * prefix
16986                     q++;
16987                     opt->value = n;
16988                 }
16989                 cps->comboList[cps->comboCnt++] = q;
16990             }
16991             cps->comboList[cps->comboCnt++] = NULL;
16992             opt->max = n + 1;
16993             opt->type = ComboBox;
16994         } else if(p = strstr(opt->name, " -button")) {
16995             opt->type = Button;
16996         } else if(p = strstr(opt->name, " -save")) {
16997             opt->type = SaveButton;
16998         } else return FALSE;
16999         *p = 0; // terminate option name
17000         // now look if the command-line options define a setting for this engine option.
17001         if(cps->optionSettings && cps->optionSettings[0])
17002             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17003         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17004           snprintf(buf, MSG_SIZ, "option %s", p);
17005                 if(p = strstr(buf, ",")) *p = 0;
17006                 if(q = strchr(buf, '=')) switch(opt->type) {
17007                     case ComboBox:
17008                         for(n=0; n<opt->max; n++)
17009                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17010                         break;
17011                     case TextBox:
17012                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17013                         break;
17014                     case Spin:
17015                     case CheckBox:
17016                         opt->value = atoi(q+1);
17017                     default:
17018                         break;
17019                 }
17020                 strcat(buf, "\n");
17021                 SendToProgram(buf, cps);
17022         }
17023         return TRUE;
17024 }
17025
17026 void
17027 FeatureDone (ChessProgramState *cps, int val)
17028 {
17029   DelayedEventCallback cb = GetDelayedEvent();
17030   if ((cb == InitBackEnd3 && cps == &first) ||
17031       (cb == SettingsMenuIfReady && cps == &second) ||
17032       (cb == LoadEngine) ||
17033       (cb == TwoMachinesEventIfReady)) {
17034     CancelDelayedEvent();
17035     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17036   }
17037   cps->initDone = val;
17038   if(val) cps->reload = FALSE;
17039 }
17040
17041 /* Parse feature command from engine */
17042 void
17043 ParseFeatures (char *args, ChessProgramState *cps)
17044 {
17045   char *p = args;
17046   char *q = NULL;
17047   int val;
17048   char buf[MSG_SIZ];
17049
17050   for (;;) {
17051     while (*p == ' ') p++;
17052     if (*p == NULLCHAR) return;
17053
17054     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17055     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17056     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17057     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17058     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17059     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17060     if (BoolFeature(&p, "reuse", &val, cps)) {
17061       /* Engine can disable reuse, but can't enable it if user said no */
17062       if (!val) cps->reuse = FALSE;
17063       continue;
17064     }
17065     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17066     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17067       if (gameMode == TwoMachinesPlay) {
17068         DisplayTwoMachinesTitle();
17069       } else {
17070         DisplayTitle("");
17071       }
17072       continue;
17073     }
17074     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17075     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17076     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17077     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17078     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17079     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17080     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17081     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17082     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17083     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17084     if (IntFeature(&p, "done", &val, cps)) {
17085       FeatureDone(cps, val);
17086       continue;
17087     }
17088     /* Added by Tord: */
17089     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17090     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17091     /* End of additions by Tord */
17092
17093     /* [HGM] added features: */
17094     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17095     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17096     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17097     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17098     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17099     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17100     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17101     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17102         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17103         FREE(cps->option[cps->nrOptions].name);
17104         cps->option[cps->nrOptions].name = q; q = NULL;
17105         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17106           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17107             SendToProgram(buf, cps);
17108             continue;
17109         }
17110         if(cps->nrOptions >= MAX_OPTIONS) {
17111             cps->nrOptions--;
17112             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17113             DisplayError(buf, 0);
17114         }
17115         continue;
17116     }
17117     /* End of additions by HGM */
17118
17119     /* unknown feature: complain and skip */
17120     q = p;
17121     while (*q && *q != '=') q++;
17122     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17123     SendToProgram(buf, cps);
17124     p = q;
17125     if (*p == '=') {
17126       p++;
17127       if (*p == '\"') {
17128         p++;
17129         while (*p && *p != '\"') p++;
17130         if (*p == '\"') p++;
17131       } else {
17132         while (*p && *p != ' ') p++;
17133       }
17134     }
17135   }
17136
17137 }
17138
17139 void
17140 PeriodicUpdatesEvent (int newState)
17141 {
17142     if (newState == appData.periodicUpdates)
17143       return;
17144
17145     appData.periodicUpdates=newState;
17146
17147     /* Display type changes, so update it now */
17148 //    DisplayAnalysis();
17149
17150     /* Get the ball rolling again... */
17151     if (newState) {
17152         AnalysisPeriodicEvent(1);
17153         StartAnalysisClock();
17154     }
17155 }
17156
17157 void
17158 PonderNextMoveEvent (int newState)
17159 {
17160     if (newState == appData.ponderNextMove) return;
17161     if (gameMode == EditPosition) EditPositionDone(TRUE);
17162     if (newState) {
17163         SendToProgram("hard\n", &first);
17164         if (gameMode == TwoMachinesPlay) {
17165             SendToProgram("hard\n", &second);
17166         }
17167     } else {
17168         SendToProgram("easy\n", &first);
17169         thinkOutput[0] = NULLCHAR;
17170         if (gameMode == TwoMachinesPlay) {
17171             SendToProgram("easy\n", &second);
17172         }
17173     }
17174     appData.ponderNextMove = newState;
17175 }
17176
17177 void
17178 NewSettingEvent (int option, int *feature, char *command, int value)
17179 {
17180     char buf[MSG_SIZ];
17181
17182     if (gameMode == EditPosition) EditPositionDone(TRUE);
17183     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17184     if(feature == NULL || *feature) SendToProgram(buf, &first);
17185     if (gameMode == TwoMachinesPlay) {
17186         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17187     }
17188 }
17189
17190 void
17191 ShowThinkingEvent ()
17192 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17193 {
17194     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17195     int newState = appData.showThinking
17196         // [HGM] thinking: other features now need thinking output as well
17197         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17198
17199     if (oldState == newState) return;
17200     oldState = newState;
17201     if (gameMode == EditPosition) EditPositionDone(TRUE);
17202     if (oldState) {
17203         SendToProgram("post\n", &first);
17204         if (gameMode == TwoMachinesPlay) {
17205             SendToProgram("post\n", &second);
17206         }
17207     } else {
17208         SendToProgram("nopost\n", &first);
17209         thinkOutput[0] = NULLCHAR;
17210         if (gameMode == TwoMachinesPlay) {
17211             SendToProgram("nopost\n", &second);
17212         }
17213     }
17214 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17215 }
17216
17217 void
17218 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17219 {
17220   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17221   if (pr == NoProc) return;
17222   AskQuestion(title, question, replyPrefix, pr);
17223 }
17224
17225 void
17226 TypeInEvent (char firstChar)
17227 {
17228     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17229         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17230         gameMode == AnalyzeMode || gameMode == EditGame ||
17231         gameMode == EditPosition || gameMode == IcsExamining ||
17232         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17233         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17234                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17235                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17236         gameMode == Training) PopUpMoveDialog(firstChar);
17237 }
17238
17239 void
17240 TypeInDoneEvent (char *move)
17241 {
17242         Board board;
17243         int n, fromX, fromY, toX, toY;
17244         char promoChar;
17245         ChessMove moveType;
17246
17247         // [HGM] FENedit
17248         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17249                 EditPositionPasteFEN(move);
17250                 return;
17251         }
17252         // [HGM] movenum: allow move number to be typed in any mode
17253         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17254           ToNrEvent(2*n-1);
17255           return;
17256         }
17257         // undocumented kludge: allow command-line option to be typed in!
17258         // (potentially fatal, and does not implement the effect of the option.)
17259         // should only be used for options that are values on which future decisions will be made,
17260         // and definitely not on options that would be used during initialization.
17261         if(strstr(move, "!!! -") == move) {
17262             ParseArgsFromString(move+4);
17263             return;
17264         }
17265
17266       if (gameMode != EditGame && currentMove != forwardMostMove &&
17267         gameMode != Training) {
17268         DisplayMoveError(_("Displayed move is not current"));
17269       } else {
17270         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17271           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17272         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17273         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17274           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17275           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17276         } else {
17277           DisplayMoveError(_("Could not parse move"));
17278         }
17279       }
17280 }
17281
17282 void
17283 DisplayMove (int moveNumber)
17284 {
17285     char message[MSG_SIZ];
17286     char res[MSG_SIZ];
17287     char cpThinkOutput[MSG_SIZ];
17288
17289     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17290
17291     if (moveNumber == forwardMostMove - 1 ||
17292         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17293
17294         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17295
17296         if (strchr(cpThinkOutput, '\n')) {
17297             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17298         }
17299     } else {
17300         *cpThinkOutput = NULLCHAR;
17301     }
17302
17303     /* [AS] Hide thinking from human user */
17304     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17305         *cpThinkOutput = NULLCHAR;
17306         if( thinkOutput[0] != NULLCHAR ) {
17307             int i;
17308
17309             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17310                 cpThinkOutput[i] = '.';
17311             }
17312             cpThinkOutput[i] = NULLCHAR;
17313             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17314         }
17315     }
17316
17317     if (moveNumber == forwardMostMove - 1 &&
17318         gameInfo.resultDetails != NULL) {
17319         if (gameInfo.resultDetails[0] == NULLCHAR) {
17320           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17321         } else {
17322           snprintf(res, MSG_SIZ, " {%s} %s",
17323                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17324         }
17325     } else {
17326         res[0] = NULLCHAR;
17327     }
17328
17329     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17330         DisplayMessage(res, cpThinkOutput);
17331     } else {
17332       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17333                 WhiteOnMove(moveNumber) ? " " : ".. ",
17334                 parseList[moveNumber], res);
17335         DisplayMessage(message, cpThinkOutput);
17336     }
17337 }
17338
17339 void
17340 DisplayComment (int moveNumber, char *text)
17341 {
17342     char title[MSG_SIZ];
17343
17344     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17345       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17346     } else {
17347       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17348               WhiteOnMove(moveNumber) ? " " : ".. ",
17349               parseList[moveNumber]);
17350     }
17351     if (text != NULL && (appData.autoDisplayComment || commentUp))
17352         CommentPopUp(title, text);
17353 }
17354
17355 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17356  * might be busy thinking or pondering.  It can be omitted if your
17357  * gnuchess is configured to stop thinking immediately on any user
17358  * input.  However, that gnuchess feature depends on the FIONREAD
17359  * ioctl, which does not work properly on some flavors of Unix.
17360  */
17361 void
17362 Attention (ChessProgramState *cps)
17363 {
17364 #if ATTENTION
17365     if (!cps->useSigint) return;
17366     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17367     switch (gameMode) {
17368       case MachinePlaysWhite:
17369       case MachinePlaysBlack:
17370       case TwoMachinesPlay:
17371       case IcsPlayingWhite:
17372       case IcsPlayingBlack:
17373       case AnalyzeMode:
17374       case AnalyzeFile:
17375         /* Skip if we know it isn't thinking */
17376         if (!cps->maybeThinking) return;
17377         if (appData.debugMode)
17378           fprintf(debugFP, "Interrupting %s\n", cps->which);
17379         InterruptChildProcess(cps->pr);
17380         cps->maybeThinking = FALSE;
17381         break;
17382       default:
17383         break;
17384     }
17385 #endif /*ATTENTION*/
17386 }
17387
17388 int
17389 CheckFlags ()
17390 {
17391     if (whiteTimeRemaining <= 0) {
17392         if (!whiteFlag) {
17393             whiteFlag = TRUE;
17394             if (appData.icsActive) {
17395                 if (appData.autoCallFlag &&
17396                     gameMode == IcsPlayingBlack && !blackFlag) {
17397                   SendToICS(ics_prefix);
17398                   SendToICS("flag\n");
17399                 }
17400             } else {
17401                 if (blackFlag) {
17402                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17403                 } else {
17404                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17405                     if (appData.autoCallFlag) {
17406                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17407                         return TRUE;
17408                     }
17409                 }
17410             }
17411         }
17412     }
17413     if (blackTimeRemaining <= 0) {
17414         if (!blackFlag) {
17415             blackFlag = TRUE;
17416             if (appData.icsActive) {
17417                 if (appData.autoCallFlag &&
17418                     gameMode == IcsPlayingWhite && !whiteFlag) {
17419                   SendToICS(ics_prefix);
17420                   SendToICS("flag\n");
17421                 }
17422             } else {
17423                 if (whiteFlag) {
17424                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17425                 } else {
17426                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17427                     if (appData.autoCallFlag) {
17428                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17429                         return TRUE;
17430                     }
17431                 }
17432             }
17433         }
17434     }
17435     return FALSE;
17436 }
17437
17438 void
17439 CheckTimeControl ()
17440 {
17441     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17442         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17443
17444     /*
17445      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17446      */
17447     if ( !WhiteOnMove(forwardMostMove) ) {
17448         /* White made time control */
17449         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17450         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17451         /* [HGM] time odds: correct new time quota for time odds! */
17452                                             / WhitePlayer()->timeOdds;
17453         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17454     } else {
17455         lastBlack -= blackTimeRemaining;
17456         /* Black made time control */
17457         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17458                                             / WhitePlayer()->other->timeOdds;
17459         lastWhite = whiteTimeRemaining;
17460     }
17461 }
17462
17463 void
17464 DisplayBothClocks ()
17465 {
17466     int wom = gameMode == EditPosition ?
17467       !blackPlaysFirst : WhiteOnMove(currentMove);
17468     DisplayWhiteClock(whiteTimeRemaining, wom);
17469     DisplayBlackClock(blackTimeRemaining, !wom);
17470 }
17471
17472
17473 /* Timekeeping seems to be a portability nightmare.  I think everyone
17474    has ftime(), but I'm really not sure, so I'm including some ifdefs
17475    to use other calls if you don't.  Clocks will be less accurate if
17476    you have neither ftime nor gettimeofday.
17477 */
17478
17479 /* VS 2008 requires the #include outside of the function */
17480 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17481 #include <sys/timeb.h>
17482 #endif
17483
17484 /* Get the current time as a TimeMark */
17485 void
17486 GetTimeMark (TimeMark *tm)
17487 {
17488 #if HAVE_GETTIMEOFDAY
17489
17490     struct timeval timeVal;
17491     struct timezone timeZone;
17492
17493     gettimeofday(&timeVal, &timeZone);
17494     tm->sec = (long) timeVal.tv_sec;
17495     tm->ms = (int) (timeVal.tv_usec / 1000L);
17496
17497 #else /*!HAVE_GETTIMEOFDAY*/
17498 #if HAVE_FTIME
17499
17500 // include <sys/timeb.h> / moved to just above start of function
17501     struct timeb timeB;
17502
17503     ftime(&timeB);
17504     tm->sec = (long) timeB.time;
17505     tm->ms = (int) timeB.millitm;
17506
17507 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17508     tm->sec = (long) time(NULL);
17509     tm->ms = 0;
17510 #endif
17511 #endif
17512 }
17513
17514 /* Return the difference in milliseconds between two
17515    time marks.  We assume the difference will fit in a long!
17516 */
17517 long
17518 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17519 {
17520     return 1000L*(tm2->sec - tm1->sec) +
17521            (long) (tm2->ms - tm1->ms);
17522 }
17523
17524
17525 /*
17526  * Code to manage the game clocks.
17527  *
17528  * In tournament play, black starts the clock and then white makes a move.
17529  * We give the human user a slight advantage if he is playing white---the
17530  * clocks don't run until he makes his first move, so it takes zero time.
17531  * Also, we don't account for network lag, so we could get out of sync
17532  * with GNU Chess's clock -- but then, referees are always right.
17533  */
17534
17535 static TimeMark tickStartTM;
17536 static long intendedTickLength;
17537
17538 long
17539 NextTickLength (long timeRemaining)
17540 {
17541     long nominalTickLength, nextTickLength;
17542
17543     if (timeRemaining > 0L && timeRemaining <= 10000L)
17544       nominalTickLength = 100L;
17545     else
17546       nominalTickLength = 1000L;
17547     nextTickLength = timeRemaining % nominalTickLength;
17548     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17549
17550     return nextTickLength;
17551 }
17552
17553 /* Adjust clock one minute up or down */
17554 void
17555 AdjustClock (Boolean which, int dir)
17556 {
17557     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17558     if(which) blackTimeRemaining += 60000*dir;
17559     else      whiteTimeRemaining += 60000*dir;
17560     DisplayBothClocks();
17561     adjustedClock = TRUE;
17562 }
17563
17564 /* Stop clocks and reset to a fresh time control */
17565 void
17566 ResetClocks ()
17567 {
17568     (void) StopClockTimer();
17569     if (appData.icsActive) {
17570         whiteTimeRemaining = blackTimeRemaining = 0;
17571     } else if (searchTime) {
17572         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17573         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17574     } else { /* [HGM] correct new time quote for time odds */
17575         whiteTC = blackTC = fullTimeControlString;
17576         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17577         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17578     }
17579     if (whiteFlag || blackFlag) {
17580         DisplayTitle("");
17581         whiteFlag = blackFlag = FALSE;
17582     }
17583     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17584     DisplayBothClocks();
17585     adjustedClock = FALSE;
17586 }
17587
17588 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17589
17590 /* Decrement running clock by amount of time that has passed */
17591 void
17592 DecrementClocks ()
17593 {
17594     long timeRemaining;
17595     long lastTickLength, fudge;
17596     TimeMark now;
17597
17598     if (!appData.clockMode) return;
17599     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17600
17601     GetTimeMark(&now);
17602
17603     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17604
17605     /* Fudge if we woke up a little too soon */
17606     fudge = intendedTickLength - lastTickLength;
17607     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17608
17609     if (WhiteOnMove(forwardMostMove)) {
17610         if(whiteNPS >= 0) lastTickLength = 0;
17611         timeRemaining = whiteTimeRemaining -= lastTickLength;
17612         if(timeRemaining < 0 && !appData.icsActive) {
17613             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17614             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17615                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17616                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17617             }
17618         }
17619         DisplayWhiteClock(whiteTimeRemaining - fudge,
17620                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17621     } else {
17622         if(blackNPS >= 0) lastTickLength = 0;
17623         timeRemaining = blackTimeRemaining -= lastTickLength;
17624         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17625             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17626             if(suddenDeath) {
17627                 blackStartMove = forwardMostMove;
17628                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17629             }
17630         }
17631         DisplayBlackClock(blackTimeRemaining - fudge,
17632                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17633     }
17634     if (CheckFlags()) return;
17635
17636     if(twoBoards) { // count down secondary board's clocks as well
17637         activePartnerTime -= lastTickLength;
17638         partnerUp = 1;
17639         if(activePartner == 'W')
17640             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17641         else
17642             DisplayBlackClock(activePartnerTime, TRUE);
17643         partnerUp = 0;
17644     }
17645
17646     tickStartTM = now;
17647     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17648     StartClockTimer(intendedTickLength);
17649
17650     /* if the time remaining has fallen below the alarm threshold, sound the
17651      * alarm. if the alarm has sounded and (due to a takeback or time control
17652      * with increment) the time remaining has increased to a level above the
17653      * threshold, reset the alarm so it can sound again.
17654      */
17655
17656     if (appData.icsActive && appData.icsAlarm) {
17657
17658         /* make sure we are dealing with the user's clock */
17659         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17660                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17661            )) return;
17662
17663         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17664             alarmSounded = FALSE;
17665         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17666             PlayAlarmSound();
17667             alarmSounded = TRUE;
17668         }
17669     }
17670 }
17671
17672
17673 /* A player has just moved, so stop the previously running
17674    clock and (if in clock mode) start the other one.
17675    We redisplay both clocks in case we're in ICS mode, because
17676    ICS gives us an update to both clocks after every move.
17677    Note that this routine is called *after* forwardMostMove
17678    is updated, so the last fractional tick must be subtracted
17679    from the color that is *not* on move now.
17680 */
17681 void
17682 SwitchClocks (int newMoveNr)
17683 {
17684     long lastTickLength;
17685     TimeMark now;
17686     int flagged = FALSE;
17687
17688     GetTimeMark(&now);
17689
17690     if (StopClockTimer() && appData.clockMode) {
17691         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17692         if (!WhiteOnMove(forwardMostMove)) {
17693             if(blackNPS >= 0) lastTickLength = 0;
17694             blackTimeRemaining -= lastTickLength;
17695            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17696 //         if(pvInfoList[forwardMostMove].time == -1)
17697                  pvInfoList[forwardMostMove].time =               // use GUI time
17698                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17699         } else {
17700            if(whiteNPS >= 0) lastTickLength = 0;
17701            whiteTimeRemaining -= lastTickLength;
17702            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17703 //         if(pvInfoList[forwardMostMove].time == -1)
17704                  pvInfoList[forwardMostMove].time =
17705                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17706         }
17707         flagged = CheckFlags();
17708     }
17709     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17710     CheckTimeControl();
17711
17712     if (flagged || !appData.clockMode) return;
17713
17714     switch (gameMode) {
17715       case MachinePlaysBlack:
17716       case MachinePlaysWhite:
17717       case BeginningOfGame:
17718         if (pausing) return;
17719         break;
17720
17721       case EditGame:
17722       case PlayFromGameFile:
17723       case IcsExamining:
17724         return;
17725
17726       default:
17727         break;
17728     }
17729
17730     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17731         if(WhiteOnMove(forwardMostMove))
17732              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17733         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17734     }
17735
17736     tickStartTM = now;
17737     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17738       whiteTimeRemaining : blackTimeRemaining);
17739     StartClockTimer(intendedTickLength);
17740 }
17741
17742
17743 /* Stop both clocks */
17744 void
17745 StopClocks ()
17746 {
17747     long lastTickLength;
17748     TimeMark now;
17749
17750     if (!StopClockTimer()) return;
17751     if (!appData.clockMode) return;
17752
17753     GetTimeMark(&now);
17754
17755     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17756     if (WhiteOnMove(forwardMostMove)) {
17757         if(whiteNPS >= 0) lastTickLength = 0;
17758         whiteTimeRemaining -= lastTickLength;
17759         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17760     } else {
17761         if(blackNPS >= 0) lastTickLength = 0;
17762         blackTimeRemaining -= lastTickLength;
17763         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17764     }
17765     CheckFlags();
17766 }
17767
17768 /* Start clock of player on move.  Time may have been reset, so
17769    if clock is already running, stop and restart it. */
17770 void
17771 StartClocks ()
17772 {
17773     (void) StopClockTimer(); /* in case it was running already */
17774     DisplayBothClocks();
17775     if (CheckFlags()) return;
17776
17777     if (!appData.clockMode) return;
17778     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17779
17780     GetTimeMark(&tickStartTM);
17781     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17782       whiteTimeRemaining : blackTimeRemaining);
17783
17784    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17785     whiteNPS = blackNPS = -1;
17786     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17787        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17788         whiteNPS = first.nps;
17789     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17790        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17791         blackNPS = first.nps;
17792     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17793         whiteNPS = second.nps;
17794     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17795         blackNPS = second.nps;
17796     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17797
17798     StartClockTimer(intendedTickLength);
17799 }
17800
17801 char *
17802 TimeString (long ms)
17803 {
17804     long second, minute, hour, day;
17805     char *sign = "";
17806     static char buf[32];
17807
17808     if (ms > 0 && ms <= 9900) {
17809       /* convert milliseconds to tenths, rounding up */
17810       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17811
17812       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17813       return buf;
17814     }
17815
17816     /* convert milliseconds to seconds, rounding up */
17817     /* use floating point to avoid strangeness of integer division
17818        with negative dividends on many machines */
17819     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17820
17821     if (second < 0) {
17822         sign = "-";
17823         second = -second;
17824     }
17825
17826     day = second / (60 * 60 * 24);
17827     second = second % (60 * 60 * 24);
17828     hour = second / (60 * 60);
17829     second = second % (60 * 60);
17830     minute = second / 60;
17831     second = second % 60;
17832
17833     if (day > 0)
17834       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17835               sign, day, hour, minute, second);
17836     else if (hour > 0)
17837       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17838     else
17839       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17840
17841     return buf;
17842 }
17843
17844
17845 /*
17846  * This is necessary because some C libraries aren't ANSI C compliant yet.
17847  */
17848 char *
17849 StrStr (char *string, char *match)
17850 {
17851     int i, length;
17852
17853     length = strlen(match);
17854
17855     for (i = strlen(string) - length; i >= 0; i--, string++)
17856       if (!strncmp(match, string, length))
17857         return string;
17858
17859     return NULL;
17860 }
17861
17862 char *
17863 StrCaseStr (char *string, char *match)
17864 {
17865     int i, j, length;
17866
17867     length = strlen(match);
17868
17869     for (i = strlen(string) - length; i >= 0; i--, string++) {
17870         for (j = 0; j < length; j++) {
17871             if (ToLower(match[j]) != ToLower(string[j]))
17872               break;
17873         }
17874         if (j == length) return string;
17875     }
17876
17877     return NULL;
17878 }
17879
17880 #ifndef _amigados
17881 int
17882 StrCaseCmp (char *s1, char *s2)
17883 {
17884     char c1, c2;
17885
17886     for (;;) {
17887         c1 = ToLower(*s1++);
17888         c2 = ToLower(*s2++);
17889         if (c1 > c2) return 1;
17890         if (c1 < c2) return -1;
17891         if (c1 == NULLCHAR) return 0;
17892     }
17893 }
17894
17895
17896 int
17897 ToLower (int c)
17898 {
17899     return isupper(c) ? tolower(c) : c;
17900 }
17901
17902
17903 int
17904 ToUpper (int c)
17905 {
17906     return islower(c) ? toupper(c) : c;
17907 }
17908 #endif /* !_amigados    */
17909
17910 char *
17911 StrSave (char *s)
17912 {
17913   char *ret;
17914
17915   if ((ret = (char *) malloc(strlen(s) + 1)))
17916     {
17917       safeStrCpy(ret, s, strlen(s)+1);
17918     }
17919   return ret;
17920 }
17921
17922 char *
17923 StrSavePtr (char *s, char **savePtr)
17924 {
17925     if (*savePtr) {
17926         free(*savePtr);
17927     }
17928     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17929       safeStrCpy(*savePtr, s, strlen(s)+1);
17930     }
17931     return(*savePtr);
17932 }
17933
17934 char *
17935 PGNDate ()
17936 {
17937     time_t clock;
17938     struct tm *tm;
17939     char buf[MSG_SIZ];
17940
17941     clock = time((time_t *)NULL);
17942     tm = localtime(&clock);
17943     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17944             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17945     return StrSave(buf);
17946 }
17947
17948
17949 char *
17950 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17951 {
17952     int i, j, fromX, fromY, toX, toY;
17953     int whiteToPlay, haveRights = nrCastlingRights;
17954     char buf[MSG_SIZ];
17955     char *p, *q;
17956     int emptycount;
17957     ChessSquare piece;
17958
17959     whiteToPlay = (gameMode == EditPosition) ?
17960       !blackPlaysFirst : (move % 2 == 0);
17961     p = buf;
17962
17963     /* Piece placement data */
17964     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17965         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17966         emptycount = 0;
17967         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17968             if (boards[move][i][j] == EmptySquare) {
17969                 emptycount++;
17970             } else { ChessSquare piece = boards[move][i][j];
17971                 if (emptycount > 0) {
17972                     if(emptycount<10) /* [HGM] can be >= 10 */
17973                         *p++ = '0' + emptycount;
17974                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17975                     emptycount = 0;
17976                 }
17977                 if(PieceToChar(piece) == '+') {
17978                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17979                     *p++ = '+';
17980                     piece = (ChessSquare)(CHUDEMOTED piece);
17981                 }
17982                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17983                 if(*p = PieceSuffix(piece)) p++;
17984                 if(p[-1] == '~') {
17985                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17986                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17987                     *p++ = '~';
17988                 }
17989             }
17990         }
17991         if (emptycount > 0) {
17992             if(emptycount<10) /* [HGM] can be >= 10 */
17993                 *p++ = '0' + emptycount;
17994             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17995             emptycount = 0;
17996         }
17997         *p++ = '/';
17998     }
17999     *(p - 1) = ' ';
18000
18001     /* [HGM] print Crazyhouse or Shogi holdings */
18002     if( gameInfo.holdingsWidth ) {
18003         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18004         q = p;
18005         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18006             piece = boards[move][i][BOARD_WIDTH-1];
18007             if( piece != EmptySquare )
18008               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18009                   *p++ = PieceToChar(piece);
18010         }
18011         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18012             piece = boards[move][BOARD_HEIGHT-i-1][0];
18013             if( piece != EmptySquare )
18014               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18015                   *p++ = PieceToChar(piece);
18016         }
18017
18018         if( q == p ) *p++ = '-';
18019         *p++ = ']';
18020         *p++ = ' ';
18021     }
18022
18023     /* Active color */
18024     *p++ = whiteToPlay ? 'w' : 'b';
18025     *p++ = ' ';
18026
18027   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18028     haveRights = 0; q = p;
18029     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18030       piece = boards[move][0][i];
18031       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18032         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18033       }
18034     }
18035     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18036       piece = boards[move][BOARD_HEIGHT-1][i];
18037       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18038         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18039       }
18040     }
18041     if(p == q) *p++ = '-';
18042     *p++ = ' ';
18043   }
18044
18045   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18046     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18047   } else {
18048   if(haveRights) {
18049      int handW=0, handB=0;
18050      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18051         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18052         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18053      }
18054      q = p;
18055      if(appData.fischerCastling) {
18056         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18057            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18058                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18059         } else {
18060        /* [HGM] write directly from rights */
18061            if(boards[move][CASTLING][2] != NoRights &&
18062               boards[move][CASTLING][0] != NoRights   )
18063                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18064            if(boards[move][CASTLING][2] != NoRights &&
18065               boards[move][CASTLING][1] != NoRights   )
18066                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18067         }
18068         if(handB) {
18069            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18070                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18071         } else {
18072            if(boards[move][CASTLING][5] != NoRights &&
18073               boards[move][CASTLING][3] != NoRights   )
18074                 *p++ = boards[move][CASTLING][3] + AAA;
18075            if(boards[move][CASTLING][5] != NoRights &&
18076               boards[move][CASTLING][4] != NoRights   )
18077                 *p++ = boards[move][CASTLING][4] + AAA;
18078         }
18079      } else {
18080
18081         /* [HGM] write true castling rights */
18082         if( nrCastlingRights == 6 ) {
18083             int q, k=0;
18084             if(boards[move][CASTLING][0] != NoRights &&
18085                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18086             q = (boards[move][CASTLING][1] != NoRights &&
18087                  boards[move][CASTLING][2] != NoRights  );
18088             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18089                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18090                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18091                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18092             }
18093             if(q) *p++ = 'Q';
18094             k = 0;
18095             if(boards[move][CASTLING][3] != NoRights &&
18096                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18097             q = (boards[move][CASTLING][4] != NoRights &&
18098                  boards[move][CASTLING][5] != NoRights  );
18099             if(handB) {
18100                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18101                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18102                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18103             }
18104             if(q) *p++ = 'q';
18105         }
18106      }
18107      if (q == p) *p++ = '-'; /* No castling rights */
18108      *p++ = ' ';
18109   }
18110
18111   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18112      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18113      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18114     /* En passant target square */
18115     if (move > backwardMostMove) {
18116         fromX = moveList[move - 1][0] - AAA;
18117         fromY = moveList[move - 1][1] - ONE;
18118         toX = moveList[move - 1][2] - AAA;
18119         toY = moveList[move - 1][3] - ONE;
18120         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18121             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18122             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18123             fromX == toX) {
18124             /* 2-square pawn move just happened */
18125             *p++ = toX + AAA;
18126             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18127         } else {
18128             *p++ = '-';
18129         }
18130     } else if(move == backwardMostMove) {
18131         // [HGM] perhaps we should always do it like this, and forget the above?
18132         if((signed char)boards[move][EP_STATUS] >= 0) {
18133             *p++ = boards[move][EP_STATUS] + AAA;
18134             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18135         } else {
18136             *p++ = '-';
18137         }
18138     } else {
18139         *p++ = '-';
18140     }
18141     *p++ = ' ';
18142   }
18143   }
18144
18145     if(moveCounts)
18146     {   int i = 0, j=move;
18147
18148         /* [HGM] find reversible plies */
18149         if (appData.debugMode) { int k;
18150             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18151             for(k=backwardMostMove; k<=forwardMostMove; k++)
18152                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18153
18154         }
18155
18156         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18157         if( j == backwardMostMove ) i += initialRulePlies;
18158         sprintf(p, "%d ", i);
18159         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18160
18161         /* Fullmove number */
18162         sprintf(p, "%d", (move / 2) + 1);
18163     } else *--p = NULLCHAR;
18164
18165     return StrSave(buf);
18166 }
18167
18168 Boolean
18169 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18170 {
18171     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18172     char *p, c;
18173     int emptycount, virgin[BOARD_FILES];
18174     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18175
18176     p = fen;
18177
18178     /* Piece placement data */
18179     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18180         j = 0;
18181         for (;;) {
18182             if (*p == '/' || *p == ' ' || *p == '[' ) {
18183                 if(j > w) w = j;
18184                 emptycount = gameInfo.boardWidth - j;
18185                 while (emptycount--)
18186                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18187                 if (*p == '/') p++;
18188                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18189                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18190                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18191                     }
18192                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18193                 }
18194                 break;
18195 #if(BOARD_FILES >= 10)*0
18196             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18197                 p++; emptycount=10;
18198                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18199                 while (emptycount--)
18200                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18201 #endif
18202             } else if (*p == '*') {
18203                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18204             } else if (isdigit(*p)) {
18205                 emptycount = *p++ - '0';
18206                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18207                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18208                 while (emptycount--)
18209                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18210             } else if (*p == '<') {
18211                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18212                 else if (i != 0 || !shuffle) return FALSE;
18213                 p++;
18214             } else if (shuffle && *p == '>') {
18215                 p++; // for now ignore closing shuffle range, and assume rank-end
18216             } else if (*p == '?') {
18217                 if (j >= gameInfo.boardWidth) return FALSE;
18218                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18219                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18220             } else if (*p == '+' || isalpha(*p)) {
18221                 char *q, *s = SUFFIXES;
18222                 if (j >= gameInfo.boardWidth) return FALSE;
18223                 if(*p=='+') {
18224                     char c = *++p;
18225                     if(q = strchr(s, p[1])) p++;
18226                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18227                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18228                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18229                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18230                 } else {
18231                     char c = *p++;
18232                     if(q = strchr(s, *p)) p++;
18233                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18234                 }
18235
18236                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18237                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18238                     piece = (ChessSquare) (PROMOTED piece);
18239                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18240                     p++;
18241                 }
18242                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18243                 if(piece == king) wKingRank = i;
18244                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18245             } else {
18246                 return FALSE;
18247             }
18248         }
18249     }
18250     while (*p == '/' || *p == ' ') p++;
18251
18252     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18253
18254     /* [HGM] by default clear Crazyhouse holdings, if present */
18255     if(gameInfo.holdingsWidth) {
18256        for(i=0; i<BOARD_HEIGHT; i++) {
18257            board[i][0]             = EmptySquare; /* black holdings */
18258            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18259            board[i][1]             = (ChessSquare) 0; /* black counts */
18260            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18261        }
18262     }
18263
18264     /* [HGM] look for Crazyhouse holdings here */
18265     while(*p==' ') p++;
18266     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18267         int swap=0, wcnt=0, bcnt=0;
18268         if(*p == '[') p++;
18269         if(*p == '<') swap++, p++;
18270         if(*p == '-' ) p++; /* empty holdings */ else {
18271             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18272             /* if we would allow FEN reading to set board size, we would   */
18273             /* have to add holdings and shift the board read so far here   */
18274             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18275                 p++;
18276                 if((int) piece >= (int) BlackPawn ) {
18277                     i = (int)piece - (int)BlackPawn;
18278                     i = PieceToNumber((ChessSquare)i);
18279                     if( i >= gameInfo.holdingsSize ) return FALSE;
18280                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18281                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18282                     bcnt++;
18283                 } else {
18284                     i = (int)piece - (int)WhitePawn;
18285                     i = PieceToNumber((ChessSquare)i);
18286                     if( i >= gameInfo.holdingsSize ) return FALSE;
18287                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18288                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18289                     wcnt++;
18290                 }
18291             }
18292             if(subst) { // substitute back-rank question marks by holdings pieces
18293                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18294                     int k, m, n = bcnt + 1;
18295                     if(board[0][j] == ClearBoard) {
18296                         if(!wcnt) return FALSE;
18297                         n = rand() % wcnt;
18298                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18299                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18300                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18301                             break;
18302                         }
18303                     }
18304                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18305                         if(!bcnt) return FALSE;
18306                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18307                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18308                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18309                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18310                             break;
18311                         }
18312                     }
18313                 }
18314                 subst = 0;
18315             }
18316         }
18317         if(*p == ']') p++;
18318     }
18319
18320     if(subst) return FALSE; // substitution requested, but no holdings
18321
18322     while(*p == ' ') p++;
18323
18324     /* Active color */
18325     c = *p++;
18326     if(appData.colorNickNames) {
18327       if( c == appData.colorNickNames[0] ) c = 'w'; else
18328       if( c == appData.colorNickNames[1] ) c = 'b';
18329     }
18330     switch (c) {
18331       case 'w':
18332         *blackPlaysFirst = FALSE;
18333         break;
18334       case 'b':
18335         *blackPlaysFirst = TRUE;
18336         break;
18337       default:
18338         return FALSE;
18339     }
18340
18341     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18342     /* return the extra info in global variiables             */
18343
18344     while(*p==' ') p++;
18345
18346     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18347         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18348         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18349     }
18350
18351     /* set defaults in case FEN is incomplete */
18352     board[EP_STATUS] = EP_UNKNOWN;
18353     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18354     for(i=0; i<nrCastlingRights; i++ ) {
18355         board[CASTLING][i] =
18356             appData.fischerCastling ? NoRights : initialRights[i];
18357     }   /* assume possible unless obviously impossible */
18358     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18359     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18360     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18361                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18362     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18363     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18364     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18365                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18366     FENrulePlies = 0;
18367
18368     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18369       char *q = p;
18370       int w=0, b=0;
18371       while(isalpha(*p)) {
18372         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18373         if(islower(*p)) b |= 1 << (*p++ - 'a');
18374       }
18375       if(*p == '-') p++;
18376       if(p != q) {
18377         board[TOUCHED_W] = ~w;
18378         board[TOUCHED_B] = ~b;
18379         while(*p == ' ') p++;
18380       }
18381     } else
18382
18383     if(nrCastlingRights) {
18384       int fischer = 0;
18385       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18386       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18387           /* castling indicator present, so default becomes no castlings */
18388           for(i=0; i<nrCastlingRights; i++ ) {
18389                  board[CASTLING][i] = NoRights;
18390           }
18391       }
18392       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18393              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18394              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18395              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18396         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18397
18398         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18399             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18400             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18401         }
18402         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18403             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18404         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18405                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18406         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18407                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18408         switch(c) {
18409           case'K':
18410               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18411               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18412               board[CASTLING][2] = whiteKingFile;
18413               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18414               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18415               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18416               break;
18417           case'Q':
18418               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18419               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18420               board[CASTLING][2] = whiteKingFile;
18421               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18422               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18423               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18424               break;
18425           case'k':
18426               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18427               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18428               board[CASTLING][5] = blackKingFile;
18429               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18430               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18431               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18432               break;
18433           case'q':
18434               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18435               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18436               board[CASTLING][5] = blackKingFile;
18437               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18438               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18439               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18440           case '-':
18441               break;
18442           default: /* FRC castlings */
18443               if(c >= 'a') { /* black rights */
18444                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18445                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18446                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18447                   if(i == BOARD_RGHT) break;
18448                   board[CASTLING][5] = i;
18449                   c -= AAA;
18450                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18451                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18452                   if(c > i)
18453                       board[CASTLING][3] = c;
18454                   else
18455                       board[CASTLING][4] = c;
18456               } else { /* white rights */
18457                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18458                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18459                     if(board[0][i] == WhiteKing) break;
18460                   if(i == BOARD_RGHT) break;
18461                   board[CASTLING][2] = i;
18462                   c -= AAA - 'a' + 'A';
18463                   if(board[0][c] >= WhiteKing) break;
18464                   if(c > i)
18465                       board[CASTLING][0] = c;
18466                   else
18467                       board[CASTLING][1] = c;
18468               }
18469         }
18470       }
18471       for(i=0; i<nrCastlingRights; i++)
18472         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18473       if(gameInfo.variant == VariantSChess)
18474         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18475       if(fischer && shuffle) appData.fischerCastling = TRUE;
18476     if (appData.debugMode) {
18477         fprintf(debugFP, "FEN castling rights:");
18478         for(i=0; i<nrCastlingRights; i++)
18479         fprintf(debugFP, " %d", board[CASTLING][i]);
18480         fprintf(debugFP, "\n");
18481     }
18482
18483       while(*p==' ') p++;
18484     }
18485
18486     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18487
18488     /* read e.p. field in games that know e.p. capture */
18489     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18490        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18491        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18492       if(*p=='-') {
18493         p++; board[EP_STATUS] = EP_NONE;
18494       } else {
18495          char c = *p++ - AAA;
18496
18497          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18498          if(*p >= '0' && *p <='9') p++;
18499          board[EP_STATUS] = c;
18500       }
18501     }
18502
18503
18504     if(sscanf(p, "%d", &i) == 1) {
18505         FENrulePlies = i; /* 50-move ply counter */
18506         /* (The move number is still ignored)    */
18507     }
18508
18509     return TRUE;
18510 }
18511
18512 void
18513 EditPositionPasteFEN (char *fen)
18514 {
18515   if (fen != NULL) {
18516     Board initial_position;
18517
18518     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18519       DisplayError(_("Bad FEN position in clipboard"), 0);
18520       return ;
18521     } else {
18522       int savedBlackPlaysFirst = blackPlaysFirst;
18523       EditPositionEvent();
18524       blackPlaysFirst = savedBlackPlaysFirst;
18525       CopyBoard(boards[0], initial_position);
18526       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18527       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18528       DisplayBothClocks();
18529       DrawPosition(FALSE, boards[currentMove]);
18530     }
18531   }
18532 }
18533
18534 static char cseq[12] = "\\   ";
18535
18536 Boolean
18537 set_cont_sequence (char *new_seq)
18538 {
18539     int len;
18540     Boolean ret;
18541
18542     // handle bad attempts to set the sequence
18543         if (!new_seq)
18544                 return 0; // acceptable error - no debug
18545
18546     len = strlen(new_seq);
18547     ret = (len > 0) && (len < sizeof(cseq));
18548     if (ret)
18549       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18550     else if (appData.debugMode)
18551       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18552     return ret;
18553 }
18554
18555 /*
18556     reformat a source message so words don't cross the width boundary.  internal
18557     newlines are not removed.  returns the wrapped size (no null character unless
18558     included in source message).  If dest is NULL, only calculate the size required
18559     for the dest buffer.  lp argument indicats line position upon entry, and it's
18560     passed back upon exit.
18561 */
18562 int
18563 wrap (char *dest, char *src, int count, int width, int *lp)
18564 {
18565     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18566
18567     cseq_len = strlen(cseq);
18568     old_line = line = *lp;
18569     ansi = len = clen = 0;
18570
18571     for (i=0; i < count; i++)
18572     {
18573         if (src[i] == '\033')
18574             ansi = 1;
18575
18576         // if we hit the width, back up
18577         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18578         {
18579             // store i & len in case the word is too long
18580             old_i = i, old_len = len;
18581
18582             // find the end of the last word
18583             while (i && src[i] != ' ' && src[i] != '\n')
18584             {
18585                 i--;
18586                 len--;
18587             }
18588
18589             // word too long?  restore i & len before splitting it
18590             if ((old_i-i+clen) >= width)
18591             {
18592                 i = old_i;
18593                 len = old_len;
18594             }
18595
18596             // extra space?
18597             if (i && src[i-1] == ' ')
18598                 len--;
18599
18600             if (src[i] != ' ' && src[i] != '\n')
18601             {
18602                 i--;
18603                 if (len)
18604                     len--;
18605             }
18606
18607             // now append the newline and continuation sequence
18608             if (dest)
18609                 dest[len] = '\n';
18610             len++;
18611             if (dest)
18612                 strncpy(dest+len, cseq, cseq_len);
18613             len += cseq_len;
18614             line = cseq_len;
18615             clen = cseq_len;
18616             continue;
18617         }
18618
18619         if (dest)
18620             dest[len] = src[i];
18621         len++;
18622         if (!ansi)
18623             line++;
18624         if (src[i] == '\n')
18625             line = 0;
18626         if (src[i] == 'm')
18627             ansi = 0;
18628     }
18629     if (dest && appData.debugMode)
18630     {
18631         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18632             count, width, line, len, *lp);
18633         show_bytes(debugFP, src, count);
18634         fprintf(debugFP, "\ndest: ");
18635         show_bytes(debugFP, dest, len);
18636         fprintf(debugFP, "\n");
18637     }
18638     *lp = dest ? line : old_line;
18639
18640     return len;
18641 }
18642
18643 // [HGM] vari: routines for shelving variations
18644 Boolean modeRestore = FALSE;
18645
18646 void
18647 PushInner (int firstMove, int lastMove)
18648 {
18649         int i, j, nrMoves = lastMove - firstMove;
18650
18651         // push current tail of game on stack
18652         savedResult[storedGames] = gameInfo.result;
18653         savedDetails[storedGames] = gameInfo.resultDetails;
18654         gameInfo.resultDetails = NULL;
18655         savedFirst[storedGames] = firstMove;
18656         savedLast [storedGames] = lastMove;
18657         savedFramePtr[storedGames] = framePtr;
18658         framePtr -= nrMoves; // reserve space for the boards
18659         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18660             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18661             for(j=0; j<MOVE_LEN; j++)
18662                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18663             for(j=0; j<2*MOVE_LEN; j++)
18664                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18665             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18666             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18667             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18668             pvInfoList[firstMove+i-1].depth = 0;
18669             commentList[framePtr+i] = commentList[firstMove+i];
18670             commentList[firstMove+i] = NULL;
18671         }
18672
18673         storedGames++;
18674         forwardMostMove = firstMove; // truncate game so we can start variation
18675 }
18676
18677 void
18678 PushTail (int firstMove, int lastMove)
18679 {
18680         if(appData.icsActive) { // only in local mode
18681                 forwardMostMove = currentMove; // mimic old ICS behavior
18682                 return;
18683         }
18684         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18685
18686         PushInner(firstMove, lastMove);
18687         if(storedGames == 1) GreyRevert(FALSE);
18688         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18689 }
18690
18691 void
18692 PopInner (Boolean annotate)
18693 {
18694         int i, j, nrMoves;
18695         char buf[8000], moveBuf[20];
18696
18697         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18698         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18699         nrMoves = savedLast[storedGames] - currentMove;
18700         if(annotate) {
18701                 int cnt = 10;
18702                 if(!WhiteOnMove(currentMove))
18703                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18704                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18705                 for(i=currentMove; i<forwardMostMove; i++) {
18706                         if(WhiteOnMove(i))
18707                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18708                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18709                         strcat(buf, moveBuf);
18710                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18711                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18712                 }
18713                 strcat(buf, ")");
18714         }
18715         for(i=1; i<=nrMoves; i++) { // copy last variation back
18716             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18717             for(j=0; j<MOVE_LEN; j++)
18718                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18719             for(j=0; j<2*MOVE_LEN; j++)
18720                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18721             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18722             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18723             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18724             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18725             commentList[currentMove+i] = commentList[framePtr+i];
18726             commentList[framePtr+i] = NULL;
18727         }
18728         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18729         framePtr = savedFramePtr[storedGames];
18730         gameInfo.result = savedResult[storedGames];
18731         if(gameInfo.resultDetails != NULL) {
18732             free(gameInfo.resultDetails);
18733       }
18734         gameInfo.resultDetails = savedDetails[storedGames];
18735         forwardMostMove = currentMove + nrMoves;
18736 }
18737
18738 Boolean
18739 PopTail (Boolean annotate)
18740 {
18741         if(appData.icsActive) return FALSE; // only in local mode
18742         if(!storedGames) return FALSE; // sanity
18743         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18744
18745         PopInner(annotate);
18746         if(currentMove < forwardMostMove) ForwardEvent(); else
18747         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18748
18749         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18750         return TRUE;
18751 }
18752
18753 void
18754 CleanupTail ()
18755 {       // remove all shelved variations
18756         int i;
18757         for(i=0; i<storedGames; i++) {
18758             if(savedDetails[i])
18759                 free(savedDetails[i]);
18760             savedDetails[i] = NULL;
18761         }
18762         for(i=framePtr; i<MAX_MOVES; i++) {
18763                 if(commentList[i]) free(commentList[i]);
18764                 commentList[i] = NULL;
18765         }
18766         framePtr = MAX_MOVES-1;
18767         storedGames = 0;
18768 }
18769
18770 void
18771 LoadVariation (int index, char *text)
18772 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18773         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18774         int level = 0, move;
18775
18776         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18777         // first find outermost bracketing variation
18778         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18779             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18780                 if(*p == '{') wait = '}'; else
18781                 if(*p == '[') wait = ']'; else
18782                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18783                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18784             }
18785             if(*p == wait) wait = NULLCHAR; // closing ]} found
18786             p++;
18787         }
18788         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18789         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18790         end[1] = NULLCHAR; // clip off comment beyond variation
18791         ToNrEvent(currentMove-1);
18792         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18793         // kludge: use ParsePV() to append variation to game
18794         move = currentMove;
18795         ParsePV(start, TRUE, TRUE);
18796         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18797         ClearPremoveHighlights();
18798         CommentPopDown();
18799         ToNrEvent(currentMove+1);
18800 }
18801
18802 void
18803 LoadTheme ()
18804 {
18805     char *p, *q, buf[MSG_SIZ];
18806     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18807         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18808         ParseArgsFromString(buf);
18809         ActivateTheme(TRUE); // also redo colors
18810         return;
18811     }
18812     p = nickName;
18813     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18814     {
18815         int len;
18816         q = appData.themeNames;
18817         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18818       if(appData.useBitmaps) {
18819         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18820                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18821                 appData.liteBackTextureMode,
18822                 appData.darkBackTextureMode );
18823       } else {
18824         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18825                 Col2Text(2),   // lightSquareColor
18826                 Col2Text(3) ); // darkSquareColor
18827       }
18828       if(appData.useBorder) {
18829         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18830                 appData.border);
18831       } else {
18832         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18833       }
18834       if(appData.useFont) {
18835         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18836                 appData.renderPiecesWithFont,
18837                 appData.fontToPieceTable,
18838                 Col2Text(9),    // appData.fontBackColorWhite
18839                 Col2Text(10) ); // appData.fontForeColorBlack
18840       } else {
18841         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18842                 appData.pieceDirectory);
18843         if(!appData.pieceDirectory[0])
18844           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18845                 Col2Text(0),   // whitePieceColor
18846                 Col2Text(1) ); // blackPieceColor
18847       }
18848       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18849                 Col2Text(4),   // highlightSquareColor
18850                 Col2Text(5) ); // premoveHighlightColor
18851         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18852         if(insert != q) insert[-1] = NULLCHAR;
18853         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18854         if(q)   free(q);
18855     }
18856     ActivateTheme(FALSE);
18857 }