Let PROMOTED and DEMOTED macros use argument
[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 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 signed char  initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                       }
4174                     }
4175
4176                     /* Usually suppress following prompt */
4177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4178                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4179                         if (looking_at(buf, &i, "*% ")) {
4180                             savingComment = FALSE;
4181                             suppressKibitz = 0;
4182                         }
4183                     }
4184                     next_out = i;
4185                 } else if (started == STARTED_HOLDINGS) {
4186                     int gamenum;
4187                     char new_piece[MSG_SIZ];
4188                     started = STARTED_NONE;
4189                     parse[parse_pos] = NULLCHAR;
4190                     if (appData.debugMode)
4191                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4192                                                         parse, currentMove);
4193                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4194                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4195                         if (gameInfo.variant == VariantNormal) {
4196                           /* [HGM] We seem to switch variant during a game!
4197                            * Presumably no holdings were displayed, so we have
4198                            * to move the position two files to the right to
4199                            * create room for them!
4200                            */
4201                           VariantClass newVariant;
4202                           switch(gameInfo.boardWidth) { // base guess on board width
4203                                 case 9:  newVariant = VariantShogi; break;
4204                                 case 10: newVariant = VariantGreat; break;
4205                                 default: newVariant = VariantCrazyhouse; break;
4206                           }
4207                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4208                           /* Get a move list just to see the header, which
4209                              will tell us whether this is really bug or zh */
4210                           if (ics_getting_history == H_FALSE) {
4211                             ics_getting_history = H_REQUESTED;
4212                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4213                             SendToICS(str);
4214                           }
4215                         }
4216                         new_piece[0] = NULLCHAR;
4217                         sscanf(parse, "game %d white [%s black [%s <- %s",
4218                                &gamenum, white_holding, black_holding,
4219                                new_piece);
4220                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4221                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4222                         /* [HGM] copy holdings to board holdings area */
4223                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4224                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4225                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4226 #if ZIPPY
4227                         if (appData.zippyPlay && first.initDone) {
4228                             ZippyHoldings(white_holding, black_holding,
4229                                           new_piece);
4230                         }
4231 #endif /*ZIPPY*/
4232                         if (tinyLayout || smallLayout) {
4233                             char wh[16], bh[16];
4234                             PackHolding(wh, white_holding);
4235                             PackHolding(bh, black_holding);
4236                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4237                                     gameInfo.white, gameInfo.black);
4238                         } else {
4239                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4240                                     gameInfo.white, white_holding, _("vs."),
4241                                     gameInfo.black, black_holding);
4242                         }
4243                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4244                         DrawPosition(FALSE, boards[currentMove]);
4245                         DisplayTitle(str);
4246                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4247                         sscanf(parse, "game %d white [%s black [%s <- %s",
4248                                &gamenum, white_holding, black_holding,
4249                                new_piece);
4250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4252                         /* [HGM] copy holdings to partner-board holdings area */
4253                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4254                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4255                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4256                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4257                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4258                       }
4259                     }
4260                     /* Suppress following prompt */
4261                     if (looking_at(buf, &i, "*% ")) {
4262                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4263                         savingComment = FALSE;
4264                         suppressKibitz = 0;
4265                     }
4266                     next_out = i;
4267                 }
4268                 continue;
4269             }
4270
4271             i++;                /* skip unparsed character and loop back */
4272         }
4273
4274         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4275 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4276 //          SendToPlayer(&buf[next_out], i - next_out);
4277             started != STARTED_HOLDINGS && leftover_start > next_out) {
4278             SendToPlayer(&buf[next_out], leftover_start - next_out);
4279             next_out = i;
4280         }
4281
4282         leftover_len = buf_len - leftover_start;
4283         /* if buffer ends with something we couldn't parse,
4284            reparse it after appending the next read */
4285
4286     } else if (count == 0) {
4287         RemoveInputSource(isr);
4288         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4289     } else {
4290         DisplayFatalError(_("Error reading from ICS"), error, 1);
4291     }
4292 }
4293
4294
4295 /* Board style 12 looks like this:
4296
4297    <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
4298
4299  * The "<12> " is stripped before it gets to this routine.  The two
4300  * trailing 0's (flip state and clock ticking) are later addition, and
4301  * some chess servers may not have them, or may have only the first.
4302  * Additional trailing fields may be added in the future.
4303  */
4304
4305 #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"
4306
4307 #define RELATION_OBSERVING_PLAYED    0
4308 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4309 #define RELATION_PLAYING_MYMOVE      1
4310 #define RELATION_PLAYING_NOTMYMOVE  -1
4311 #define RELATION_EXAMINING           2
4312 #define RELATION_ISOLATED_BOARD     -3
4313 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4314
4315 void
4316 ParseBoard12 (char *string)
4317 {
4318 #if ZIPPY
4319     int i, takeback;
4320     char *bookHit = NULL; // [HGM] book
4321 #endif
4322     GameMode newGameMode;
4323     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4324     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4325     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4326     char to_play, board_chars[200];
4327     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4328     char black[32], white[32];
4329     Board board;
4330     int prevMove = currentMove;
4331     int ticking = 2;
4332     ChessMove moveType;
4333     int fromX, fromY, toX, toY;
4334     char promoChar;
4335     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4336     Boolean weird = FALSE, reqFlag = FALSE;
4337
4338     fromX = fromY = toX = toY = -1;
4339
4340     newGame = FALSE;
4341
4342     if (appData.debugMode)
4343       fprintf(debugFP, "Parsing board: %s\n", string);
4344
4345     move_str[0] = NULLCHAR;
4346     elapsed_time[0] = NULLCHAR;
4347     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4348         int  i = 0, j;
4349         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4350             if(string[i] == ' ') { ranks++; files = 0; }
4351             else files++;
4352             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4353             i++;
4354         }
4355         for(j = 0; j <i; j++) board_chars[j] = string[j];
4356         board_chars[i] = '\0';
4357         string += i + 1;
4358     }
4359     n = sscanf(string, PATTERN, &to_play, &double_push,
4360                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4361                &gamenum, white, black, &relation, &basetime, &increment,
4362                &white_stren, &black_stren, &white_time, &black_time,
4363                &moveNum, str, elapsed_time, move_str, &ics_flip,
4364                &ticking);
4365
4366     if (n < 21) {
4367         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4368         DisplayError(str, 0);
4369         return;
4370     }
4371
4372     /* Convert the move number to internal form */
4373     moveNum = (moveNum - 1) * 2;
4374     if (to_play == 'B') moveNum++;
4375     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4376       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4377                         0, 1);
4378       return;
4379     }
4380
4381     switch (relation) {
4382       case RELATION_OBSERVING_PLAYED:
4383       case RELATION_OBSERVING_STATIC:
4384         if (gamenum == -1) {
4385             /* Old ICC buglet */
4386             relation = RELATION_OBSERVING_STATIC;
4387         }
4388         newGameMode = IcsObserving;
4389         break;
4390       case RELATION_PLAYING_MYMOVE:
4391       case RELATION_PLAYING_NOTMYMOVE:
4392         newGameMode =
4393           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4394             IcsPlayingWhite : IcsPlayingBlack;
4395         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4396         break;
4397       case RELATION_EXAMINING:
4398         newGameMode = IcsExamining;
4399         break;
4400       case RELATION_ISOLATED_BOARD:
4401       default:
4402         /* Just display this board.  If user was doing something else,
4403            we will forget about it until the next board comes. */
4404         newGameMode = IcsIdle;
4405         break;
4406       case RELATION_STARTING_POSITION:
4407         newGameMode = gameMode;
4408         break;
4409     }
4410
4411     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4412         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4413          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4414       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4415       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4416       static int lastBgGame = -1;
4417       char *toSqr;
4418       for (k = 0; k < ranks; k++) {
4419         for (j = 0; j < files; j++)
4420           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4421         if(gameInfo.holdingsWidth > 1) {
4422              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4423              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4424         }
4425       }
4426       CopyBoard(partnerBoard, board);
4427       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4428         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4429         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4431       if(toSqr = strchr(str, '-')) {
4432         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4433         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4434       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4435       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4436       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4437       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4438       if(twoBoards) {
4439           DisplayWhiteClock(white_time*fac, to_play == 'W');
4440           DisplayBlackClock(black_time*fac, to_play != 'W');
4441           activePartner = to_play;
4442           if(gamenum != lastBgGame) {
4443               char buf[MSG_SIZ];
4444               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4445               DisplayTitle(buf);
4446           }
4447           lastBgGame = gamenum;
4448           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4449                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4450       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4451                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4452       if(!twoBoards) DisplayMessage(partnerStatus, "");
4453         partnerBoardValid = TRUE;
4454       return;
4455     }
4456
4457     if(appData.dualBoard && appData.bgObserve) {
4458         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4459             SendToICS(ics_prefix), SendToICS("pobserve\n");
4460         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4461             char buf[MSG_SIZ];
4462             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4463             SendToICS(buf);
4464         }
4465     }
4466
4467     /* Modify behavior for initial board display on move listing
4468        of wild games.
4469        */
4470     switch (ics_getting_history) {
4471       case H_FALSE:
4472       case H_REQUESTED:
4473         break;
4474       case H_GOT_REQ_HEADER:
4475       case H_GOT_UNREQ_HEADER:
4476         /* This is the initial position of the current game */
4477         gamenum = ics_gamenum;
4478         moveNum = 0;            /* old ICS bug workaround */
4479         if (to_play == 'B') {
4480           startedFromSetupPosition = TRUE;
4481           blackPlaysFirst = TRUE;
4482           moveNum = 1;
4483           if (forwardMostMove == 0) forwardMostMove = 1;
4484           if (backwardMostMove == 0) backwardMostMove = 1;
4485           if (currentMove == 0) currentMove = 1;
4486         }
4487         newGameMode = gameMode;
4488         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4489         break;
4490       case H_GOT_UNWANTED_HEADER:
4491         /* This is an initial board that we don't want */
4492         return;
4493       case H_GETTING_MOVES:
4494         /* Should not happen */
4495         DisplayError(_("Error gathering move list: extra board"), 0);
4496         ics_getting_history = H_FALSE;
4497         return;
4498     }
4499
4500    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4501                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4502                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4503      /* [HGM] We seem to have switched variant unexpectedly
4504       * Try to guess new variant from board size
4505       */
4506           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4507           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4508           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4509           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4510           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4511           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4512           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4513           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4514           /* Get a move list just to see the header, which
4515              will tell us whether this is really bug or zh */
4516           if (ics_getting_history == H_FALSE) {
4517             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4518             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4519             SendToICS(str);
4520           }
4521     }
4522
4523     /* Take action if this is the first board of a new game, or of a
4524        different game than is currently being displayed.  */
4525     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4526         relation == RELATION_ISOLATED_BOARD) {
4527
4528         /* Forget the old game and get the history (if any) of the new one */
4529         if (gameMode != BeginningOfGame) {
4530           Reset(TRUE, TRUE);
4531         }
4532         newGame = TRUE;
4533         if (appData.autoRaiseBoard) BoardToTop();
4534         prevMove = -3;
4535         if (gamenum == -1) {
4536             newGameMode = IcsIdle;
4537         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4538                    appData.getMoveList && !reqFlag) {
4539             /* Need to get game history */
4540             ics_getting_history = H_REQUESTED;
4541             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542             SendToICS(str);
4543         }
4544
4545         /* Initially flip the board to have black on the bottom if playing
4546            black or if the ICS flip flag is set, but let the user change
4547            it with the Flip View button. */
4548         flipView = appData.autoFlipView ?
4549           (newGameMode == IcsPlayingBlack) || ics_flip :
4550           appData.flipView;
4551
4552         /* Done with values from previous mode; copy in new ones */
4553         gameMode = newGameMode;
4554         ModeHighlight();
4555         ics_gamenum = gamenum;
4556         if (gamenum == gs_gamenum) {
4557             int klen = strlen(gs_kind);
4558             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4559             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4560             gameInfo.event = StrSave(str);
4561         } else {
4562             gameInfo.event = StrSave("ICS game");
4563         }
4564         gameInfo.site = StrSave(appData.icsHost);
4565         gameInfo.date = PGNDate();
4566         gameInfo.round = StrSave("-");
4567         gameInfo.white = StrSave(white);
4568         gameInfo.black = StrSave(black);
4569         timeControl = basetime * 60 * 1000;
4570         timeControl_2 = 0;
4571         timeIncrement = increment * 1000;
4572         movesPerSession = 0;
4573         gameInfo.timeControl = TimeControlTagValue();
4574         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4575   if (appData.debugMode) {
4576     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4577     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4578     setbuf(debugFP, NULL);
4579   }
4580
4581         gameInfo.outOfBook = NULL;
4582
4583         /* Do we have the ratings? */
4584         if (strcmp(player1Name, white) == 0 &&
4585             strcmp(player2Name, black) == 0) {
4586             if (appData.debugMode)
4587               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4588                       player1Rating, player2Rating);
4589             gameInfo.whiteRating = player1Rating;
4590             gameInfo.blackRating = player2Rating;
4591         } else if (strcmp(player2Name, white) == 0 &&
4592                    strcmp(player1Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player2Rating, player1Rating);
4596             gameInfo.whiteRating = player2Rating;
4597             gameInfo.blackRating = player1Rating;
4598         }
4599         player1Name[0] = player2Name[0] = NULLCHAR;
4600
4601         /* Silence shouts if requested */
4602         if (appData.quietPlay &&
4603             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4604             SendToICS(ics_prefix);
4605             SendToICS("set shout 0\n");
4606         }
4607     }
4608
4609     /* Deal with midgame name changes */
4610     if (!newGame) {
4611         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4612             if (gameInfo.white) free(gameInfo.white);
4613             gameInfo.white = StrSave(white);
4614         }
4615         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4616             if (gameInfo.black) free(gameInfo.black);
4617             gameInfo.black = StrSave(black);
4618         }
4619     }
4620
4621     /* Throw away game result if anything actually changes in examine mode */
4622     if (gameMode == IcsExamining && !newGame) {
4623         gameInfo.result = GameUnfinished;
4624         if (gameInfo.resultDetails != NULL) {
4625             free(gameInfo.resultDetails);
4626             gameInfo.resultDetails = NULL;
4627         }
4628     }
4629
4630     /* In pausing && IcsExamining mode, we ignore boards coming
4631        in if they are in a different variation than we are. */
4632     if (pauseExamInvalid) return;
4633     if (pausing && gameMode == IcsExamining) {
4634         if (moveNum <= pauseExamForwardMostMove) {
4635             pauseExamInvalid = TRUE;
4636             forwardMostMove = pauseExamForwardMostMove;
4637             return;
4638         }
4639     }
4640
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4643   }
4644     /* Parse the board */
4645     for (k = 0; k < ranks; k++) {
4646       for (j = 0; j < files; j++)
4647         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4648       if(gameInfo.holdingsWidth > 1) {
4649            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4650            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4651       }
4652     }
4653     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4654       board[5][BOARD_RGHT+1] = WhiteAngel;
4655       board[6][BOARD_RGHT+1] = WhiteMarshall;
4656       board[1][0] = BlackMarshall;
4657       board[2][0] = BlackAngel;
4658       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4659     }
4660     CopyBoard(boards[moveNum], board);
4661     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4662     if (moveNum == 0) {
4663         startedFromSetupPosition =
4664           !CompareBoards(board, initialPosition);
4665         if(startedFromSetupPosition)
4666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4667     }
4668
4669     /* [HGM] Set castling rights. Take the outermost Rooks,
4670        to make it also work for FRC opening positions. Note that board12
4671        is really defective for later FRC positions, as it has no way to
4672        indicate which Rook can castle if they are on the same side of King.
4673        For the initial position we grant rights to the outermost Rooks,
4674        and remember thos rights, and we then copy them on positions
4675        later in an FRC game. This means WB might not recognize castlings with
4676        Rooks that have moved back to their original position as illegal,
4677        but in ICS mode that is not its job anyway.
4678     */
4679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4681
4682         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4683             if(board[0][i] == WhiteRook) j = i;
4684         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4686             if(board[0][i] == WhiteRook) j = i;
4687         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4688         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4690         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4691         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4693         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4694
4695         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4696         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4699         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4700             if(board[BOARD_HEIGHT-1][k] == bKing)
4701                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4702         if(gameInfo.variant == VariantTwoKings) {
4703             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4704             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4705             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4706         }
4707     } else { int r;
4708         r = boards[moveNum][CASTLING][0] = initialRights[0];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4710         r = boards[moveNum][CASTLING][1] = initialRights[1];
4711         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4712         r = boards[moveNum][CASTLING][3] = initialRights[3];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4714         r = boards[moveNum][CASTLING][4] = initialRights[4];
4715         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4716         /* wildcastle kludge: always assume King has rights */
4717         r = boards[moveNum][CASTLING][2] = initialRights[2];
4718         r = boards[moveNum][CASTLING][5] = initialRights[5];
4719     }
4720     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4721     boards[moveNum][EP_STATUS] = EP_NONE;
4722     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4723     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4724     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4725
4726
4727     if (ics_getting_history == H_GOT_REQ_HEADER ||
4728         ics_getting_history == H_GOT_UNREQ_HEADER) {
4729         /* This was an initial position from a move list, not
4730            the current position */
4731         return;
4732     }
4733
4734     /* Update currentMove and known move number limits */
4735     newMove = newGame || moveNum > forwardMostMove;
4736
4737     if (newGame) {
4738         forwardMostMove = backwardMostMove = currentMove = moveNum;
4739         if (gameMode == IcsExamining && moveNum == 0) {
4740           /* Workaround for ICS limitation: we are not told the wild
4741              type when starting to examine a game.  But if we ask for
4742              the move list, the move list header will tell us */
4743             ics_getting_history = H_REQUESTED;
4744             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4745             SendToICS(str);
4746         }
4747     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4748                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4749 #if ZIPPY
4750         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4751         /* [HGM] applied this also to an engine that is silently watching        */
4752         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4753             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4754             gameInfo.variant == currentlyInitializedVariant) {
4755           takeback = forwardMostMove - moveNum;
4756           for (i = 0; i < takeback; i++) {
4757             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4758             SendToProgram("undo\n", &first);
4759           }
4760         }
4761 #endif
4762
4763         forwardMostMove = moveNum;
4764         if (!pausing || currentMove > forwardMostMove)
4765           currentMove = forwardMostMove;
4766     } else {
4767         /* New part of history that is not contiguous with old part */
4768         if (pausing && gameMode == IcsExamining) {
4769             pauseExamInvalid = TRUE;
4770             forwardMostMove = pauseExamForwardMostMove;
4771             return;
4772         }
4773         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4774 #if ZIPPY
4775             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4776                 // [HGM] when we will receive the move list we now request, it will be
4777                 // fed to the engine from the first move on. So if the engine is not
4778                 // in the initial position now, bring it there.
4779                 InitChessProgram(&first, 0);
4780             }
4781 #endif
4782             ics_getting_history = H_REQUESTED;
4783             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4784             SendToICS(str);
4785         }
4786         forwardMostMove = backwardMostMove = currentMove = moveNum;
4787     }
4788
4789     /* Update the clocks */
4790     if (strchr(elapsed_time, '.')) {
4791       /* Time is in ms */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4794     } else {
4795       /* Time is in seconds */
4796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4798     }
4799
4800
4801 #if ZIPPY
4802     if (appData.zippyPlay && newGame &&
4803         gameMode != IcsObserving && gameMode != IcsIdle &&
4804         gameMode != IcsExamining)
4805       ZippyFirstBoard(moveNum, basetime, increment);
4806 #endif
4807
4808     /* Put the move on the move list, first converting
4809        to canonical algebraic form. */
4810     if (moveNum > 0) {
4811   if (appData.debugMode) {
4812     int f = forwardMostMove;
4813     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4814             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4815             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4816     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4817     fprintf(debugFP, "moveNum = %d\n", moveNum);
4818     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4819     setbuf(debugFP, NULL);
4820   }
4821         if (moveNum <= backwardMostMove) {
4822             /* We don't know what the board looked like before
4823                this move.  Punt. */
4824           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4825             strcat(parseList[moveNum - 1], " ");
4826             strcat(parseList[moveNum - 1], elapsed_time);
4827             moveList[moveNum - 1][0] = NULLCHAR;
4828         } else if (strcmp(move_str, "none") == 0) {
4829             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4830             /* Again, we don't know what the board looked like;
4831                this is really the start of the game. */
4832             parseList[moveNum - 1][0] = NULLCHAR;
4833             moveList[moveNum - 1][0] = NULLCHAR;
4834             backwardMostMove = moveNum;
4835             startedFromSetupPosition = TRUE;
4836             fromX = fromY = toX = toY = -1;
4837         } else {
4838           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4839           //                 So we parse the long-algebraic move string in stead of the SAN move
4840           int valid; char buf[MSG_SIZ], *prom;
4841
4842           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4843                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4844           // str looks something like "Q/a1-a2"; kill the slash
4845           if(str[1] == '/')
4846             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4847           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4848           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4849                 strcat(buf, prom); // long move lacks promo specification!
4850           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4851                 if(appData.debugMode)
4852                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4853                 safeStrCpy(move_str, buf, MSG_SIZ);
4854           }
4855           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4856                                 &fromX, &fromY, &toX, &toY, &promoChar)
4857                || ParseOneMove(buf, moveNum - 1, &moveType,
4858                                 &fromX, &fromY, &toX, &toY, &promoChar);
4859           // end of long SAN patch
4860           if (valid) {
4861             (void) CoordsToAlgebraic(boards[moveNum - 1],
4862                                      PosFlags(moveNum - 1),
4863                                      fromY, fromX, toY, toX, promoChar,
4864                                      parseList[moveNum-1]);
4865             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4866               case MT_NONE:
4867               case MT_STALEMATE:
4868               default:
4869                 break;
4870               case MT_CHECK:
4871                 if(!IS_SHOGI(gameInfo.variant))
4872                     strcat(parseList[moveNum - 1], "+");
4873                 break;
4874               case MT_CHECKMATE:
4875               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4876                 strcat(parseList[moveNum - 1], "#");
4877                 break;
4878             }
4879             strcat(parseList[moveNum - 1], " ");
4880             strcat(parseList[moveNum - 1], elapsed_time);
4881             /* currentMoveString is set as a side-effect of ParseOneMove */
4882             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4883             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4884             strcat(moveList[moveNum - 1], "\n");
4885
4886             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4887                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4888               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4889                 ChessSquare old, new = boards[moveNum][k][j];
4890                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4891                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4892                   if(old == new) continue;
4893                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4894                   else if(new == WhiteWazir || new == BlackWazir) {
4895                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4896                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4897                       else boards[moveNum][k][j] = old; // preserve type of Gold
4898                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4899                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4900               }
4901           } else {
4902             /* Move from ICS was illegal!?  Punt. */
4903             if (appData.debugMode) {
4904               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4905               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4906             }
4907             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4908             strcat(parseList[moveNum - 1], " ");
4909             strcat(parseList[moveNum - 1], elapsed_time);
4910             moveList[moveNum - 1][0] = NULLCHAR;
4911             fromX = fromY = toX = toY = -1;
4912           }
4913         }
4914   if (appData.debugMode) {
4915     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4916     setbuf(debugFP, NULL);
4917   }
4918
4919 #if ZIPPY
4920         /* Send move to chess program (BEFORE animating it). */
4921         if (appData.zippyPlay && !newGame && newMove &&
4922            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4923
4924             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4925                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4926                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4927                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4928                             move_str);
4929                     DisplayError(str, 0);
4930                 } else {
4931                     if (first.sendTime) {
4932                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4933                     }
4934                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4935                     if (firstMove && !bookHit) {
4936                         firstMove = FALSE;
4937                         if (first.useColors) {
4938                           SendToProgram(gameMode == IcsPlayingWhite ?
4939                                         "white\ngo\n" :
4940                                         "black\ngo\n", &first);
4941                         } else {
4942                           SendToProgram("go\n", &first);
4943                         }
4944                         first.maybeThinking = TRUE;
4945                     }
4946                 }
4947             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4948               if (moveList[moveNum - 1][0] == NULLCHAR) {
4949                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4950                 DisplayError(str, 0);
4951               } else {
4952                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4953                 SendMoveToProgram(moveNum - 1, &first);
4954               }
4955             }
4956         }
4957 #endif
4958     }
4959
4960     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4961         /* If move comes from a remote source, animate it.  If it
4962            isn't remote, it will have already been animated. */
4963         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4964             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4965         }
4966         if (!pausing && appData.highlightLastMove) {
4967             SetHighlights(fromX, fromY, toX, toY);
4968         }
4969     }
4970
4971     /* Start the clocks */
4972     whiteFlag = blackFlag = FALSE;
4973     appData.clockMode = !(basetime == 0 && increment == 0);
4974     if (ticking == 0) {
4975       ics_clock_paused = TRUE;
4976       StopClocks();
4977     } else if (ticking == 1) {
4978       ics_clock_paused = FALSE;
4979     }
4980     if (gameMode == IcsIdle ||
4981         relation == RELATION_OBSERVING_STATIC ||
4982         relation == RELATION_EXAMINING ||
4983         ics_clock_paused)
4984       DisplayBothClocks();
4985     else
4986       StartClocks();
4987
4988     /* Display opponents and material strengths */
4989     if (gameInfo.variant != VariantBughouse &&
4990         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4991         if (tinyLayout || smallLayout) {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4998                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4999                     basetime, increment, (int) gameInfo.variant);
5000         } else {
5001             if(gameInfo.variant == VariantNormal)
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment);
5005             else
5006               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5007                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5008                     basetime, increment, VariantName(gameInfo.variant));
5009         }
5010         DisplayTitle(str);
5011   if (appData.debugMode) {
5012     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5013   }
5014     }
5015
5016
5017     /* Display the board */
5018     if (!pausing && !appData.noGUI) {
5019
5020       if (appData.premove)
5021           if (!gotPremove ||
5022              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5023              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5024               ClearPremoveHighlights();
5025
5026       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5027         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5028       DrawPosition(j, boards[currentMove]);
5029
5030       DisplayMove(moveNum - 1);
5031       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5032             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5033               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5034         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5035       }
5036     }
5037
5038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5039 #if ZIPPY
5040     if(bookHit) { // [HGM] book: simulate book reply
5041         static char bookMove[MSG_SIZ]; // a bit generous?
5042
5043         programStats.nodes = programStats.depth = programStats.time =
5044         programStats.score = programStats.got_only_move = 0;
5045         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5046
5047         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5048         strcat(bookMove, bookHit);
5049         HandleMachineMove(bookMove, &first);
5050     }
5051 #endif
5052 }
5053
5054 void
5055 GetMoveListEvent ()
5056 {
5057     char buf[MSG_SIZ];
5058     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5059         ics_getting_history = H_REQUESTED;
5060         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5061         SendToICS(buf);
5062     }
5063 }
5064
5065 void
5066 SendToBoth (char *msg)
5067 {   // to make it easy to keep two engines in step in dual analysis
5068     SendToProgram(msg, &first);
5069     if(second.analyzing) SendToProgram(msg, &second);
5070 }
5071
5072 void
5073 AnalysisPeriodicEvent (int force)
5074 {
5075     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5076          && !force) || !appData.periodicUpdates)
5077       return;
5078
5079     /* Send . command to Crafty to collect stats */
5080     SendToBoth(".\n");
5081
5082     /* Don't send another until we get a response (this makes
5083        us stop sending to old Crafty's which don't understand
5084        the "." command (sending illegal cmds resets node count & time,
5085        which looks bad)) */
5086     programStats.ok_to_send = 0;
5087 }
5088
5089 void
5090 ics_update_width (int new_width)
5091 {
5092         ics_printf("set width %d\n", new_width);
5093 }
5094
5095 void
5096 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5097 {
5098     char buf[MSG_SIZ];
5099
5100     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5101         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5102             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5103             SendToProgram(buf, cps);
5104             return;
5105         }
5106         // null move in variant where engine does not understand it (for analysis purposes)
5107         SendBoard(cps, moveNum + 1); // send position after move in stead.
5108         return;
5109     }
5110     if (cps->useUsermove) {
5111       SendToProgram("usermove ", cps);
5112     }
5113     if (cps->useSAN) {
5114       char *space;
5115       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5116         int len = space - parseList[moveNum];
5117         memcpy(buf, parseList[moveNum], len);
5118         buf[len++] = '\n';
5119         buf[len] = NULLCHAR;
5120       } else {
5121         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5122       }
5123       SendToProgram(buf, cps);
5124     } else {
5125       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5126         AlphaRank(moveList[moveNum], 4);
5127         SendToProgram(moveList[moveNum], cps);
5128         AlphaRank(moveList[moveNum], 4); // and back
5129       } else
5130       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5131        * the engine. It would be nice to have a better way to identify castle
5132        * moves here. */
5133       if(appData.fischerCastling && cps->useOOCastle) {
5134         int fromX = moveList[moveNum][0] - AAA;
5135         int fromY = moveList[moveNum][1] - ONE;
5136         int toX = moveList[moveNum][2] - AAA;
5137         int toY = moveList[moveNum][3] - ONE;
5138         if((boards[moveNum][fromY][fromX] == WhiteKing
5139             && boards[moveNum][toY][toX] == WhiteRook)
5140            || (boards[moveNum][fromY][fromX] == BlackKing
5141                && boards[moveNum][toY][toX] == BlackRook)) {
5142           if(toX > fromX) SendToProgram("O-O\n", cps);
5143           else SendToProgram("O-O-O\n", cps);
5144         }
5145         else SendToProgram(moveList[moveNum], cps);
5146       } else
5147       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5148         char *m = moveList[moveNum];
5149         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
5150           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5151                                                m[2], m[3] - '0',
5152                                                m[5], m[6] - '0',
5153                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5154         else
5155           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5156                                                m[5], m[6] - '0',
5157                                                m[5], m[6] - '0',
5158                                                m[2], m[3] - '0');
5159           SendToProgram(buf, cps);
5160       } else
5161       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5162         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5163           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5164           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5165                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5166         } else
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5168                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5169         SendToProgram(buf, cps);
5170       }
5171       else SendToProgram(moveList[moveNum], cps);
5172       /* End of additions by Tord */
5173     }
5174
5175     /* [HGM] setting up the opening has brought engine in force mode! */
5176     /*       Send 'go' if we are in a mode where machine should play. */
5177     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5178         (gameMode == TwoMachinesPlay   ||
5179 #if ZIPPY
5180          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5181 #endif
5182          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5183         SendToProgram("go\n", cps);
5184   if (appData.debugMode) {
5185     fprintf(debugFP, "(extra)\n");
5186   }
5187     }
5188     setboardSpoiledMachineBlack = 0;
5189 }
5190
5191 void
5192 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5193 {
5194     char user_move[MSG_SIZ];
5195     char suffix[4];
5196
5197     if(gameInfo.variant == VariantSChess && promoChar) {
5198         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5199         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5200     } else suffix[0] = NULLCHAR;
5201
5202     switch (moveType) {
5203       default:
5204         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5205                 (int)moveType, fromX, fromY, toX, toY);
5206         DisplayError(user_move + strlen("say "), 0);
5207         break;
5208       case WhiteKingSideCastle:
5209       case BlackKingSideCastle:
5210       case WhiteQueenSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* PUSH Fabien */
5213       case WhiteHSideCastleFR:
5214       case BlackHSideCastleFR:
5215       /* POP Fabien */
5216         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5217         break;
5218       case WhiteQueenSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case BlackKingSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteASideCastleFR:
5224       case BlackASideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5227         break;
5228       case WhiteNonPromotion:
5229       case BlackNonPromotion:
5230         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5231         break;
5232       case WhitePromotion:
5233       case BlackPromotion:
5234         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5235            gameInfo.variant == VariantMakruk)
5236           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteFerz));
5239         else if(gameInfo.variant == VariantGreat)
5240           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 PieceToChar(WhiteMan));
5243         else
5244           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5246                 promoChar);
5247         break;
5248       case WhiteDrop:
5249       case BlackDrop:
5250       drop:
5251         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5252                  ToUpper(PieceToChar((ChessSquare) fromX)),
5253                  AAA + toX, ONE + toY);
5254         break;
5255       case IllegalMove:  /* could be a variant we don't quite understand */
5256         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5257       case NormalMove:
5258       case WhiteCapturesEnPassant:
5259       case BlackCapturesEnPassant:
5260         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262         break;
5263     }
5264     SendToICS(user_move);
5265     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5266         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5267 }
5268
5269 void
5270 UploadGameEvent ()
5271 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5272     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5273     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5274     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5275       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5276       return;
5277     }
5278     if(gameMode != IcsExamining) { // is this ever not the case?
5279         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5280
5281         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5282           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5283         } else { // on FICS we must first go to general examine mode
5284           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5285         }
5286         if(gameInfo.variant != VariantNormal) {
5287             // try figure out wild number, as xboard names are not always valid on ICS
5288             for(i=1; i<=36; i++) {
5289               snprintf(buf, MSG_SIZ, "wild/%d", i);
5290                 if(StringToVariant(buf) == gameInfo.variant) break;
5291             }
5292             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5293             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5294             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5295         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5296         SendToICS(ics_prefix);
5297         SendToICS(buf);
5298         if(startedFromSetupPosition || backwardMostMove != 0) {
5299           fen = PositionToFEN(backwardMostMove, NULL, 1);
5300           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5301             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5302             SendToICS(buf);
5303           } else { // FICS: everything has to set by separate bsetup commands
5304             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5305             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5306             SendToICS(buf);
5307             if(!WhiteOnMove(backwardMostMove)) {
5308                 SendToICS("bsetup tomove black\n");
5309             }
5310             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5311             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5312             SendToICS(buf);
5313             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5314             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5315             SendToICS(buf);
5316             i = boards[backwardMostMove][EP_STATUS];
5317             if(i >= 0) { // set e.p.
5318               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5319                 SendToICS(buf);
5320             }
5321             bsetup++;
5322           }
5323         }
5324       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5325             SendToICS("bsetup done\n"); // switch to normal examining.
5326     }
5327     for(i = backwardMostMove; i<last; i++) {
5328         char buf[20];
5329         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5330         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5331             int len = strlen(moveList[i]);
5332             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5333             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5334         }
5335         SendToICS(buf);
5336     }
5337     SendToICS(ics_prefix);
5338     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5339 }
5340
5341 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5342 int legNr = 1;
5343
5344 void
5345 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5346 {
5347     if (rf == DROP_RANK) {
5348       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5349       sprintf(move, "%c@%c%c\n",
5350                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5351     } else {
5352         if (promoChar == 'x' || promoChar == NULLCHAR) {
5353           sprintf(move, "%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5355           if(killX >= 0 && killY >= 0) {
5356             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5357             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5358           }
5359         } else {
5360             sprintf(move, "%c%c%c%c%c\n",
5361                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5362         }
5363     }
5364 }
5365
5366 void
5367 ProcessICSInitScript (FILE *f)
5368 {
5369     char buf[MSG_SIZ];
5370
5371     while (fgets(buf, MSG_SIZ, f)) {
5372         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5373     }
5374
5375     fclose(f);
5376 }
5377
5378
5379 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5380 int dragging;
5381 static ClickType lastClickType;
5382
5383 int
5384 Partner (ChessSquare *p)
5385 { // change piece into promotion partner if one shogi-promotes to the other
5386   int stride = gameInfo.variant == VariantChu ? WhiteTokin : 11;
5387   ChessSquare partner;
5388   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5389   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5390   *p = partner;
5391   return 1;
5392 }
5393
5394 void
5395 Sweep (int step)
5396 {
5397     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5398     static int toggleFlag;
5399     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5400     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5401     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5402     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5403     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5404     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5405     do {
5406         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5407         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5408         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5409         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5410         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5411         if(!step) step = -1;
5412     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5413             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5414             promoRestrict[0] ? !strchr(promoRestrict, ToUpper(PieceToChar(promoSweep))) : // if choice set available, use it 
5415             promoSweep == pawn ||
5416             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5417             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5418     if(toX >= 0) {
5419         int victim = boards[currentMove][toY][toX];
5420         boards[currentMove][toY][toX] = promoSweep;
5421         DrawPosition(FALSE, boards[currentMove]);
5422         boards[currentMove][toY][toX] = victim;
5423     } else
5424     ChangeDragPiece(promoSweep);
5425 }
5426
5427 int
5428 PromoScroll (int x, int y)
5429 {
5430   int step = 0;
5431
5432   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5433   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5434   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5435   if(!step) return FALSE;
5436   lastX = x; lastY = y;
5437   if((promoSweep < BlackPawn) == flipView) step = -step;
5438   if(step > 0) selectFlag = 1;
5439   if(!selectFlag) Sweep(step);
5440   return FALSE;
5441 }
5442
5443 void
5444 NextPiece (int step)
5445 {
5446     ChessSquare piece = boards[currentMove][toY][toX];
5447     do {
5448         pieceSweep -= step;
5449         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5450         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5451         if(!step) step = -1;
5452     } while(PieceToChar(pieceSweep) == '.');
5453     boards[currentMove][toY][toX] = pieceSweep;
5454     DrawPosition(FALSE, boards[currentMove]);
5455     boards[currentMove][toY][toX] = piece;
5456 }
5457 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5458 void
5459 AlphaRank (char *move, int n)
5460 {
5461 //    char *p = move, c; int x, y;
5462
5463     if (appData.debugMode) {
5464         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5465     }
5466
5467     if(move[1]=='*' &&
5468        move[2]>='0' && move[2]<='9' &&
5469        move[3]>='a' && move[3]<='x'    ) {
5470         move[1] = '@';
5471         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5472         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5473     } else
5474     if(move[0]>='0' && move[0]<='9' &&
5475        move[1]>='a' && move[1]<='x' &&
5476        move[2]>='0' && move[2]<='9' &&
5477        move[3]>='a' && move[3]<='x'    ) {
5478         /* input move, Shogi -> normal */
5479         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5480         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5481         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5482         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5483     } else
5484     if(move[1]=='@' &&
5485        move[3]>='0' && move[3]<='9' &&
5486        move[2]>='a' && move[2]<='x'    ) {
5487         move[1] = '*';
5488         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5489         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5490     } else
5491     if(
5492        move[0]>='a' && move[0]<='x' &&
5493        move[3]>='0' && move[3]<='9' &&
5494        move[2]>='a' && move[2]<='x'    ) {
5495          /* output move, normal -> Shogi */
5496         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5497         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5498         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5499         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5500         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5501     }
5502     if (appData.debugMode) {
5503         fprintf(debugFP, "   out = '%s'\n", move);
5504     }
5505 }
5506
5507 char yy_textstr[8000];
5508
5509 /* Parser for moves from gnuchess, ICS, or user typein box */
5510 Boolean
5511 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5512 {
5513     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5514
5515     switch (*moveType) {
5516       case WhitePromotion:
5517       case BlackPromotion:
5518       case WhiteNonPromotion:
5519       case BlackNonPromotion:
5520       case NormalMove:
5521       case FirstLeg:
5522       case WhiteCapturesEnPassant:
5523       case BlackCapturesEnPassant:
5524       case WhiteKingSideCastle:
5525       case WhiteQueenSideCastle:
5526       case BlackKingSideCastle:
5527       case BlackQueenSideCastle:
5528       case WhiteKingSideCastleWild:
5529       case WhiteQueenSideCastleWild:
5530       case BlackKingSideCastleWild:
5531       case BlackQueenSideCastleWild:
5532       /* Code added by Tord: */
5533       case WhiteHSideCastleFR:
5534       case WhiteASideCastleFR:
5535       case BlackHSideCastleFR:
5536       case BlackASideCastleFR:
5537       /* End of code added by Tord */
5538       case IllegalMove:         /* bug or odd chess variant */
5539         if(currentMoveString[1] == '@') { // illegal drop
5540           *fromX = WhiteOnMove(moveNum) ?
5541             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5542             (int) CharToPiece(ToLower(currentMoveString[0]));
5543           goto drop;
5544         }
5545         *fromX = currentMoveString[0] - AAA;
5546         *fromY = currentMoveString[1] - ONE;
5547         *toX = currentMoveString[2] - AAA;
5548         *toY = currentMoveString[3] - ONE;
5549         *promoChar = currentMoveString[4];
5550         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5551             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5552     if (appData.debugMode) {
5553         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5554     }
5555             *fromX = *fromY = *toX = *toY = 0;
5556             return FALSE;
5557         }
5558         if (appData.testLegality) {
5559           return (*moveType != IllegalMove);
5560         } else {
5561           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5562                          // [HGM] lion: if this is a double move we are less critical
5563                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5564         }
5565
5566       case WhiteDrop:
5567       case BlackDrop:
5568         *fromX = *moveType == WhiteDrop ?
5569           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5570           (int) CharToPiece(ToLower(currentMoveString[0]));
5571       drop:
5572         *fromY = DROP_RANK;
5573         *toX = currentMoveString[2] - AAA;
5574         *toY = currentMoveString[3] - ONE;
5575         *promoChar = NULLCHAR;
5576         return TRUE;
5577
5578       case AmbiguousMove:
5579       case ImpossibleMove:
5580       case EndOfFile:
5581       case ElapsedTime:
5582       case Comment:
5583       case PGNTag:
5584       case NAG:
5585       case WhiteWins:
5586       case BlackWins:
5587       case GameIsDrawn:
5588       default:
5589     if (appData.debugMode) {
5590         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5591     }
5592         /* bug? */
5593         *fromX = *fromY = *toX = *toY = 0;
5594         *promoChar = NULLCHAR;
5595         return FALSE;
5596     }
5597 }
5598
5599 Boolean pushed = FALSE;
5600 char *lastParseAttempt;
5601
5602 void
5603 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5604 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5605   int fromX, fromY, toX, toY; char promoChar;
5606   ChessMove moveType;
5607   Boolean valid;
5608   int nr = 0;
5609
5610   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5611   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5612     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5613     pushed = TRUE;
5614   }
5615   endPV = forwardMostMove;
5616   do {
5617     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5618     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5619     lastParseAttempt = pv;
5620     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5621     if(!valid && nr == 0 &&
5622        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5623         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5624         // Hande case where played move is different from leading PV move
5625         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5626         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5627         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5628         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5629           endPV += 2; // if position different, keep this
5630           moveList[endPV-1][0] = fromX + AAA;
5631           moveList[endPV-1][1] = fromY + ONE;
5632           moveList[endPV-1][2] = toX + AAA;
5633           moveList[endPV-1][3] = toY + ONE;
5634           parseList[endPV-1][0] = NULLCHAR;
5635           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5636         }
5637       }
5638     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5639     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5640     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5641     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5642         valid++; // allow comments in PV
5643         continue;
5644     }
5645     nr++;
5646     if(endPV+1 > framePtr) break; // no space, truncate
5647     if(!valid) break;
5648     endPV++;
5649     CopyBoard(boards[endPV], boards[endPV-1]);
5650     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5651     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5652     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5653     CoordsToAlgebraic(boards[endPV - 1],
5654                              PosFlags(endPV - 1),
5655                              fromY, fromX, toY, toX, promoChar,
5656                              parseList[endPV - 1]);
5657   } while(valid);
5658   if(atEnd == 2) return; // used hidden, for PV conversion
5659   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5660   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5661   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5662                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5663   DrawPosition(TRUE, boards[currentMove]);
5664 }
5665
5666 int
5667 MultiPV (ChessProgramState *cps, int kind)
5668 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5669         int i;
5670         for(i=0; i<cps->nrOptions; i++) {
5671             char *s = cps->option[i].name;
5672             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5673             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5674                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5675         }
5676         return -1;
5677 }
5678
5679 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5680 static int multi, pv_margin;
5681 static ChessProgramState *activeCps;
5682
5683 Boolean
5684 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5685 {
5686         int startPV, lineStart, origIndex = index;
5687         char *p, buf2[MSG_SIZ];
5688         ChessProgramState *cps = (pane ? &second : &first);
5689
5690         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5691         lastX = x; lastY = y;
5692         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5693         lineStart = startPV = index;
5694         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5695         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5696         index = startPV;
5697         do{ while(buf[index] && buf[index] != '\n') index++;
5698         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5699         buf[index] = 0;
5700         if(lineStart == 0 && gameMode == AnalyzeMode) {
5701             int n = 0;
5702             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5703             if(n == 0) { // click not on "fewer" or "more"
5704                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5705                     pv_margin = cps->option[multi].value;
5706                     activeCps = cps; // non-null signals margin adjustment
5707                 }
5708             } else if((multi = MultiPV(cps, 1)) >= 0) {
5709                 n += cps->option[multi].value; if(n < 1) n = 1;
5710                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5711                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5712                 cps->option[multi].value = n;
5713                 *start = *end = 0;
5714                 return FALSE;
5715             }
5716         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5717                 ExcludeClick(origIndex - lineStart);
5718                 return FALSE;
5719         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5720                 Collapse(origIndex - lineStart);
5721                 return FALSE;
5722         }
5723         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5724         *start = startPV; *end = index-1;
5725         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5726         return TRUE;
5727 }
5728
5729 char *
5730 PvToSAN (char *pv)
5731 {
5732         static char buf[10*MSG_SIZ];
5733         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5734         *buf = NULLCHAR;
5735         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5736         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5737         for(i = forwardMostMove; i<endPV; i++){
5738             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5739             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5740             k += strlen(buf+k);
5741         }
5742         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5743         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5744         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5745         endPV = savedEnd;
5746         return buf;
5747 }
5748
5749 Boolean
5750 LoadPV (int x, int y)
5751 { // called on right mouse click to load PV
5752   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5753   lastX = x; lastY = y;
5754   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5755   extendGame = FALSE;
5756   return TRUE;
5757 }
5758
5759 void
5760 UnLoadPV ()
5761 {
5762   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5763   if(activeCps) {
5764     if(pv_margin != activeCps->option[multi].value) {
5765       char buf[MSG_SIZ];
5766       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5767       SendToProgram(buf, activeCps);
5768       activeCps->option[multi].value = pv_margin;
5769     }
5770     activeCps = NULL;
5771     return;
5772   }
5773   if(endPV < 0) return;
5774   if(appData.autoCopyPV) CopyFENToClipboard();
5775   endPV = -1;
5776   if(extendGame && currentMove > forwardMostMove) {
5777         Boolean saveAnimate = appData.animate;
5778         if(pushed) {
5779             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5780                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5781             } else storedGames--; // abandon shelved tail of original game
5782         }
5783         pushed = FALSE;
5784         forwardMostMove = currentMove;
5785         currentMove = oldFMM;
5786         appData.animate = FALSE;
5787         ToNrEvent(forwardMostMove);
5788         appData.animate = saveAnimate;
5789   }
5790   currentMove = forwardMostMove;
5791   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5792   ClearPremoveHighlights();
5793   DrawPosition(TRUE, boards[currentMove]);
5794 }
5795
5796 void
5797 MovePV (int x, int y, int h)
5798 { // step through PV based on mouse coordinates (called on mouse move)
5799   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5800
5801   if(activeCps) { // adjusting engine's multi-pv margin
5802     if(x > lastX) pv_margin++; else
5803     if(x < lastX) pv_margin -= (pv_margin > 0);
5804     if(x != lastX) {
5805       char buf[MSG_SIZ];
5806       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5807       DisplayMessage(buf, "");
5808     }
5809     lastX = x;
5810     return;
5811   }
5812   // we must somehow check if right button is still down (might be released off board!)
5813   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5814   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5815   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5816   if(!step) return;
5817   lastX = x; lastY = y;
5818
5819   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5820   if(endPV < 0) return;
5821   if(y < margin) step = 1; else
5822   if(y > h - margin) step = -1;
5823   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5824   currentMove += step;
5825   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5826   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5827                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5828   DrawPosition(FALSE, boards[currentMove]);
5829 }
5830
5831
5832 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5833 // All positions will have equal probability, but the current method will not provide a unique
5834 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5835 #define DARK 1
5836 #define LITE 2
5837 #define ANY 3
5838
5839 int squaresLeft[4];
5840 int piecesLeft[(int)BlackPawn];
5841 int seed, nrOfShuffles;
5842
5843 void
5844 GetPositionNumber ()
5845 {       // sets global variable seed
5846         int i;
5847
5848         seed = appData.defaultFrcPosition;
5849         if(seed < 0) { // randomize based on time for negative FRC position numbers
5850                 for(i=0; i<50; i++) seed += random();
5851                 seed = random() ^ random() >> 8 ^ random() << 8;
5852                 if(seed<0) seed = -seed;
5853         }
5854 }
5855
5856 int
5857 put (Board board, int pieceType, int rank, int n, int shade)
5858 // put the piece on the (n-1)-th empty squares of the given shade
5859 {
5860         int i;
5861
5862         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5863                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5864                         board[rank][i] = (ChessSquare) pieceType;
5865                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5866                         squaresLeft[ANY]--;
5867                         piecesLeft[pieceType]--;
5868                         return i;
5869                 }
5870         }
5871         return -1;
5872 }
5873
5874
5875 void
5876 AddOnePiece (Board board, int pieceType, int rank, int shade)
5877 // calculate where the next piece goes, (any empty square), and put it there
5878 {
5879         int i;
5880
5881         i = seed % squaresLeft[shade];
5882         nrOfShuffles *= squaresLeft[shade];
5883         seed /= squaresLeft[shade];
5884         put(board, pieceType, rank, i, shade);
5885 }
5886
5887 void
5888 AddTwoPieces (Board board, int pieceType, int rank)
5889 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5890 {
5891         int i, n=squaresLeft[ANY], j=n-1, k;
5892
5893         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5894         i = seed % k;  // pick one
5895         nrOfShuffles *= k;
5896         seed /= k;
5897         while(i >= j) i -= j--;
5898         j = n - 1 - j; i += j;
5899         put(board, pieceType, rank, j, ANY);
5900         put(board, pieceType, rank, i, ANY);
5901 }
5902
5903 void
5904 SetUpShuffle (Board board, int number)
5905 {
5906         int i, p, first=1;
5907
5908         GetPositionNumber(); nrOfShuffles = 1;
5909
5910         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5911         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5912         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5913
5914         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5915
5916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5917             p = (int) board[0][i];
5918             if(p < (int) BlackPawn) piecesLeft[p] ++;
5919             board[0][i] = EmptySquare;
5920         }
5921
5922         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5923             // shuffles restricted to allow normal castling put KRR first
5924             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5925                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5926             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5927                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5928             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5929                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5930             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5931                 put(board, WhiteRook, 0, 0, ANY);
5932             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5933         }
5934
5935         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5936             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5937             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5938                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5939                 while(piecesLeft[p] >= 2) {
5940                     AddOnePiece(board, p, 0, LITE);
5941                     AddOnePiece(board, p, 0, DARK);
5942                 }
5943                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5944             }
5945
5946         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5947             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5948             // but we leave King and Rooks for last, to possibly obey FRC restriction
5949             if(p == (int)WhiteRook) continue;
5950             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5951             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5952         }
5953
5954         // now everything is placed, except perhaps King (Unicorn) and Rooks
5955
5956         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5957             // Last King gets castling rights
5958             while(piecesLeft[(int)WhiteUnicorn]) {
5959                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5960                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5961             }
5962
5963             while(piecesLeft[(int)WhiteKing]) {
5964                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5965                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5966             }
5967
5968
5969         } else {
5970             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5971             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5972         }
5973
5974         // Only Rooks can be left; simply place them all
5975         while(piecesLeft[(int)WhiteRook]) {
5976                 i = put(board, WhiteRook, 0, 0, ANY);
5977                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5978                         if(first) {
5979                                 first=0;
5980                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5981                         }
5982                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5983                 }
5984         }
5985         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5986             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5987         }
5988
5989         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5990 }
5991
5992 int
5993 ptclen (const char *s, char *escapes)
5994 {
5995     int n = 0;
5996     if(!*escapes) return strlen(s);
5997     while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
5998     return n;
5999 }
6000
6001 int
6002 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6003 /* [HGM] moved here from winboard.c because of its general usefulness */
6004 /*       Basically a safe strcpy that uses the last character as King */
6005 {
6006     int result = FALSE; int NrPieces, offs;
6007
6008     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6009                     && NrPieces >= 12 && !(NrPieces&1)) {
6010         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
6011
6012         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6013         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6014             char *p;
6015             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6016             table[i + offs] = map[j++];
6017             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
6018         }
6019         table[(int) WhiteKing]  = map[j++];
6020         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6021             char *p;
6022             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
6023             table[WHITE_TO_BLACK i + offs] = map[j++];
6024             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
6025         }
6026         table[(int) BlackKing]  = map[j++];
6027
6028         result = TRUE;
6029     }
6030
6031     return result;
6032 }
6033
6034 int
6035 SetCharTable (unsigned char *table, const char * map)
6036 {
6037     return SetCharTableEsc(table, map, "");
6038 }
6039
6040 void
6041 Prelude (Board board)
6042 {       // [HGM] superchess: random selection of exo-pieces
6043         int i, j, k; ChessSquare p;
6044         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6045
6046         GetPositionNumber(); // use FRC position number
6047
6048         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6049             SetCharTable(pieceToChar, appData.pieceToCharTable);
6050             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6051                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6052         }
6053
6054         j = seed%4;                 seed /= 4;
6055         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6056         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6057         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6058         j = seed%3 + (seed%3 >= j); seed /= 3;
6059         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6060         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6061         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6062         j = seed%3;                 seed /= 3;
6063         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6064         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6065         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6066         j = seed%2 + (seed%2 >= j); seed /= 2;
6067         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6068         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6069         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6070         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6071         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6072         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6073         put(board, exoPieces[0],    0, 0, ANY);
6074         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6075 }
6076
6077 void
6078 InitPosition (int redraw)
6079 {
6080     ChessSquare (* pieces)[BOARD_FILES];
6081     int i, j, pawnRow=1, pieceRows=1, overrule,
6082     oldx = gameInfo.boardWidth,
6083     oldy = gameInfo.boardHeight,
6084     oldh = gameInfo.holdingsWidth;
6085     static int oldv;
6086
6087     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6088
6089     /* [AS] Initialize pv info list [HGM] and game status */
6090     {
6091         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6092             pvInfoList[i].depth = 0;
6093             boards[i][EP_STATUS] = EP_NONE;
6094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6095         }
6096
6097         initialRulePlies = 0; /* 50-move counter start */
6098
6099         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6100         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6101     }
6102
6103
6104     /* [HGM] logic here is completely changed. In stead of full positions */
6105     /* the initialized data only consist of the two backranks. The switch */
6106     /* selects which one we will use, which is than copied to the Board   */
6107     /* initialPosition, which for the rest is initialized by Pawns and    */
6108     /* empty squares. This initial position is then copied to boards[0],  */
6109     /* possibly after shuffling, so that it remains available.            */
6110
6111     gameInfo.holdingsWidth = 0; /* default board sizes */
6112     gameInfo.boardWidth    = 8;
6113     gameInfo.boardHeight   = 8;
6114     gameInfo.holdingsSize  = 0;
6115     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6116     for(i=0; i<BOARD_FILES-6; i++)
6117       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6118     initialPosition[EP_STATUS] = EP_NONE;
6119     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6120     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6121     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6122          SetCharTable(pieceNickName, appData.pieceNickNames);
6123     else SetCharTable(pieceNickName, "............");
6124     pieces = FIDEArray;
6125
6126     switch (gameInfo.variant) {
6127     case VariantFischeRandom:
6128       shuffleOpenings = TRUE;
6129       appData.fischerCastling = TRUE;
6130     default:
6131       break;
6132     case VariantShatranj:
6133       pieces = ShatranjArray;
6134       nrCastlingRights = 0;
6135       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6136       break;
6137     case VariantMakruk:
6138       pieces = makrukArray;
6139       nrCastlingRights = 0;
6140       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6141       break;
6142     case VariantASEAN:
6143       pieces = aseanArray;
6144       nrCastlingRights = 0;
6145       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6146       break;
6147     case VariantTwoKings:
6148       pieces = twoKingsArray;
6149       break;
6150     case VariantGrand:
6151       pieces = GrandArray;
6152       nrCastlingRights = 0;
6153       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6154       gameInfo.boardWidth = 10;
6155       gameInfo.boardHeight = 10;
6156       gameInfo.holdingsSize = 7;
6157       break;
6158     case VariantCapaRandom:
6159       shuffleOpenings = TRUE;
6160       appData.fischerCastling = TRUE;
6161     case VariantCapablanca:
6162       pieces = CapablancaArray;
6163       gameInfo.boardWidth = 10;
6164       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6165       break;
6166     case VariantGothic:
6167       pieces = GothicArray;
6168       gameInfo.boardWidth = 10;
6169       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6170       break;
6171     case VariantSChess:
6172       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6173       gameInfo.holdingsSize = 7;
6174       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6175       break;
6176     case VariantJanus:
6177       pieces = JanusArray;
6178       gameInfo.boardWidth = 10;
6179       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6180       nrCastlingRights = 6;
6181         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6182         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6183         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6184         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6185         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6186         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6187       break;
6188     case VariantFalcon:
6189       pieces = FalconArray;
6190       gameInfo.boardWidth = 10;
6191       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6192       break;
6193     case VariantXiangqi:
6194       pieces = XiangqiArray;
6195       gameInfo.boardWidth  = 9;
6196       gameInfo.boardHeight = 10;
6197       nrCastlingRights = 0;
6198       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6199       break;
6200     case VariantShogi:
6201       pieces = ShogiArray;
6202       gameInfo.boardWidth  = 9;
6203       gameInfo.boardHeight = 9;
6204       gameInfo.holdingsSize = 7;
6205       nrCastlingRights = 0;
6206       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6207       break;
6208     case VariantChu:
6209       pieces = ChuArray; pieceRows = 3;
6210       gameInfo.boardWidth  = 12;
6211       gameInfo.boardHeight = 12;
6212       nrCastlingRights = 0;
6213       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
6214                                    "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
6215       break;
6216     case VariantCourier:
6217       pieces = CourierArray;
6218       gameInfo.boardWidth  = 12;
6219       nrCastlingRights = 0;
6220       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6221       break;
6222     case VariantKnightmate:
6223       pieces = KnightmateArray;
6224       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6225       break;
6226     case VariantSpartan:
6227       pieces = SpartanArray;
6228       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6229       break;
6230     case VariantLion:
6231       pieces = lionArray;
6232       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6233       break;
6234     case VariantChuChess:
6235       pieces = ChuChessArray;
6236       gameInfo.boardWidth = 10;
6237       gameInfo.boardHeight = 10;
6238       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6239       break;
6240     case VariantFairy:
6241       pieces = fairyArray;
6242       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6243       break;
6244     case VariantGreat:
6245       pieces = GreatArray;
6246       gameInfo.boardWidth = 10;
6247       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6248       gameInfo.holdingsSize = 8;
6249       break;
6250     case VariantSuper:
6251       pieces = FIDEArray;
6252       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6253       gameInfo.holdingsSize = 8;
6254       startedFromSetupPosition = TRUE;
6255       break;
6256     case VariantCrazyhouse:
6257     case VariantBughouse:
6258       pieces = FIDEArray;
6259       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6260       gameInfo.holdingsSize = 5;
6261       break;
6262     case VariantWildCastle:
6263       pieces = FIDEArray;
6264       /* !!?shuffle with kings guaranteed to be on d or e file */
6265       shuffleOpenings = 1;
6266       break;
6267     case VariantNoCastle:
6268       pieces = FIDEArray;
6269       nrCastlingRights = 0;
6270       /* !!?unconstrained back-rank shuffle */
6271       shuffleOpenings = 1;
6272       break;
6273     }
6274
6275     overrule = 0;
6276     if(appData.NrFiles >= 0) {
6277         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6278         gameInfo.boardWidth = appData.NrFiles;
6279     }
6280     if(appData.NrRanks >= 0) {
6281         gameInfo.boardHeight = appData.NrRanks;
6282     }
6283     if(appData.holdingsSize >= 0) {
6284         i = appData.holdingsSize;
6285         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6286         gameInfo.holdingsSize = i;
6287     }
6288     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6289     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6290         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6291
6292     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6293     if(pawnRow < 1) pawnRow = 1;
6294     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6295        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6296     if(gameInfo.variant == VariantChu) pawnRow = 3;
6297
6298     /* User pieceToChar list overrules defaults */
6299     if(appData.pieceToCharTable != NULL)
6300         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6301
6302     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6303
6304         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6305             s = (ChessSquare) 0; /* account holding counts in guard band */
6306         for( i=0; i<BOARD_HEIGHT; i++ )
6307             initialPosition[i][j] = s;
6308
6309         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6310         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6311         initialPosition[pawnRow][j] = WhitePawn;
6312         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6313         if(gameInfo.variant == VariantXiangqi) {
6314             if(j&1) {
6315                 initialPosition[pawnRow][j] =
6316                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6317                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6318                    initialPosition[2][j] = WhiteCannon;
6319                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6320                 }
6321             }
6322         }
6323         if(gameInfo.variant == VariantChu) {
6324              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6325                initialPosition[pawnRow+1][j] = WhiteCobra,
6326                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6327              for(i=1; i<pieceRows; i++) {
6328                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6329                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6330              }
6331         }
6332         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6333             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6334                initialPosition[0][j] = WhiteRook;
6335                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6336             }
6337         }
6338         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6339     }
6340     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6341     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6342
6343             j=BOARD_LEFT+1;
6344             initialPosition[1][j] = WhiteBishop;
6345             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6346             j=BOARD_RGHT-2;
6347             initialPosition[1][j] = WhiteRook;
6348             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6349     }
6350
6351     if( nrCastlingRights == -1) {
6352         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6353         /*       This sets default castling rights from none to normal corners   */
6354         /* Variants with other castling rights must set them themselves above    */
6355         nrCastlingRights = 6;
6356
6357         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6358         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6359         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6360         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6361         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6362         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6363      }
6364
6365      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6366      if(gameInfo.variant == VariantGreat) { // promotion commoners
6367         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6368         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6369         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6370         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6371      }
6372      if( gameInfo.variant == VariantSChess ) {
6373       initialPosition[1][0] = BlackMarshall;
6374       initialPosition[2][0] = BlackAngel;
6375       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6376       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6377       initialPosition[1][1] = initialPosition[2][1] =
6378       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6379      }
6380   if (appData.debugMode) {
6381     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6382   }
6383     if(shuffleOpenings) {
6384         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6385         startedFromSetupPosition = TRUE;
6386     }
6387     if(startedFromPositionFile) {
6388       /* [HGM] loadPos: use PositionFile for every new game */
6389       CopyBoard(initialPosition, filePosition);
6390       for(i=0; i<nrCastlingRights; i++)
6391           initialRights[i] = filePosition[CASTLING][i];
6392       startedFromSetupPosition = TRUE;
6393     }
6394
6395     CopyBoard(boards[0], initialPosition);
6396
6397     if(oldx != gameInfo.boardWidth ||
6398        oldy != gameInfo.boardHeight ||
6399        oldv != gameInfo.variant ||
6400        oldh != gameInfo.holdingsWidth
6401                                          )
6402             InitDrawingSizes(-2 ,0);
6403
6404     oldv = gameInfo.variant;
6405     if (redraw)
6406       DrawPosition(TRUE, boards[currentMove]);
6407 }
6408
6409 void
6410 SendBoard (ChessProgramState *cps, int moveNum)
6411 {
6412     char message[MSG_SIZ];
6413
6414     if (cps->useSetboard) {
6415       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6416       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6417       SendToProgram(message, cps);
6418       free(fen);
6419
6420     } else {
6421       ChessSquare *bp;
6422       int i, j, left=0, right=BOARD_WIDTH;
6423       /* Kludge to set black to move, avoiding the troublesome and now
6424        * deprecated "black" command.
6425        */
6426       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6427         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6428
6429       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6430
6431       SendToProgram("edit\n", cps);
6432       SendToProgram("#\n", cps);
6433       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6434         bp = &boards[moveNum][i][left];
6435         for (j = left; j < right; j++, bp++) {
6436           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6437           if ((int) *bp < (int) BlackPawn) {
6438             if(j == BOARD_RGHT+1)
6439                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6440             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6441             if(message[0] == '+' || message[0] == '~') {
6442               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6443                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6444                         AAA + j, ONE + i - '0');
6445             }
6446             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6447                 message[1] = BOARD_RGHT   - 1 - j + '1';
6448                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6449             }
6450             SendToProgram(message, cps);
6451           }
6452         }
6453       }
6454
6455       SendToProgram("c\n", cps);
6456       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6457         bp = &boards[moveNum][i][left];
6458         for (j = left; j < right; j++, bp++) {
6459           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6460           if (((int) *bp != (int) EmptySquare)
6461               && ((int) *bp >= (int) BlackPawn)) {
6462             if(j == BOARD_LEFT-2)
6463                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6464             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6465                     AAA + j, ONE + i - '0');
6466             if(message[0] == '+' || message[0] == '~') {
6467               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6468                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6469                         AAA + j, ONE + i - '0');
6470             }
6471             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6472                 message[1] = BOARD_RGHT   - 1 - j + '1';
6473                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6474             }
6475             SendToProgram(message, cps);
6476           }
6477         }
6478       }
6479
6480       SendToProgram(".\n", cps);
6481     }
6482     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6483 }
6484
6485 char exclusionHeader[MSG_SIZ];
6486 int exCnt, excludePtr;
6487 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6488 static Exclusion excluTab[200];
6489 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6490
6491 static void
6492 WriteMap (int s)
6493 {
6494     int j;
6495     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6496     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6497 }
6498
6499 static void
6500 ClearMap ()
6501 {
6502     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6503     excludePtr = 24; exCnt = 0;
6504     WriteMap(0);
6505 }
6506
6507 static void
6508 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6509 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6510     char buf[2*MOVE_LEN], *p;
6511     Exclusion *e = excluTab;
6512     int i;
6513     for(i=0; i<exCnt; i++)
6514         if(e[i].ff == fromX && e[i].fr == fromY &&
6515            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6516     if(i == exCnt) { // was not in exclude list; add it
6517         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6518         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6519             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6520             return; // abort
6521         }
6522         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6523         excludePtr++; e[i].mark = excludePtr++;
6524         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6525         exCnt++;
6526     }
6527     exclusionHeader[e[i].mark] = state;
6528 }
6529
6530 static int
6531 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6532 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6533     char buf[MSG_SIZ];
6534     int j, k;
6535     ChessMove moveType;
6536     if((signed char)promoChar == -1) { // kludge to indicate best move
6537         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6538             return 1; // if unparsable, abort
6539     }
6540     // update exclusion map (resolving toggle by consulting existing state)
6541     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6542     j = k%8; k >>= 3;
6543     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6544     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6545          excludeMap[k] |=   1<<j;
6546     else excludeMap[k] &= ~(1<<j);
6547     // update header
6548     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6549     // inform engine
6550     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6551     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6552     SendToBoth(buf);
6553     return (state == '+');
6554 }
6555
6556 static void
6557 ExcludeClick (int index)
6558 {
6559     int i, j;
6560     Exclusion *e = excluTab;
6561     if(index < 25) { // none, best or tail clicked
6562         if(index < 13) { // none: include all
6563             WriteMap(0); // clear map
6564             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6565             SendToBoth("include all\n"); // and inform engine
6566         } else if(index > 18) { // tail
6567             if(exclusionHeader[19] == '-') { // tail was excluded
6568                 SendToBoth("include all\n");
6569                 WriteMap(0); // clear map completely
6570                 // now re-exclude selected moves
6571                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6572                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6573             } else { // tail was included or in mixed state
6574                 SendToBoth("exclude all\n");
6575                 WriteMap(0xFF); // fill map completely
6576                 // now re-include selected moves
6577                 j = 0; // count them
6578                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6579                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6580                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6581             }
6582         } else { // best
6583             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6584         }
6585     } else {
6586         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6587             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6588             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6589             break;
6590         }
6591     }
6592 }
6593
6594 ChessSquare
6595 DefaultPromoChoice (int white)
6596 {
6597     ChessSquare result;
6598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6599        gameInfo.variant == VariantMakruk)
6600         result = WhiteFerz; // no choice
6601     else if(gameInfo.variant == VariantASEAN)
6602         result = WhiteRook; // no choice
6603     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6604         result= WhiteKing; // in Suicide Q is the last thing we want
6605     else if(gameInfo.variant == VariantSpartan)
6606         result = white ? WhiteQueen : WhiteAngel;
6607     else result = WhiteQueen;
6608     if(!white) result = WHITE_TO_BLACK result;
6609     return result;
6610 }
6611
6612 static int autoQueen; // [HGM] oneclick
6613
6614 int
6615 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6616 {
6617     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6618     /* [HGM] add Shogi promotions */
6619     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6620     ChessSquare piece, partner;
6621     ChessMove moveType;
6622     Boolean premove;
6623
6624     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6625     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6626
6627     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6628       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6629         return FALSE;
6630
6631     piece = boards[currentMove][fromY][fromX];
6632     if(gameInfo.variant == VariantChu) {
6633         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6634         promotionZoneSize = BOARD_HEIGHT/3;
6635         highestPromotingPiece = (p >= WhiteTokin || PieceToChar(piece + WhiteTokin) != '+') ? WhitePawn : WhiteTokin-1;
6636     } else if(gameInfo.variant == VariantShogi) {
6637         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6638         highestPromotingPiece = (int)WhiteAlfil;
6639     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6640         promotionZoneSize = 3;
6641     }
6642
6643     // Treat Lance as Pawn when it is not representing Amazon or Lance
6644     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6645         if(piece == WhiteLance) piece = WhitePawn; else
6646         if(piece == BlackLance) piece = BlackPawn;
6647     }
6648
6649     // next weed out all moves that do not touch the promotion zone at all
6650     if((int)piece >= BlackPawn) {
6651         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6652              return FALSE;
6653         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6654         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6655     } else {
6656         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6657            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6658         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6659              return FALSE;
6660     }
6661
6662     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6663
6664     // weed out mandatory Shogi promotions
6665     if(gameInfo.variant == VariantShogi) {
6666         if(piece >= BlackPawn) {
6667             if(toY == 0 && piece == BlackPawn ||
6668                toY == 0 && piece == BlackQueen ||
6669                toY <= 1 && piece == BlackKnight) {
6670                 *promoChoice = '+';
6671                 return FALSE;
6672             }
6673         } else {
6674             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6675                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6676                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6677                 *promoChoice = '+';
6678                 return FALSE;
6679             }
6680         }
6681     }
6682
6683     // weed out obviously illegal Pawn moves
6684     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6685         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6686         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6687         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6688         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6689         // note we are not allowed to test for valid (non-)capture, due to premove
6690     }
6691
6692     // we either have a choice what to promote to, or (in Shogi) whether to promote
6693     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6694        gameInfo.variant == VariantMakruk) {
6695         ChessSquare p=BlackFerz;  // no choice
6696         while(p < EmptySquare) {  //but make sure we use piece that exists
6697             *promoChoice = PieceToChar(p++);
6698             if(*promoChoice != '.') break;
6699         }
6700         return FALSE;
6701     }
6702     // no sense asking what we must promote to if it is going to explode...
6703     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6704         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6705         return FALSE;
6706     }
6707     // give caller the default choice even if we will not make it
6708     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6709     partner = piece; // pieces can promote if the pieceToCharTable says so
6710     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6711     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6712     if(        sweepSelect && gameInfo.variant != VariantGreat
6713                            && gameInfo.variant != VariantGrand
6714                            && gameInfo.variant != VariantSuper) return FALSE;
6715     if(autoQueen) return FALSE; // predetermined
6716
6717     // suppress promotion popup on illegal moves that are not premoves
6718     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6719               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6720     if(appData.testLegality && !premove) {
6721         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6722                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6723         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6724         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6725             return FALSE;
6726     }
6727
6728     return TRUE;
6729 }
6730
6731 int
6732 InPalace (int row, int column)
6733 {   /* [HGM] for Xiangqi */
6734     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6735          column < (BOARD_WIDTH + 4)/2 &&
6736          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6737     return FALSE;
6738 }
6739
6740 int
6741 PieceForSquare (int x, int y)
6742 {
6743   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6744      return -1;
6745   else
6746      return boards[currentMove][y][x];
6747 }
6748
6749 int
6750 OKToStartUserMove (int x, int y)
6751 {
6752     ChessSquare from_piece;
6753     int white_piece;
6754
6755     if (matchMode) return FALSE;
6756     if (gameMode == EditPosition) return TRUE;
6757
6758     if (x >= 0 && y >= 0)
6759       from_piece = boards[currentMove][y][x];
6760     else
6761       from_piece = EmptySquare;
6762
6763     if (from_piece == EmptySquare) return FALSE;
6764
6765     white_piece = (int)from_piece >= (int)WhitePawn &&
6766       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6767
6768     switch (gameMode) {
6769       case AnalyzeFile:
6770       case TwoMachinesPlay:
6771       case EndOfGame:
6772         return FALSE;
6773
6774       case IcsObserving:
6775       case IcsIdle:
6776         return FALSE;
6777
6778       case MachinePlaysWhite:
6779       case IcsPlayingBlack:
6780         if (appData.zippyPlay) return FALSE;
6781         if (white_piece) {
6782             DisplayMoveError(_("You are playing Black"));
6783             return FALSE;
6784         }
6785         break;
6786
6787       case MachinePlaysBlack:
6788       case IcsPlayingWhite:
6789         if (appData.zippyPlay) return FALSE;
6790         if (!white_piece) {
6791             DisplayMoveError(_("You are playing White"));
6792             return FALSE;
6793         }
6794         break;
6795
6796       case PlayFromGameFile:
6797             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6798       case EditGame:
6799         if (!white_piece && WhiteOnMove(currentMove)) {
6800             DisplayMoveError(_("It is White's turn"));
6801             return FALSE;
6802         }
6803         if (white_piece && !WhiteOnMove(currentMove)) {
6804             DisplayMoveError(_("It is Black's turn"));
6805             return FALSE;
6806         }
6807         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6808             /* Editing correspondence game history */
6809             /* Could disallow this or prompt for confirmation */
6810             cmailOldMove = -1;
6811         }
6812         break;
6813
6814       case BeginningOfGame:
6815         if (appData.icsActive) return FALSE;
6816         if (!appData.noChessProgram) {
6817             if (!white_piece) {
6818                 DisplayMoveError(_("You are playing White"));
6819                 return FALSE;
6820             }
6821         }
6822         break;
6823
6824       case Training:
6825         if (!white_piece && WhiteOnMove(currentMove)) {
6826             DisplayMoveError(_("It is White's turn"));
6827             return FALSE;
6828         }
6829         if (white_piece && !WhiteOnMove(currentMove)) {
6830             DisplayMoveError(_("It is Black's turn"));
6831             return FALSE;
6832         }
6833         break;
6834
6835       default:
6836       case IcsExamining:
6837         break;
6838     }
6839     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6840         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6841         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6842         && gameMode != AnalyzeFile && gameMode != Training) {
6843         DisplayMoveError(_("Displayed position is not current"));
6844         return FALSE;
6845     }
6846     return TRUE;
6847 }
6848
6849 Boolean
6850 OnlyMove (int *x, int *y, Boolean captures)
6851 {
6852     DisambiguateClosure cl;
6853     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6854     switch(gameMode) {
6855       case MachinePlaysBlack:
6856       case IcsPlayingWhite:
6857       case BeginningOfGame:
6858         if(!WhiteOnMove(currentMove)) return FALSE;
6859         break;
6860       case MachinePlaysWhite:
6861       case IcsPlayingBlack:
6862         if(WhiteOnMove(currentMove)) return FALSE;
6863         break;
6864       case EditGame:
6865         break;
6866       default:
6867         return FALSE;
6868     }
6869     cl.pieceIn = EmptySquare;
6870     cl.rfIn = *y;
6871     cl.ffIn = *x;
6872     cl.rtIn = -1;
6873     cl.ftIn = -1;
6874     cl.promoCharIn = NULLCHAR;
6875     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6876     if( cl.kind == NormalMove ||
6877         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6878         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6879         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6880       fromX = cl.ff;
6881       fromY = cl.rf;
6882       *x = cl.ft;
6883       *y = cl.rt;
6884       return TRUE;
6885     }
6886     if(cl.kind != ImpossibleMove) return FALSE;
6887     cl.pieceIn = EmptySquare;
6888     cl.rfIn = -1;
6889     cl.ffIn = -1;
6890     cl.rtIn = *y;
6891     cl.ftIn = *x;
6892     cl.promoCharIn = NULLCHAR;
6893     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6894     if( cl.kind == NormalMove ||
6895         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6896         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6897         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6898       fromX = cl.ff;
6899       fromY = cl.rf;
6900       *x = cl.ft;
6901       *y = cl.rt;
6902       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6903       return TRUE;
6904     }
6905     return FALSE;
6906 }
6907
6908 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6909 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6910 int lastLoadGameUseList = FALSE;
6911 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6912 ChessMove lastLoadGameStart = EndOfFile;
6913 int doubleClick;
6914 Boolean addToBookFlag;
6915
6916 void
6917 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6918 {
6919     ChessMove moveType;
6920     ChessSquare pup;
6921     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6922
6923     /* Check if the user is playing in turn.  This is complicated because we
6924        let the user "pick up" a piece before it is his turn.  So the piece he
6925        tried to pick up may have been captured by the time he puts it down!
6926        Therefore we use the color the user is supposed to be playing in this
6927        test, not the color of the piece that is currently on the starting
6928        square---except in EditGame mode, where the user is playing both
6929        sides; fortunately there the capture race can't happen.  (It can
6930        now happen in IcsExamining mode, but that's just too bad.  The user
6931        will get a somewhat confusing message in that case.)
6932        */
6933
6934     switch (gameMode) {
6935       case AnalyzeFile:
6936       case TwoMachinesPlay:
6937       case EndOfGame:
6938       case IcsObserving:
6939       case IcsIdle:
6940         /* We switched into a game mode where moves are not accepted,
6941            perhaps while the mouse button was down. */
6942         return;
6943
6944       case MachinePlaysWhite:
6945         /* User is moving for Black */
6946         if (WhiteOnMove(currentMove)) {
6947             DisplayMoveError(_("It is White's turn"));
6948             return;
6949         }
6950         break;
6951
6952       case MachinePlaysBlack:
6953         /* User is moving for White */
6954         if (!WhiteOnMove(currentMove)) {
6955             DisplayMoveError(_("It is Black's turn"));
6956             return;
6957         }
6958         break;
6959
6960       case PlayFromGameFile:
6961             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6962       case EditGame:
6963       case IcsExamining:
6964       case BeginningOfGame:
6965       case AnalyzeMode:
6966       case Training:
6967         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6968         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6969             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6970             /* User is moving for Black */
6971             if (WhiteOnMove(currentMove)) {
6972                 DisplayMoveError(_("It is White's turn"));
6973                 return;
6974             }
6975         } else {
6976             /* User is moving for White */
6977             if (!WhiteOnMove(currentMove)) {
6978                 DisplayMoveError(_("It is Black's turn"));
6979                 return;
6980             }
6981         }
6982         break;
6983
6984       case IcsPlayingBlack:
6985         /* User is moving for Black */
6986         if (WhiteOnMove(currentMove)) {
6987             if (!appData.premove) {
6988                 DisplayMoveError(_("It is White's turn"));
6989             } else if (toX >= 0 && toY >= 0) {
6990                 premoveToX = toX;
6991                 premoveToY = toY;
6992                 premoveFromX = fromX;
6993                 premoveFromY = fromY;
6994                 premovePromoChar = promoChar;
6995                 gotPremove = 1;
6996                 if (appData.debugMode)
6997                     fprintf(debugFP, "Got premove: fromX %d,"
6998                             "fromY %d, toX %d, toY %d\n",
6999                             fromX, fromY, toX, toY);
7000             }
7001             return;
7002         }
7003         break;
7004
7005       case IcsPlayingWhite:
7006         /* User is moving for White */
7007         if (!WhiteOnMove(currentMove)) {
7008             if (!appData.premove) {
7009                 DisplayMoveError(_("It is Black's turn"));
7010             } else if (toX >= 0 && toY >= 0) {
7011                 premoveToX = toX;
7012                 premoveToY = toY;
7013                 premoveFromX = fromX;
7014                 premoveFromY = fromY;
7015                 premovePromoChar = promoChar;
7016                 gotPremove = 1;
7017                 if (appData.debugMode)
7018                     fprintf(debugFP, "Got premove: fromX %d,"
7019                             "fromY %d, toX %d, toY %d\n",
7020                             fromX, fromY, toX, toY);
7021             }
7022             return;
7023         }
7024         break;
7025
7026       default:
7027         break;
7028
7029       case EditPosition:
7030         /* EditPosition, empty square, or different color piece;
7031            click-click move is possible */
7032         if (toX == -2 || toY == -2) {
7033             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7034             DrawPosition(FALSE, boards[currentMove]);
7035             return;
7036         } else if (toX >= 0 && toY >= 0) {
7037             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7038                 ChessSquare q, p = boards[0][rf][ff];
7039                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7040                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7041                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7042                 if(PieceToChar(q) == '+') gatingPiece = p;
7043             }
7044             boards[0][toY][toX] = boards[0][fromY][fromX];
7045             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7046                 if(boards[0][fromY][0] != EmptySquare) {
7047                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7048                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7049                 }
7050             } else
7051             if(fromX == BOARD_RGHT+1) {
7052                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7053                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7054                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7055                 }
7056             } else
7057             boards[0][fromY][fromX] = gatingPiece;
7058             DrawPosition(FALSE, boards[currentMove]);
7059             return;
7060         }
7061         return;
7062     }
7063
7064     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7065     pup = boards[currentMove][toY][toX];
7066
7067     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7068     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7069          if( pup != EmptySquare ) return;
7070          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7071            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7072                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7073            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7074            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7075            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7076            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7077          fromY = DROP_RANK;
7078     }
7079
7080     /* [HGM] always test for legality, to get promotion info */
7081     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7082                                          fromY, fromX, toY, toX, promoChar);
7083
7084     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7085
7086     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7087
7088     /* [HGM] but possibly ignore an IllegalMove result */
7089     if (appData.testLegality) {
7090         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7091             DisplayMoveError(_("Illegal move"));
7092             return;
7093         }
7094     }
7095
7096     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7097         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7098              ClearPremoveHighlights(); // was included
7099         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7100         return;
7101     }
7102
7103     if(addToBookFlag) { // adding moves to book
7104         char buf[MSG_SIZ], move[MSG_SIZ];
7105         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7106         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');
7107         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7108         AddBookMove(buf);
7109         addToBookFlag = FALSE;
7110         ClearHighlights();
7111         return;
7112     }
7113
7114     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7115 }
7116
7117 /* Common tail of UserMoveEvent and DropMenuEvent */
7118 int
7119 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7120 {
7121     char *bookHit = 0;
7122
7123     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7124         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7125         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7126         if(WhiteOnMove(currentMove)) {
7127             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7128         } else {
7129             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7130         }
7131     }
7132
7133     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7134        move type in caller when we know the move is a legal promotion */
7135     if(moveType == NormalMove && promoChar)
7136         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7137
7138     /* [HGM] <popupFix> The following if has been moved here from
7139        UserMoveEvent(). Because it seemed to belong here (why not allow
7140        piece drops in training games?), and because it can only be
7141        performed after it is known to what we promote. */
7142     if (gameMode == Training) {
7143       /* compare the move played on the board to the next move in the
7144        * game. If they match, display the move and the opponent's response.
7145        * If they don't match, display an error message.
7146        */
7147       int saveAnimate;
7148       Board testBoard;
7149       CopyBoard(testBoard, boards[currentMove]);
7150       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7151
7152       if (CompareBoards(testBoard, boards[currentMove+1])) {
7153         ForwardInner(currentMove+1);
7154
7155         /* Autoplay the opponent's response.
7156          * if appData.animate was TRUE when Training mode was entered,
7157          * the response will be animated.
7158          */
7159         saveAnimate = appData.animate;
7160         appData.animate = animateTraining;
7161         ForwardInner(currentMove+1);
7162         appData.animate = saveAnimate;
7163
7164         /* check for the end of the game */
7165         if (currentMove >= forwardMostMove) {
7166           gameMode = PlayFromGameFile;
7167           ModeHighlight();
7168           SetTrainingModeOff();
7169           DisplayInformation(_("End of game"));
7170         }
7171       } else {
7172         DisplayError(_("Incorrect move"), 0);
7173       }
7174       return 1;
7175     }
7176
7177   /* Ok, now we know that the move is good, so we can kill
7178      the previous line in Analysis Mode */
7179   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7180                                 && currentMove < forwardMostMove) {
7181     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7182     else forwardMostMove = currentMove;
7183   }
7184
7185   ClearMap();
7186
7187   /* If we need the chess program but it's dead, restart it */
7188   ResurrectChessProgram();
7189
7190   /* A user move restarts a paused game*/
7191   if (pausing)
7192     PauseEvent();
7193
7194   thinkOutput[0] = NULLCHAR;
7195
7196   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7197
7198   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7199     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7200     return 1;
7201   }
7202
7203   if (gameMode == BeginningOfGame) {
7204     if (appData.noChessProgram) {
7205       gameMode = EditGame;
7206       SetGameInfo();
7207     } else {
7208       char buf[MSG_SIZ];
7209       gameMode = MachinePlaysBlack;
7210       StartClocks();
7211       SetGameInfo();
7212       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7213       DisplayTitle(buf);
7214       if (first.sendName) {
7215         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7216         SendToProgram(buf, &first);
7217       }
7218       StartClocks();
7219     }
7220     ModeHighlight();
7221   }
7222
7223   /* Relay move to ICS or chess engine */
7224   if (appData.icsActive) {
7225     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7226         gameMode == IcsExamining) {
7227       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7228         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7229         SendToICS("draw ");
7230         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7231       }
7232       // also send plain move, in case ICS does not understand atomic claims
7233       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7234       ics_user_moved = 1;
7235     }
7236   } else {
7237     if (first.sendTime && (gameMode == BeginningOfGame ||
7238                            gameMode == MachinePlaysWhite ||
7239                            gameMode == MachinePlaysBlack)) {
7240       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7241     }
7242     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7243          // [HGM] book: if program might be playing, let it use book
7244         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7245         first.maybeThinking = TRUE;
7246     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7247         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7248         SendBoard(&first, currentMove+1);
7249         if(second.analyzing) {
7250             if(!second.useSetboard) SendToProgram("undo\n", &second);
7251             SendBoard(&second, currentMove+1);
7252         }
7253     } else {
7254         SendMoveToProgram(forwardMostMove-1, &first);
7255         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7256     }
7257     if (currentMove == cmailOldMove + 1) {
7258       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7259     }
7260   }
7261
7262   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7263
7264   switch (gameMode) {
7265   case EditGame:
7266     if(appData.testLegality)
7267     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7268     case MT_NONE:
7269     case MT_CHECK:
7270       break;
7271     case MT_CHECKMATE:
7272     case MT_STAINMATE:
7273       if (WhiteOnMove(currentMove)) {
7274         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7275       } else {
7276         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7277       }
7278       break;
7279     case MT_STALEMATE:
7280       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7281       break;
7282     }
7283     break;
7284
7285   case MachinePlaysBlack:
7286   case MachinePlaysWhite:
7287     /* disable certain menu options while machine is thinking */
7288     SetMachineThinkingEnables();
7289     break;
7290
7291   default:
7292     break;
7293   }
7294
7295   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7296   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7297
7298   if(bookHit) { // [HGM] book: simulate book reply
7299         static char bookMove[MSG_SIZ]; // a bit generous?
7300
7301         programStats.nodes = programStats.depth = programStats.time =
7302         programStats.score = programStats.got_only_move = 0;
7303         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7304
7305         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7306         strcat(bookMove, bookHit);
7307         HandleMachineMove(bookMove, &first);
7308   }
7309   return 1;
7310 }
7311
7312 void
7313 MarkByFEN(char *fen)
7314 {
7315         int r, f;
7316         if(!appData.markers || !appData.highlightDragging) return;
7317         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7318         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7319         while(*fen) {
7320             int s = 0;
7321             marker[r][f] = 0;
7322             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7323             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7324             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7325             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7326             if(*fen == 'T') marker[r][f++] = 0; else
7327             if(*fen == 'Y') marker[r][f++] = 1; else
7328             if(*fen == 'G') marker[r][f++] = 3; else
7329             if(*fen == 'B') marker[r][f++] = 4; else
7330             if(*fen == 'C') marker[r][f++] = 5; else
7331             if(*fen == 'M') marker[r][f++] = 6; else
7332             if(*fen == 'W') marker[r][f++] = 7; else
7333             if(*fen == 'D') marker[r][f++] = 8; else
7334             if(*fen == 'R') marker[r][f++] = 2; else {
7335                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7336               f += s; fen -= s>0;
7337             }
7338             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7339             if(r < 0) break;
7340             fen++;
7341         }
7342         DrawPosition(TRUE, NULL);
7343 }
7344
7345 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7346
7347 void
7348 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7349 {
7350     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7351     Markers *m = (Markers *) closure;
7352     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7353         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7354                          || kind == WhiteCapturesEnPassant
7355                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7356     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7357 }
7358
7359 static int hoverSavedValid;
7360
7361 void
7362 MarkTargetSquares (int clear)
7363 {
7364   int x, y, sum=0;
7365   if(clear) { // no reason to ever suppress clearing
7366     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7367     hoverSavedValid = 0;
7368     if(!sum) return; // nothing was cleared,no redraw needed
7369   } else {
7370     int capt = 0;
7371     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7372        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7373     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7374     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7375       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7376       if(capt)
7377       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7378     }
7379   }
7380   DrawPosition(FALSE, NULL);
7381 }
7382
7383 int
7384 Explode (Board board, int fromX, int fromY, int toX, int toY)
7385 {
7386     if(gameInfo.variant == VariantAtomic &&
7387        (board[toY][toX] != EmptySquare ||                     // capture?
7388         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7389                          board[fromY][fromX] == BlackPawn   )
7390       )) {
7391         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7392         return TRUE;
7393     }
7394     return FALSE;
7395 }
7396
7397 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7398
7399 int
7400 CanPromote (ChessSquare piece, int y)
7401 {
7402         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7403         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7404         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7405         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7406            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7407            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7408          gameInfo.variant == VariantMakruk) return FALSE;
7409         return (piece == BlackPawn && y <= zone ||
7410                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7411                 piece == BlackLance && y <= zone ||
7412                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7413 }
7414
7415 void
7416 HoverEvent (int xPix, int yPix, int x, int y)
7417 {
7418         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7419         int r, f;
7420         if(!first.highlight) return;
7421         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7422         if(x == oldX && y == oldY) return; // only do something if we enter new square
7423         oldFromX = fromX; oldFromY = fromY;
7424         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7425           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7426             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7427           hoverSavedValid = 1;
7428         } else if(oldX != x || oldY != y) {
7429           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7430           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7431           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7432             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7433           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7434             char buf[MSG_SIZ];
7435             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7436             SendToProgram(buf, &first);
7437           }
7438           oldX = x; oldY = y;
7439 //        SetHighlights(fromX, fromY, x, y);
7440         }
7441 }
7442
7443 void ReportClick(char *action, int x, int y)
7444 {
7445         char buf[MSG_SIZ]; // Inform engine of what user does
7446         int r, f;
7447         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7448           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7449             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7450         if(!first.highlight || gameMode == EditPosition) return;
7451         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7452         SendToProgram(buf, &first);
7453 }
7454
7455 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7456
7457 void
7458 LeftClick (ClickType clickType, int xPix, int yPix)
7459 {
7460     int x, y;
7461     Boolean saveAnimate;
7462     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7463     char promoChoice = NULLCHAR;
7464     ChessSquare piece;
7465     static TimeMark lastClickTime, prevClickTime;
7466
7467     x = EventToSquare(xPix, BOARD_WIDTH);
7468     y = EventToSquare(yPix, BOARD_HEIGHT);
7469     if (!flipView && y >= 0) {
7470         y = BOARD_HEIGHT - 1 - y;
7471     }
7472     if (flipView && x >= 0) {
7473         x = BOARD_WIDTH - 1 - x;
7474     }
7475
7476     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7477         static int dummy;
7478         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7479         right = TRUE;
7480         return;
7481     }
7482
7483     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7484
7485     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7486
7487     if (clickType == Press) ErrorPopDown();
7488     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7489
7490     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7491         defaultPromoChoice = promoSweep;
7492         promoSweep = EmptySquare;   // terminate sweep
7493         promoDefaultAltered = TRUE;
7494         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7495     }
7496
7497     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7498         if(clickType == Release) return; // ignore upclick of click-click destination
7499         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7500         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7501         if(gameInfo.holdingsWidth &&
7502                 (WhiteOnMove(currentMove)
7503                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7504                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7505             // click in right holdings, for determining promotion piece
7506             ChessSquare p = boards[currentMove][y][x];
7507             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7508             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7509             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7510                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7511                 fromX = fromY = -1;
7512                 return;
7513             }
7514         }
7515         DrawPosition(FALSE, boards[currentMove]);
7516         return;
7517     }
7518
7519     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7520     if(clickType == Press
7521             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7522               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7523               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7524         return;
7525
7526     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7527         // could be static click on premove from-square: abort premove
7528         gotPremove = 0;
7529         ClearPremoveHighlights();
7530     }
7531
7532     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7533         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7534
7535     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7536         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7537                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7538         defaultPromoChoice = DefaultPromoChoice(side);
7539     }
7540
7541     autoQueen = appData.alwaysPromoteToQueen;
7542
7543     if (fromX == -1) {
7544       int originalY = y;
7545       gatingPiece = EmptySquare;
7546       if (clickType != Press) {
7547         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7548             DragPieceEnd(xPix, yPix); dragging = 0;
7549             DrawPosition(FALSE, NULL);
7550         }
7551         return;
7552       }
7553       doubleClick = FALSE;
7554       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7555         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7556       }
7557       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7558       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7559          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7560          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7561             /* First square */
7562             if (OKToStartUserMove(fromX, fromY)) {
7563                 second = 0;
7564                 ReportClick("lift", x, y);
7565                 MarkTargetSquares(0);
7566                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7567                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7568                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7569                     promoSweep = defaultPromoChoice;
7570                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7571                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7572                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7573                 }
7574                 if (appData.highlightDragging) {
7575                     SetHighlights(fromX, fromY, -1, -1);
7576                 } else {
7577                     ClearHighlights();
7578                 }
7579             } else fromX = fromY = -1;
7580             return;
7581         }
7582     }
7583
7584     /* fromX != -1 */
7585     if (clickType == Press && gameMode != EditPosition) {
7586         ChessSquare fromP;
7587         ChessSquare toP;
7588         int frc;
7589
7590         // ignore off-board to clicks
7591         if(y < 0 || x < 0) return;
7592
7593         /* Check if clicking again on the same color piece */
7594         fromP = boards[currentMove][fromY][fromX];
7595         toP = boards[currentMove][y][x];
7596         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7597         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7598             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7599            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7600              WhitePawn <= toP && toP <= WhiteKing &&
7601              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7602              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7603             (BlackPawn <= fromP && fromP <= BlackKing &&
7604              BlackPawn <= toP && toP <= BlackKing &&
7605              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7606              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7607             /* Clicked again on same color piece -- changed his mind */
7608             second = (x == fromX && y == fromY);
7609             killX = killY = -1;
7610             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7611                 second = FALSE; // first double-click rather than scond click
7612                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7613             }
7614             promoDefaultAltered = FALSE;
7615             MarkTargetSquares(1);
7616            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7617             if (appData.highlightDragging) {
7618                 SetHighlights(x, y, -1, -1);
7619             } else {
7620                 ClearHighlights();
7621             }
7622             if (OKToStartUserMove(x, y)) {
7623                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7624                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7625                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7626                  gatingPiece = boards[currentMove][fromY][fromX];
7627                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7628                 fromX = x;
7629                 fromY = y; dragging = 1;
7630                 if(!second) ReportClick("lift", x, y);
7631                 MarkTargetSquares(0);
7632                 DragPieceBegin(xPix, yPix, FALSE);
7633                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7634                     promoSweep = defaultPromoChoice;
7635                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7636                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7637                 }
7638             }
7639            }
7640            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7641            second = FALSE;
7642         }
7643         // ignore clicks on holdings
7644         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7645     }
7646
7647     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7648         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7649         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7650         return;
7651     }
7652
7653     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7654         DragPieceEnd(xPix, yPix); dragging = 0;
7655         if(clearFlag) {
7656             // a deferred attempt to click-click move an empty square on top of a piece
7657             boards[currentMove][y][x] = EmptySquare;
7658             ClearHighlights();
7659             DrawPosition(FALSE, boards[currentMove]);
7660             fromX = fromY = -1; clearFlag = 0;
7661             return;
7662         }
7663         if (appData.animateDragging) {
7664             /* Undo animation damage if any */
7665             DrawPosition(FALSE, NULL);
7666         }
7667         if (second) {
7668             /* Second up/down in same square; just abort move */
7669             second = 0;
7670             fromX = fromY = -1;
7671             gatingPiece = EmptySquare;
7672             MarkTargetSquares(1);
7673             ClearHighlights();
7674             gotPremove = 0;
7675             ClearPremoveHighlights();
7676         } else {
7677             /* First upclick in same square; start click-click mode */
7678             SetHighlights(x, y, -1, -1);
7679         }
7680         return;
7681     }
7682
7683     clearFlag = 0;
7684
7685     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7686        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7687         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7688         DisplayMessage(_("only marked squares are legal"),"");
7689         DrawPosition(TRUE, NULL);
7690         return; // ignore to-click
7691     }
7692
7693     /* we now have a different from- and (possibly off-board) to-square */
7694     /* Completed move */
7695     if(!sweepSelecting) {
7696         toX = x;
7697         toY = y;
7698     }
7699
7700     piece = boards[currentMove][fromY][fromX];
7701
7702     saveAnimate = appData.animate;
7703     if (clickType == Press) {
7704         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7705         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7706             // must be Edit Position mode with empty-square selected
7707             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7708             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7709             return;
7710         }
7711         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7712             return;
7713         }
7714         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7715             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7716         } else
7717         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7718         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7719           if(appData.sweepSelect) {
7720             promoSweep = defaultPromoChoice;
7721             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7722             selectFlag = 0; lastX = xPix; lastY = yPix;
7723             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7724             Sweep(0); // Pawn that is going to promote: preview promotion piece
7725             sweepSelecting = 1;
7726             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7727             MarkTargetSquares(1);
7728           }
7729           return; // promo popup appears on up-click
7730         }
7731         /* Finish clickclick move */
7732         if (appData.animate || appData.highlightLastMove) {
7733             SetHighlights(fromX, fromY, toX, toY);
7734         } else {
7735             ClearHighlights();
7736         }
7737     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7738         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7739         *promoRestrict = 0;
7740         if (appData.animate || appData.highlightLastMove) {
7741             SetHighlights(fromX, fromY, toX, toY);
7742         } else {
7743             ClearHighlights();
7744         }
7745     } else {
7746 #if 0
7747 // [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
7748         /* Finish drag move */
7749         if (appData.highlightLastMove) {
7750             SetHighlights(fromX, fromY, toX, toY);
7751         } else {
7752             ClearHighlights();
7753         }
7754 #endif
7755         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7756         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7757           dragging *= 2;            // flag button-less dragging if we are dragging
7758           MarkTargetSquares(1);
7759           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7760           else {
7761             kill2X = killX; kill2Y = killY;
7762             killX = x; killY = y;     //remeber this square as intermediate
7763             ReportClick("put", x, y); // and inform engine
7764             ReportClick("lift", x, y);
7765             MarkTargetSquares(0);
7766             return;
7767           }
7768         }
7769         DragPieceEnd(xPix, yPix); dragging = 0;
7770         /* Don't animate move and drag both */
7771         appData.animate = FALSE;
7772     }
7773
7774     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7775     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7776         ChessSquare piece = boards[currentMove][fromY][fromX];
7777         if(gameMode == EditPosition && piece != EmptySquare &&
7778            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7779             int n;
7780
7781             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7782                 n = PieceToNumber(piece - (int)BlackPawn);
7783                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7784                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7785                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7786             } else
7787             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7788                 n = PieceToNumber(piece);
7789                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7790                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7791                 boards[currentMove][n][BOARD_WIDTH-2]++;
7792             }
7793             boards[currentMove][fromY][fromX] = EmptySquare;
7794         }
7795         ClearHighlights();
7796         fromX = fromY = -1;
7797         MarkTargetSquares(1);
7798         DrawPosition(TRUE, boards[currentMove]);
7799         return;
7800     }
7801
7802     // off-board moves should not be highlighted
7803     if(x < 0 || y < 0) ClearHighlights();
7804     else ReportClick("put", x, y);
7805
7806     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7807
7808     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7809
7810     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7811         SetHighlights(fromX, fromY, toX, toY);
7812         MarkTargetSquares(1);
7813         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7814             // [HGM] super: promotion to captured piece selected from holdings
7815             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7816             promotionChoice = TRUE;
7817             // kludge follows to temporarily execute move on display, without promoting yet
7818             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7819             boards[currentMove][toY][toX] = p;
7820             DrawPosition(FALSE, boards[currentMove]);
7821             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7822             boards[currentMove][toY][toX] = q;
7823             DisplayMessage("Click in holdings to choose piece", "");
7824             return;
7825         }
7826         PromotionPopUp(promoChoice);
7827     } else {
7828         int oldMove = currentMove;
7829         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7830         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7831         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7832         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7833            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7834             DrawPosition(TRUE, boards[currentMove]);
7835         MarkTargetSquares(1);
7836         fromX = fromY = -1;
7837     }
7838     appData.animate = saveAnimate;
7839     if (appData.animate || appData.animateDragging) {
7840         /* Undo animation damage if needed */
7841         DrawPosition(FALSE, NULL);
7842     }
7843 }
7844
7845 int
7846 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7847 {   // front-end-free part taken out of PieceMenuPopup
7848     int whichMenu; int xSqr, ySqr;
7849
7850     if(seekGraphUp) { // [HGM] seekgraph
7851         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7852         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7853         return -2;
7854     }
7855
7856     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7857          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7858         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7859         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7860         if(action == Press)   {
7861             originalFlip = flipView;
7862             flipView = !flipView; // temporarily flip board to see game from partners perspective
7863             DrawPosition(TRUE, partnerBoard);
7864             DisplayMessage(partnerStatus, "");
7865             partnerUp = TRUE;
7866         } else if(action == Release) {
7867             flipView = originalFlip;
7868             DrawPosition(TRUE, boards[currentMove]);
7869             partnerUp = FALSE;
7870         }
7871         return -2;
7872     }
7873
7874     xSqr = EventToSquare(x, BOARD_WIDTH);
7875     ySqr = EventToSquare(y, BOARD_HEIGHT);
7876     if (action == Release) {
7877         if(pieceSweep != EmptySquare) {
7878             EditPositionMenuEvent(pieceSweep, toX, toY);
7879             pieceSweep = EmptySquare;
7880         } else UnLoadPV(); // [HGM] pv
7881     }
7882     if (action != Press) return -2; // return code to be ignored
7883     switch (gameMode) {
7884       case IcsExamining:
7885         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7886       case EditPosition:
7887         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7888         if (xSqr < 0 || ySqr < 0) return -1;
7889         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7890         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7891         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7892         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7893         NextPiece(0);
7894         return 2; // grab
7895       case IcsObserving:
7896         if(!appData.icsEngineAnalyze) return -1;
7897       case IcsPlayingWhite:
7898       case IcsPlayingBlack:
7899         if(!appData.zippyPlay) goto noZip;
7900       case AnalyzeMode:
7901       case AnalyzeFile:
7902       case MachinePlaysWhite:
7903       case MachinePlaysBlack:
7904       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7905         if (!appData.dropMenu) {
7906           LoadPV(x, y);
7907           return 2; // flag front-end to grab mouse events
7908         }
7909         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7910            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7911       case EditGame:
7912       noZip:
7913         if (xSqr < 0 || ySqr < 0) return -1;
7914         if (!appData.dropMenu || appData.testLegality &&
7915             gameInfo.variant != VariantBughouse &&
7916             gameInfo.variant != VariantCrazyhouse) return -1;
7917         whichMenu = 1; // drop menu
7918         break;
7919       default:
7920         return -1;
7921     }
7922
7923     if (((*fromX = xSqr) < 0) ||
7924         ((*fromY = ySqr) < 0)) {
7925         *fromX = *fromY = -1;
7926         return -1;
7927     }
7928     if (flipView)
7929       *fromX = BOARD_WIDTH - 1 - *fromX;
7930     else
7931       *fromY = BOARD_HEIGHT - 1 - *fromY;
7932
7933     return whichMenu;
7934 }
7935
7936 void
7937 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7938 {
7939 //    char * hint = lastHint;
7940     FrontEndProgramStats stats;
7941
7942     stats.which = cps == &first ? 0 : 1;
7943     stats.depth = cpstats->depth;
7944     stats.nodes = cpstats->nodes;
7945     stats.score = cpstats->score;
7946     stats.time = cpstats->time;
7947     stats.pv = cpstats->movelist;
7948     stats.hint = lastHint;
7949     stats.an_move_index = 0;
7950     stats.an_move_count = 0;
7951
7952     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7953         stats.hint = cpstats->move_name;
7954         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7955         stats.an_move_count = cpstats->nr_moves;
7956     }
7957
7958     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
7959
7960     SetProgramStats( &stats );
7961 }
7962
7963 void
7964 ClearEngineOutputPane (int which)
7965 {
7966     static FrontEndProgramStats dummyStats;
7967     dummyStats.which = which;
7968     dummyStats.pv = "#";
7969     SetProgramStats( &dummyStats );
7970 }
7971
7972 #define MAXPLAYERS 500
7973
7974 char *
7975 TourneyStandings (int display)
7976 {
7977     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7978     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7979     char result, *p, *names[MAXPLAYERS];
7980
7981     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7982         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7983     names[0] = p = strdup(appData.participants);
7984     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7985
7986     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7987
7988     while(result = appData.results[nr]) {
7989         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7990         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7991         wScore = bScore = 0;
7992         switch(result) {
7993           case '+': wScore = 2; break;
7994           case '-': bScore = 2; break;
7995           case '=': wScore = bScore = 1; break;
7996           case ' ':
7997           case '*': return strdup("busy"); // tourney not finished
7998         }
7999         score[w] += wScore;
8000         score[b] += bScore;
8001         games[w]++;
8002         games[b]++;
8003         nr++;
8004     }
8005     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8006     for(w=0; w<nPlayers; w++) {
8007         bScore = -1;
8008         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8009         ranking[w] = b; points[w] = bScore; score[b] = -2;
8010     }
8011     p = malloc(nPlayers*34+1);
8012     for(w=0; w<nPlayers && w<display; w++)
8013         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8014     free(names[0]);
8015     return p;
8016 }
8017
8018 void
8019 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8020 {       // count all piece types
8021         int p, f, r;
8022         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8023         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8024         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8025                 p = board[r][f];
8026                 pCnt[p]++;
8027                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8028                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8029                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8030                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8031                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8032                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8033         }
8034 }
8035
8036 int
8037 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8038 {
8039         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8040         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8041
8042         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8043         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8044         if(myPawns == 2 && nMine == 3) // KPP
8045             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8046         if(myPawns == 1 && nMine == 2) // KP
8047             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8048         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8049             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8050         if(myPawns) return FALSE;
8051         if(pCnt[WhiteRook+side])
8052             return pCnt[BlackRook-side] ||
8053                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8054                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8055                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8056         if(pCnt[WhiteCannon+side]) {
8057             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8058             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8059         }
8060         if(pCnt[WhiteKnight+side])
8061             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8062         return FALSE;
8063 }
8064
8065 int
8066 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8067 {
8068         VariantClass v = gameInfo.variant;
8069
8070         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8071         if(v == VariantShatranj) return TRUE; // always winnable through baring
8072         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8073         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8074
8075         if(v == VariantXiangqi) {
8076                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8077
8078                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8079                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8080                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8081                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8082                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8083                 if(stale) // we have at least one last-rank P plus perhaps C
8084                     return majors // KPKX
8085                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8086                 else // KCA*E*
8087                     return pCnt[WhiteFerz+side] // KCAK
8088                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8089                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8090                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8091
8092         } else if(v == VariantKnightmate) {
8093                 if(nMine == 1) return FALSE;
8094                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8095         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8096                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8097
8098                 if(nMine == 1) return FALSE; // bare King
8099                 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
8100                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8101                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8102                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8103                 if(pCnt[WhiteKnight+side])
8104                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8105                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8106                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8107                 if(nBishops)
8108                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8109                 if(pCnt[WhiteAlfil+side])
8110                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8111                 if(pCnt[WhiteWazir+side])
8112                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8113         }
8114
8115         return TRUE;
8116 }
8117
8118 int
8119 CompareWithRights (Board b1, Board b2)
8120 {
8121     int rights = 0;
8122     if(!CompareBoards(b1, b2)) return FALSE;
8123     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8124     /* compare castling rights */
8125     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8126            rights++; /* King lost rights, while rook still had them */
8127     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8128         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8129            rights++; /* but at least one rook lost them */
8130     }
8131     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8132            rights++;
8133     if( b1[CASTLING][5] != NoRights ) {
8134         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8135            rights++;
8136     }
8137     return rights == 0;
8138 }
8139
8140 int
8141 Adjudicate (ChessProgramState *cps)
8142 {       // [HGM] some adjudications useful with buggy engines
8143         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8144         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8145         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8146         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8147         int k, drop, count = 0; static int bare = 1;
8148         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8149         Boolean canAdjudicate = !appData.icsActive;
8150
8151         // most tests only when we understand the game, i.e. legality-checking on
8152             if( appData.testLegality )
8153             {   /* [HGM] Some more adjudications for obstinate engines */
8154                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8155                 static int moveCount = 6;
8156                 ChessMove result;
8157                 char *reason = NULL;
8158
8159                 /* Count what is on board. */
8160                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8161
8162                 /* Some material-based adjudications that have to be made before stalemate test */
8163                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8164                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8165                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8166                      if(canAdjudicate && appData.checkMates) {
8167                          if(engineOpponent)
8168                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8169                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8170                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8171                          return 1;
8172                      }
8173                 }
8174
8175                 /* Bare King in Shatranj (loses) or Losers (wins) */
8176                 if( nrW == 1 || nrB == 1) {
8177                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8178                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8179                      if(canAdjudicate && appData.checkMates) {
8180                          if(engineOpponent)
8181                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8182                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8183                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8184                          return 1;
8185                      }
8186                   } else
8187                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8188                   {    /* bare King */
8189                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8190                         if(canAdjudicate && appData.checkMates) {
8191                             /* but only adjudicate if adjudication enabled */
8192                             if(engineOpponent)
8193                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8194                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8195                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8196                             return 1;
8197                         }
8198                   }
8199                 } else bare = 1;
8200
8201
8202             // don't wait for engine to announce game end if we can judge ourselves
8203             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8204               case MT_CHECK:
8205                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8206                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8207                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8208                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8209                             checkCnt++;
8210                         if(checkCnt >= 2) {
8211                             reason = "Xboard adjudication: 3rd check";
8212                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8213                             break;
8214                         }
8215                     }
8216                 }
8217               case MT_NONE:
8218               default:
8219                 break;
8220               case MT_STEALMATE:
8221               case MT_STALEMATE:
8222               case MT_STAINMATE:
8223                 reason = "Xboard adjudication: Stalemate";
8224                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8225                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8226                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8227                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8228                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8229                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8230                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8231                                                                         EP_CHECKMATE : EP_WINS);
8232                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8233                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8234                 }
8235                 break;
8236               case MT_CHECKMATE:
8237                 reason = "Xboard adjudication: Checkmate";
8238                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8239                 if(gameInfo.variant == VariantShogi) {
8240                     if(forwardMostMove > backwardMostMove
8241                        && moveList[forwardMostMove-1][1] == '@'
8242                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8243                         reason = "XBoard adjudication: pawn-drop mate";
8244                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8245                     }
8246                 }
8247                 break;
8248             }
8249
8250                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8251                     case EP_STALEMATE:
8252                         result = GameIsDrawn; break;
8253                     case EP_CHECKMATE:
8254                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8255                     case EP_WINS:
8256                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8257                     default:
8258                         result = EndOfFile;
8259                 }
8260                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8261                     if(engineOpponent)
8262                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8263                     GameEnds( result, reason, GE_XBOARD );
8264                     return 1;
8265                 }
8266
8267                 /* Next absolutely insufficient mating material. */
8268                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8269                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8270                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8271
8272                      /* always flag draws, for judging claims */
8273                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8274
8275                      if(canAdjudicate && appData.materialDraws) {
8276                          /* but only adjudicate them if adjudication enabled */
8277                          if(engineOpponent) {
8278                            SendToProgram("force\n", engineOpponent); // suppress reply
8279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8280                          }
8281                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8282                          return 1;
8283                      }
8284                 }
8285
8286                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8287                 if(gameInfo.variant == VariantXiangqi ?
8288                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8289                  : nrW + nrB == 4 &&
8290                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8291                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8292                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8293                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8294                    ) ) {
8295                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8296                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8297                           if(engineOpponent) {
8298                             SendToProgram("force\n", engineOpponent); // suppress reply
8299                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8300                           }
8301                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8302                           return 1;
8303                      }
8304                 } else moveCount = 6;
8305             }
8306
8307         // Repetition draws and 50-move rule can be applied independently of legality testing
8308
8309                 /* Check for rep-draws */
8310                 count = 0;
8311                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8312                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8313                 for(k = forwardMostMove-2;
8314                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8315                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8316                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8317                     k-=2)
8318                 {   int rights=0;
8319                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8320                         /* compare castling rights */
8321                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8322                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8323                                 rights++; /* King lost rights, while rook still had them */
8324                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8325                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8326                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8327                                    rights++; /* but at least one rook lost them */
8328                         }
8329                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8330                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8331                                 rights++;
8332                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8333                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8334                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8335                                    rights++;
8336                         }
8337                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8338                             && appData.drawRepeats > 1) {
8339                              /* adjudicate after user-specified nr of repeats */
8340                              int result = GameIsDrawn;
8341                              char *details = "XBoard adjudication: repetition draw";
8342                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8343                                 // [HGM] xiangqi: check for forbidden perpetuals
8344                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8345                                 for(m=forwardMostMove; m>k; m-=2) {
8346                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8347                                         ourPerpetual = 0; // the current mover did not always check
8348                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8349                                         hisPerpetual = 0; // the opponent did not always check
8350                                 }
8351                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8352                                                                         ourPerpetual, hisPerpetual);
8353                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8354                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8355                                     details = "Xboard adjudication: perpetual checking";
8356                                 } else
8357                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8358                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8359                                 } else
8360                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8361                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8362                                         result = BlackWins;
8363                                         details = "Xboard adjudication: repetition";
8364                                     }
8365                                 } else // it must be XQ
8366                                 // Now check for perpetual chases
8367                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8368                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8369                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8370                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8371                                         static char resdet[MSG_SIZ];
8372                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8373                                         details = resdet;
8374                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8375                                     } else
8376                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8377                                         break; // Abort repetition-checking loop.
8378                                 }
8379                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8380                              }
8381                              if(engineOpponent) {
8382                                SendToProgram("force\n", engineOpponent); // suppress reply
8383                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8384                              }
8385                              GameEnds( result, details, GE_XBOARD );
8386                              return 1;
8387                         }
8388                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8389                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8390                     }
8391                 }
8392
8393                 /* Now we test for 50-move draws. Determine ply count */
8394                 count = forwardMostMove;
8395                 /* look for last irreversble move */
8396                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8397                     count--;
8398                 /* if we hit starting position, add initial plies */
8399                 if( count == backwardMostMove )
8400                     count -= initialRulePlies;
8401                 count = forwardMostMove - count;
8402                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8403                         // adjust reversible move counter for checks in Xiangqi
8404                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8405                         if(i < backwardMostMove) i = backwardMostMove;
8406                         while(i <= forwardMostMove) {
8407                                 lastCheck = inCheck; // check evasion does not count
8408                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8409                                 if(inCheck || lastCheck) count--; // check does not count
8410                                 i++;
8411                         }
8412                 }
8413                 if( count >= 100)
8414                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8415                          /* this is used to judge if draw claims are legal */
8416                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8417                          if(engineOpponent) {
8418                            SendToProgram("force\n", engineOpponent); // suppress reply
8419                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8420                          }
8421                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8422                          return 1;
8423                 }
8424
8425                 /* if draw offer is pending, treat it as a draw claim
8426                  * when draw condition present, to allow engines a way to
8427                  * claim draws before making their move to avoid a race
8428                  * condition occurring after their move
8429                  */
8430                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8431                          char *p = NULL;
8432                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8433                              p = "Draw claim: 50-move rule";
8434                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8435                              p = "Draw claim: 3-fold repetition";
8436                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8437                              p = "Draw claim: insufficient mating material";
8438                          if( p != NULL && canAdjudicate) {
8439                              if(engineOpponent) {
8440                                SendToProgram("force\n", engineOpponent); // suppress reply
8441                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8442                              }
8443                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8444                              return 1;
8445                          }
8446                 }
8447
8448                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8449                     if(engineOpponent) {
8450                       SendToProgram("force\n", engineOpponent); // suppress reply
8451                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8452                     }
8453                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8454                     return 1;
8455                 }
8456         return 0;
8457 }
8458
8459 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8460 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8461 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8462
8463 static int
8464 BitbaseProbe ()
8465 {
8466     int pieces[10], squares[10], cnt=0, r, f, res;
8467     static int loaded;
8468     static PPROBE_EGBB probeBB;
8469     if(!appData.testLegality) return 10;
8470     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8471     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8472     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8473     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8474         ChessSquare piece = boards[forwardMostMove][r][f];
8475         int black = (piece >= BlackPawn);
8476         int type = piece - black*BlackPawn;
8477         if(piece == EmptySquare) continue;
8478         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8479         if(type == WhiteKing) type = WhiteQueen + 1;
8480         type = egbbCode[type];
8481         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8482         pieces[cnt] = type + black*6;
8483         if(++cnt > 5) return 11;
8484     }
8485     pieces[cnt] = squares[cnt] = 0;
8486     // probe EGBB
8487     if(loaded == 2) return 13; // loading failed before
8488     if(loaded == 0) {
8489         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8490         HMODULE lib;
8491         PLOAD_EGBB loadBB;
8492         loaded = 2; // prepare for failure
8493         if(!path) return 13; // no egbb installed
8494         strncpy(buf, path + 8, MSG_SIZ);
8495         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8496         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8497         lib = LoadLibrary(buf);
8498         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8499         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8500         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8501         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8502         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8503         loaded = 1; // success!
8504     }
8505     res = probeBB(forwardMostMove & 1, pieces, squares);
8506     return res > 0 ? 1 : res < 0 ? -1 : 0;
8507 }
8508
8509 char *
8510 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8511 {   // [HGM] book: this routine intercepts moves to simulate book replies
8512     char *bookHit = NULL;
8513
8514     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8515         char buf[MSG_SIZ];
8516         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8517         SendToProgram(buf, cps);
8518     }
8519     //first determine if the incoming move brings opponent into his book
8520     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8521         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8522     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8523     if(bookHit != NULL && !cps->bookSuspend) {
8524         // make sure opponent is not going to reply after receiving move to book position
8525         SendToProgram("force\n", cps);
8526         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8527     }
8528     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8529     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8530     // now arrange restart after book miss
8531     if(bookHit) {
8532         // after a book hit we never send 'go', and the code after the call to this routine
8533         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8534         char buf[MSG_SIZ], *move = bookHit;
8535         if(cps->useSAN) {
8536             int fromX, fromY, toX, toY;
8537             char promoChar;
8538             ChessMove moveType;
8539             move = buf + 30;
8540             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8541                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8542                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8543                                     PosFlags(forwardMostMove),
8544                                     fromY, fromX, toY, toX, promoChar, move);
8545             } else {
8546                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8547                 bookHit = NULL;
8548             }
8549         }
8550         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8551         SendToProgram(buf, cps);
8552         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8553     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8554         SendToProgram("go\n", cps);
8555         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8556     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8557         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8558             SendToProgram("go\n", cps);
8559         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8560     }
8561     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8562 }
8563
8564 int
8565 LoadError (char *errmess, ChessProgramState *cps)
8566 {   // unloads engine and switches back to -ncp mode if it was first
8567     if(cps->initDone) return FALSE;
8568     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8569     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8570     cps->pr = NoProc;
8571     if(cps == &first) {
8572         appData.noChessProgram = TRUE;
8573         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8574         gameMode = BeginningOfGame; ModeHighlight();
8575         SetNCPMode();
8576     }
8577     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8578     DisplayMessage("", ""); // erase waiting message
8579     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8580     return TRUE;
8581 }
8582
8583 char *savedMessage;
8584 ChessProgramState *savedState;
8585 void
8586 DeferredBookMove (void)
8587 {
8588         if(savedState->lastPing != savedState->lastPong)
8589                     ScheduleDelayedEvent(DeferredBookMove, 10);
8590         else
8591         HandleMachineMove(savedMessage, savedState);
8592 }
8593
8594 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8595 static ChessProgramState *stalledEngine;
8596 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8597
8598 void
8599 HandleMachineMove (char *message, ChessProgramState *cps)
8600 {
8601     static char firstLeg[20];
8602     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8603     char realname[MSG_SIZ];
8604     int fromX, fromY, toX, toY;
8605     ChessMove moveType;
8606     char promoChar, roar;
8607     char *p, *pv=buf1;
8608     int machineWhite, oldError;
8609     char *bookHit;
8610
8611     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8612         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8613         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8614             DisplayError(_("Invalid pairing from pairing engine"), 0);
8615             return;
8616         }
8617         pairingReceived = 1;
8618         NextMatchGame();
8619         return; // Skim the pairing messages here.
8620     }
8621
8622     oldError = cps->userError; cps->userError = 0;
8623
8624 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8625     /*
8626      * Kludge to ignore BEL characters
8627      */
8628     while (*message == '\007') message++;
8629
8630     /*
8631      * [HGM] engine debug message: ignore lines starting with '#' character
8632      */
8633     if(cps->debug && *message == '#') return;
8634
8635     /*
8636      * Look for book output
8637      */
8638     if (cps == &first && bookRequested) {
8639         if (message[0] == '\t' || message[0] == ' ') {
8640             /* Part of the book output is here; append it */
8641             strcat(bookOutput, message);
8642             strcat(bookOutput, "  \n");
8643             return;
8644         } else if (bookOutput[0] != NULLCHAR) {
8645             /* All of book output has arrived; display it */
8646             char *p = bookOutput;
8647             while (*p != NULLCHAR) {
8648                 if (*p == '\t') *p = ' ';
8649                 p++;
8650             }
8651             DisplayInformation(bookOutput);
8652             bookRequested = FALSE;
8653             /* Fall through to parse the current output */
8654         }
8655     }
8656
8657     /*
8658      * Look for machine move.
8659      */
8660     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8661         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8662     {
8663         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8664             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8665             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8666             stalledEngine = cps;
8667             if(appData.ponderNextMove) { // bring opponent out of ponder
8668                 if(gameMode == TwoMachinesPlay) {
8669                     if(cps->other->pause)
8670                         PauseEngine(cps->other);
8671                     else
8672                         SendToProgram("easy\n", cps->other);
8673                 }
8674             }
8675             StopClocks();
8676             return;
8677         }
8678
8679       if(cps->usePing) {
8680
8681         /* This method is only useful on engines that support ping */
8682         if(abortEngineThink) {
8683             if (appData.debugMode) {
8684                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8685             }
8686             SendToProgram("undo\n", cps);
8687             return;
8688         }
8689
8690         if (cps->lastPing != cps->lastPong) {
8691             /* Extra move from before last new; ignore */
8692             if (appData.debugMode) {
8693                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8694             }
8695           return;
8696         }
8697
8698       } else {
8699
8700         switch (gameMode) {
8701           case BeginningOfGame:
8702             /* Extra move from before last reset; ignore */
8703             if (appData.debugMode) {
8704                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8705             }
8706             return;
8707
8708           case EndOfGame:
8709           case IcsIdle:
8710           default:
8711             /* Extra move after we tried to stop.  The mode test is
8712                not a reliable way of detecting this problem, but it's
8713                the best we can do on engines that don't support ping.
8714             */
8715             if (appData.debugMode) {
8716                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8717                         cps->which, gameMode);
8718             }
8719             SendToProgram("undo\n", cps);
8720             return;
8721
8722           case MachinePlaysWhite:
8723           case IcsPlayingWhite:
8724             machineWhite = TRUE;
8725             break;
8726
8727           case MachinePlaysBlack:
8728           case IcsPlayingBlack:
8729             machineWhite = FALSE;
8730             break;
8731
8732           case TwoMachinesPlay:
8733             machineWhite = (cps->twoMachinesColor[0] == 'w');
8734             break;
8735         }
8736         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8737             if (appData.debugMode) {
8738                 fprintf(debugFP,
8739                         "Ignoring move out of turn by %s, gameMode %d"
8740                         ", forwardMost %d\n",
8741                         cps->which, gameMode, forwardMostMove);
8742             }
8743             return;
8744         }
8745       }
8746
8747         if(cps->alphaRank) AlphaRank(machineMove, 4);
8748
8749         // [HGM] lion: (some very limited) support for Alien protocol
8750         killX = killY = kill2X = kill2Y = -1;
8751         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8752             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8753             return;
8754         }
8755         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8756             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8757             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8758         }
8759         if(firstLeg[0]) { // there was a previous leg;
8760             // only support case where same piece makes two step
8761             char buf[20], *p = machineMove+1, *q = buf+1, f;
8762             safeStrCpy(buf, machineMove, 20);
8763             while(isdigit(*q)) q++; // find start of to-square
8764             safeStrCpy(machineMove, firstLeg, 20);
8765             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8766             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8767             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8768             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8769             firstLeg[0] = NULLCHAR;
8770         }
8771
8772         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8773                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8774             /* Machine move could not be parsed; ignore it. */
8775           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8776                     machineMove, _(cps->which));
8777             DisplayMoveError(buf1);
8778             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8779                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8780             if (gameMode == TwoMachinesPlay) {
8781               GameEnds(machineWhite ? BlackWins : WhiteWins,
8782                        buf1, GE_XBOARD);
8783             }
8784             return;
8785         }
8786
8787         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8788         /* So we have to redo legality test with true e.p. status here,  */
8789         /* to make sure an illegal e.p. capture does not slip through,   */
8790         /* to cause a forfeit on a justified illegal-move complaint      */
8791         /* of the opponent.                                              */
8792         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8793            ChessMove moveType;
8794            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8795                              fromY, fromX, toY, toX, promoChar);
8796             if(moveType == IllegalMove) {
8797               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8798                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8799                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8800                            buf1, GE_XBOARD);
8801                 return;
8802            } else if(!appData.fischerCastling)
8803            /* [HGM] Kludge to handle engines that send FRC-style castling
8804               when they shouldn't (like TSCP-Gothic) */
8805            switch(moveType) {
8806              case WhiteASideCastleFR:
8807              case BlackASideCastleFR:
8808                toX+=2;
8809                currentMoveString[2]++;
8810                break;
8811              case WhiteHSideCastleFR:
8812              case BlackHSideCastleFR:
8813                toX--;
8814                currentMoveString[2]--;
8815                break;
8816              default: ; // nothing to do, but suppresses warning of pedantic compilers
8817            }
8818         }
8819         hintRequested = FALSE;
8820         lastHint[0] = NULLCHAR;
8821         bookRequested = FALSE;
8822         /* Program may be pondering now */
8823         cps->maybeThinking = TRUE;
8824         if (cps->sendTime == 2) cps->sendTime = 1;
8825         if (cps->offeredDraw) cps->offeredDraw--;
8826
8827         /* [AS] Save move info*/
8828         pvInfoList[ forwardMostMove ].score = programStats.score;
8829         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8830         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8831
8832         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8833
8834         /* Test suites abort the 'game' after one move */
8835         if(*appData.finger) {
8836            static FILE *f;
8837            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8838            if(!f) f = fopen(appData.finger, "w");
8839            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8840            else { DisplayFatalError("Bad output file", errno, 0); return; }
8841            free(fen);
8842            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8843         }
8844         if(appData.epd) {
8845            if(solvingTime >= 0) {
8846               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8847               totalTime += solvingTime; first.matchWins++;
8848            } else {
8849               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8850               second.matchWins++;
8851            }
8852            OutputKibitz(2, buf1);
8853            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8854         }
8855
8856         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8857         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8858             int count = 0;
8859
8860             while( count < adjudicateLossPlies ) {
8861                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8862
8863                 if( count & 1 ) {
8864                     score = -score; /* Flip score for winning side */
8865                 }
8866
8867                 if( score > appData.adjudicateLossThreshold ) {
8868                     break;
8869                 }
8870
8871                 count++;
8872             }
8873
8874             if( count >= adjudicateLossPlies ) {
8875                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8876
8877                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8878                     "Xboard adjudication",
8879                     GE_XBOARD );
8880
8881                 return;
8882             }
8883         }
8884
8885         if(Adjudicate(cps)) {
8886             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8887             return; // [HGM] adjudicate: for all automatic game ends
8888         }
8889
8890 #if ZIPPY
8891         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8892             first.initDone) {
8893           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8894                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8895                 SendToICS("draw ");
8896                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8897           }
8898           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8899           ics_user_moved = 1;
8900           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8901                 char buf[3*MSG_SIZ];
8902
8903                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8904                         programStats.score / 100.,
8905                         programStats.depth,
8906                         programStats.time / 100.,
8907                         (unsigned int)programStats.nodes,
8908                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8909                         programStats.movelist);
8910                 SendToICS(buf);
8911           }
8912         }
8913 #endif
8914
8915         /* [AS] Clear stats for next move */
8916         ClearProgramStats();
8917         thinkOutput[0] = NULLCHAR;
8918         hiddenThinkOutputState = 0;
8919
8920         bookHit = NULL;
8921         if (gameMode == TwoMachinesPlay) {
8922             /* [HGM] relaying draw offers moved to after reception of move */
8923             /* and interpreting offer as claim if it brings draw condition */
8924             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8925                 SendToProgram("draw\n", cps->other);
8926             }
8927             if (cps->other->sendTime) {
8928                 SendTimeRemaining(cps->other,
8929                                   cps->other->twoMachinesColor[0] == 'w');
8930             }
8931             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8932             if (firstMove && !bookHit) {
8933                 firstMove = FALSE;
8934                 if (cps->other->useColors) {
8935                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8936                 }
8937                 SendToProgram("go\n", cps->other);
8938             }
8939             cps->other->maybeThinking = TRUE;
8940         }
8941
8942         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8943
8944         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8945
8946         if (!pausing && appData.ringBellAfterMoves) {
8947             if(!roar) RingBell();
8948         }
8949
8950         /*
8951          * Reenable menu items that were disabled while
8952          * machine was thinking
8953          */
8954         if (gameMode != TwoMachinesPlay)
8955             SetUserThinkingEnables();
8956
8957         // [HGM] book: after book hit opponent has received move and is now in force mode
8958         // force the book reply into it, and then fake that it outputted this move by jumping
8959         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8960         if(bookHit) {
8961                 static char bookMove[MSG_SIZ]; // a bit generous?
8962
8963                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8964                 strcat(bookMove, bookHit);
8965                 message = bookMove;
8966                 cps = cps->other;
8967                 programStats.nodes = programStats.depth = programStats.time =
8968                 programStats.score = programStats.got_only_move = 0;
8969                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8970
8971                 if(cps->lastPing != cps->lastPong) {
8972                     savedMessage = message; // args for deferred call
8973                     savedState = cps;
8974                     ScheduleDelayedEvent(DeferredBookMove, 10);
8975                     return;
8976                 }
8977                 goto FakeBookMove;
8978         }
8979
8980         return;
8981     }
8982
8983     /* Set special modes for chess engines.  Later something general
8984      *  could be added here; for now there is just one kludge feature,
8985      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8986      *  when "xboard" is given as an interactive command.
8987      */
8988     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8989         cps->useSigint = FALSE;
8990         cps->useSigterm = FALSE;
8991     }
8992     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8993       ParseFeatures(message+8, cps);
8994       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8995     }
8996
8997     if (!strncmp(message, "setup ", 6) && 
8998         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8999           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9000                                         ) { // [HGM] allow first engine to define opening position
9001       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9002       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9003       *buf = NULLCHAR;
9004       if(sscanf(message, "setup (%s", buf) == 1) {
9005         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9006         ASSIGN(appData.pieceToCharTable, buf);
9007       }
9008       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9009       if(dummy >= 3) {
9010         while(message[s] && message[s++] != ' ');
9011         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9012            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9013             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9014             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9015           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9016           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9017           startedFromSetupPosition = FALSE;
9018         }
9019       }
9020       if(startedFromSetupPosition) return;
9021       ParseFEN(boards[0], &dummy, message+s, FALSE);
9022       DrawPosition(TRUE, boards[0]);
9023       CopyBoard(initialPosition, boards[0]);
9024       startedFromSetupPosition = TRUE;
9025       return;
9026     }
9027     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9028       ChessSquare piece = WhitePawn;
9029       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9030       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9031       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9032       piece += CharToPiece(ID & 255) - WhitePawn;
9033       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9034       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9035       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9036       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9037       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9038       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9039                                                && gameInfo.variant != VariantGreat
9040                                                && gameInfo.variant != VariantFairy    ) return;
9041       if(piece < EmptySquare) {
9042         pieceDefs = TRUE;
9043         ASSIGN(pieceDesc[piece], buf1);
9044         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9045       }
9046       return;
9047     }
9048     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9049       promoSweep = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9050       Sweep(0);
9051       return;
9052     }
9053     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9054      * want this, I was asked to put it in, and obliged.
9055      */
9056     if (!strncmp(message, "setboard ", 9)) {
9057         Board initial_position;
9058
9059         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9060
9061         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9062             DisplayError(_("Bad FEN received from engine"), 0);
9063             return ;
9064         } else {
9065            Reset(TRUE, FALSE);
9066            CopyBoard(boards[0], initial_position);
9067            initialRulePlies = FENrulePlies;
9068            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9069            else gameMode = MachinePlaysBlack;
9070            DrawPosition(FALSE, boards[currentMove]);
9071         }
9072         return;
9073     }
9074
9075     /*
9076      * Look for communication commands
9077      */
9078     if (!strncmp(message, "telluser ", 9)) {
9079         if(message[9] == '\\' && message[10] == '\\')
9080             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9081         PlayTellSound();
9082         DisplayNote(message + 9);
9083         return;
9084     }
9085     if (!strncmp(message, "tellusererror ", 14)) {
9086         cps->userError = 1;
9087         if(message[14] == '\\' && message[15] == '\\')
9088             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9089         PlayTellSound();
9090         DisplayError(message + 14, 0);
9091         return;
9092     }
9093     if (!strncmp(message, "tellopponent ", 13)) {
9094       if (appData.icsActive) {
9095         if (loggedOn) {
9096           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9097           SendToICS(buf1);
9098         }
9099       } else {
9100         DisplayNote(message + 13);
9101       }
9102       return;
9103     }
9104     if (!strncmp(message, "tellothers ", 11)) {
9105       if (appData.icsActive) {
9106         if (loggedOn) {
9107           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9108           SendToICS(buf1);
9109         }
9110       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9111       return;
9112     }
9113     if (!strncmp(message, "tellall ", 8)) {
9114       if (appData.icsActive) {
9115         if (loggedOn) {
9116           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9117           SendToICS(buf1);
9118         }
9119       } else {
9120         DisplayNote(message + 8);
9121       }
9122       return;
9123     }
9124     if (strncmp(message, "warning", 7) == 0) {
9125         /* Undocumented feature, use tellusererror in new code */
9126         DisplayError(message, 0);
9127         return;
9128     }
9129     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9130         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9131         strcat(realname, " query");
9132         AskQuestion(realname, buf2, buf1, cps->pr);
9133         return;
9134     }
9135     /* Commands from the engine directly to ICS.  We don't allow these to be
9136      *  sent until we are logged on. Crafty kibitzes have been known to
9137      *  interfere with the login process.
9138      */
9139     if (loggedOn) {
9140         if (!strncmp(message, "tellics ", 8)) {
9141             SendToICS(message + 8);
9142             SendToICS("\n");
9143             return;
9144         }
9145         if (!strncmp(message, "tellicsnoalias ", 15)) {
9146             SendToICS(ics_prefix);
9147             SendToICS(message + 15);
9148             SendToICS("\n");
9149             return;
9150         }
9151         /* The following are for backward compatibility only */
9152         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9153             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9154             SendToICS(ics_prefix);
9155             SendToICS(message);
9156             SendToICS("\n");
9157             return;
9158         }
9159     }
9160     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9161         if(initPing == cps->lastPong) {
9162             if(gameInfo.variant == VariantUnknown) {
9163                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9164                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9165                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9166             }
9167             initPing = -1;
9168         }
9169         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9170             abortEngineThink = FALSE;
9171             DisplayMessage("", "");
9172             ThawUI();
9173         }
9174         return;
9175     }
9176     if(!strncmp(message, "highlight ", 10)) {
9177         if(appData.testLegality && !*engineVariant && appData.markers) return;
9178         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9179         return;
9180     }
9181     if(!strncmp(message, "click ", 6)) {
9182         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9183         if(appData.testLegality || !appData.oneClick) return;
9184         sscanf(message+6, "%c%d%c", &f, &y, &c);
9185         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9186         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9187         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9188         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9189         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9190         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9191             LeftClick(Release, lastLeftX, lastLeftY);
9192         controlKey  = (c == ',');
9193         LeftClick(Press, x, y);
9194         LeftClick(Release, x, y);
9195         first.highlight = f;
9196         return;
9197     }
9198     /*
9199      * If the move is illegal, cancel it and redraw the board.
9200      * Also deal with other error cases.  Matching is rather loose
9201      * here to accommodate engines written before the spec.
9202      */
9203     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9204         strncmp(message, "Error", 5) == 0) {
9205         if (StrStr(message, "name") ||
9206             StrStr(message, "rating") || StrStr(message, "?") ||
9207             StrStr(message, "result") || StrStr(message, "board") ||
9208             StrStr(message, "bk") || StrStr(message, "computer") ||
9209             StrStr(message, "variant") || StrStr(message, "hint") ||
9210             StrStr(message, "random") || StrStr(message, "depth") ||
9211             StrStr(message, "accepted")) {
9212             return;
9213         }
9214         if (StrStr(message, "protover")) {
9215           /* Program is responding to input, so it's apparently done
9216              initializing, and this error message indicates it is
9217              protocol version 1.  So we don't need to wait any longer
9218              for it to initialize and send feature commands. */
9219           FeatureDone(cps, 1);
9220           cps->protocolVersion = 1;
9221           return;
9222         }
9223         cps->maybeThinking = FALSE;
9224
9225         if (StrStr(message, "draw")) {
9226             /* Program doesn't have "draw" command */
9227             cps->sendDrawOffers = 0;
9228             return;
9229         }
9230         if (cps->sendTime != 1 &&
9231             (StrStr(message, "time") || StrStr(message, "otim"))) {
9232           /* Program apparently doesn't have "time" or "otim" command */
9233           cps->sendTime = 0;
9234           return;
9235         }
9236         if (StrStr(message, "analyze")) {
9237             cps->analysisSupport = FALSE;
9238             cps->analyzing = FALSE;
9239 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9240             EditGameEvent(); // [HGM] try to preserve loaded game
9241             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9242             DisplayError(buf2, 0);
9243             return;
9244         }
9245         if (StrStr(message, "(no matching move)st")) {
9246           /* Special kludge for GNU Chess 4 only */
9247           cps->stKludge = TRUE;
9248           SendTimeControl(cps, movesPerSession, timeControl,
9249                           timeIncrement, appData.searchDepth,
9250                           searchTime);
9251           return;
9252         }
9253         if (StrStr(message, "(no matching move)sd")) {
9254           /* Special kludge for GNU Chess 4 only */
9255           cps->sdKludge = TRUE;
9256           SendTimeControl(cps, movesPerSession, timeControl,
9257                           timeIncrement, appData.searchDepth,
9258                           searchTime);
9259           return;
9260         }
9261         if (!StrStr(message, "llegal")) {
9262             return;
9263         }
9264         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9265             gameMode == IcsIdle) return;
9266         if (forwardMostMove <= backwardMostMove) return;
9267         if (pausing) PauseEvent();
9268       if(appData.forceIllegal) {
9269             // [HGM] illegal: machine refused move; force position after move into it
9270           SendToProgram("force\n", cps);
9271           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9272                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9273                 // when black is to move, while there might be nothing on a2 or black
9274                 // might already have the move. So send the board as if white has the move.
9275                 // But first we must change the stm of the engine, as it refused the last move
9276                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9277                 if(WhiteOnMove(forwardMostMove)) {
9278                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9279                     SendBoard(cps, forwardMostMove); // kludgeless board
9280                 } else {
9281                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9282                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9283                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9284                 }
9285           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9286             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9287                  gameMode == TwoMachinesPlay)
9288               SendToProgram("go\n", cps);
9289             return;
9290       } else
9291         if (gameMode == PlayFromGameFile) {
9292             /* Stop reading this game file */
9293             gameMode = EditGame;
9294             ModeHighlight();
9295         }
9296         /* [HGM] illegal-move claim should forfeit game when Xboard */
9297         /* only passes fully legal moves                            */
9298         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9299             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9300                                 "False illegal-move claim", GE_XBOARD );
9301             return; // do not take back move we tested as valid
9302         }
9303         currentMove = forwardMostMove-1;
9304         DisplayMove(currentMove-1); /* before DisplayMoveError */
9305         SwitchClocks(forwardMostMove-1); // [HGM] race
9306         DisplayBothClocks();
9307         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9308                 parseList[currentMove], _(cps->which));
9309         DisplayMoveError(buf1);
9310         DrawPosition(FALSE, boards[currentMove]);
9311
9312         SetUserThinkingEnables();
9313         return;
9314     }
9315     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9316         /* Program has a broken "time" command that
9317            outputs a string not ending in newline.
9318            Don't use it. */
9319         cps->sendTime = 0;
9320     }
9321     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9322         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9323             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9324     }
9325
9326     /*
9327      * If chess program startup fails, exit with an error message.
9328      * Attempts to recover here are futile. [HGM] Well, we try anyway
9329      */
9330     if ((StrStr(message, "unknown host") != NULL)
9331         || (StrStr(message, "No remote directory") != NULL)
9332         || (StrStr(message, "not found") != NULL)
9333         || (StrStr(message, "No such file") != NULL)
9334         || (StrStr(message, "can't alloc") != NULL)
9335         || (StrStr(message, "Permission denied") != NULL)) {
9336
9337         cps->maybeThinking = FALSE;
9338         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9339                 _(cps->which), cps->program, cps->host, message);
9340         RemoveInputSource(cps->isr);
9341         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9342             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9343             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9344         }
9345         return;
9346     }
9347
9348     /*
9349      * Look for hint output
9350      */
9351     if (sscanf(message, "Hint: %s", buf1) == 1) {
9352         if (cps == &first && hintRequested) {
9353             hintRequested = FALSE;
9354             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9355                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9356                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9357                                     PosFlags(forwardMostMove),
9358                                     fromY, fromX, toY, toX, promoChar, buf1);
9359                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9360                 DisplayInformation(buf2);
9361             } else {
9362                 /* Hint move could not be parsed!? */
9363               snprintf(buf2, sizeof(buf2),
9364                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9365                         buf1, _(cps->which));
9366                 DisplayError(buf2, 0);
9367             }
9368         } else {
9369           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9370         }
9371         return;
9372     }
9373
9374     /*
9375      * Ignore other messages if game is not in progress
9376      */
9377     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9378         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9379
9380     /*
9381      * look for win, lose, draw, or draw offer
9382      */
9383     if (strncmp(message, "1-0", 3) == 0) {
9384         char *p, *q, *r = "";
9385         p = strchr(message, '{');
9386         if (p) {
9387             q = strchr(p, '}');
9388             if (q) {
9389                 *q = NULLCHAR;
9390                 r = p + 1;
9391             }
9392         }
9393         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9394         return;
9395     } else if (strncmp(message, "0-1", 3) == 0) {
9396         char *p, *q, *r = "";
9397         p = strchr(message, '{');
9398         if (p) {
9399             q = strchr(p, '}');
9400             if (q) {
9401                 *q = NULLCHAR;
9402                 r = p + 1;
9403             }
9404         }
9405         /* Kludge for Arasan 4.1 bug */
9406         if (strcmp(r, "Black resigns") == 0) {
9407             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9408             return;
9409         }
9410         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9411         return;
9412     } else if (strncmp(message, "1/2", 3) == 0) {
9413         char *p, *q, *r = "";
9414         p = strchr(message, '{');
9415         if (p) {
9416             q = strchr(p, '}');
9417             if (q) {
9418                 *q = NULLCHAR;
9419                 r = p + 1;
9420             }
9421         }
9422
9423         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9424         return;
9425
9426     } else if (strncmp(message, "White resign", 12) == 0) {
9427         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9428         return;
9429     } else if (strncmp(message, "Black resign", 12) == 0) {
9430         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9431         return;
9432     } else if (strncmp(message, "White matches", 13) == 0 ||
9433                strncmp(message, "Black matches", 13) == 0   ) {
9434         /* [HGM] ignore GNUShogi noises */
9435         return;
9436     } else if (strncmp(message, "White", 5) == 0 &&
9437                message[5] != '(' &&
9438                StrStr(message, "Black") == NULL) {
9439         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9440         return;
9441     } else if (strncmp(message, "Black", 5) == 0 &&
9442                message[5] != '(') {
9443         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9444         return;
9445     } else if (strcmp(message, "resign") == 0 ||
9446                strcmp(message, "computer resigns") == 0) {
9447         switch (gameMode) {
9448           case MachinePlaysBlack:
9449           case IcsPlayingBlack:
9450             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9451             break;
9452           case MachinePlaysWhite:
9453           case IcsPlayingWhite:
9454             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9455             break;
9456           case TwoMachinesPlay:
9457             if (cps->twoMachinesColor[0] == 'w')
9458               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9459             else
9460               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9461             break;
9462           default:
9463             /* can't happen */
9464             break;
9465         }
9466         return;
9467     } else if (strncmp(message, "opponent mates", 14) == 0) {
9468         switch (gameMode) {
9469           case MachinePlaysBlack:
9470           case IcsPlayingBlack:
9471             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9472             break;
9473           case MachinePlaysWhite:
9474           case IcsPlayingWhite:
9475             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9476             break;
9477           case TwoMachinesPlay:
9478             if (cps->twoMachinesColor[0] == 'w')
9479               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9480             else
9481               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9482             break;
9483           default:
9484             /* can't happen */
9485             break;
9486         }
9487         return;
9488     } else if (strncmp(message, "computer mates", 14) == 0) {
9489         switch (gameMode) {
9490           case MachinePlaysBlack:
9491           case IcsPlayingBlack:
9492             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9493             break;
9494           case MachinePlaysWhite:
9495           case IcsPlayingWhite:
9496             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9497             break;
9498           case TwoMachinesPlay:
9499             if (cps->twoMachinesColor[0] == 'w')
9500               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9501             else
9502               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9503             break;
9504           default:
9505             /* can't happen */
9506             break;
9507         }
9508         return;
9509     } else if (strncmp(message, "checkmate", 9) == 0) {
9510         if (WhiteOnMove(forwardMostMove)) {
9511             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9512         } else {
9513             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9514         }
9515         return;
9516     } else if (strstr(message, "Draw") != NULL ||
9517                strstr(message, "game is a draw") != NULL) {
9518         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9519         return;
9520     } else if (strstr(message, "offer") != NULL &&
9521                strstr(message, "draw") != NULL) {
9522 #if ZIPPY
9523         if (appData.zippyPlay && first.initDone) {
9524             /* Relay offer to ICS */
9525             SendToICS(ics_prefix);
9526             SendToICS("draw\n");
9527         }
9528 #endif
9529         cps->offeredDraw = 2; /* valid until this engine moves twice */
9530         if (gameMode == TwoMachinesPlay) {
9531             if (cps->other->offeredDraw) {
9532                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9533             /* [HGM] in two-machine mode we delay relaying draw offer      */
9534             /* until after we also have move, to see if it is really claim */
9535             }
9536         } else if (gameMode == MachinePlaysWhite ||
9537                    gameMode == MachinePlaysBlack) {
9538           if (userOfferedDraw) {
9539             DisplayInformation(_("Machine accepts your draw offer"));
9540             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9541           } else {
9542             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9543           }
9544         }
9545     }
9546
9547
9548     /*
9549      * Look for thinking output
9550      */
9551     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9552           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9553                                 ) {
9554         int plylev, mvleft, mvtot, curscore, time;
9555         char mvname[MOVE_LEN];
9556         u64 nodes; // [DM]
9557         char plyext;
9558         int ignore = FALSE;
9559         int prefixHint = FALSE;
9560         mvname[0] = NULLCHAR;
9561
9562         switch (gameMode) {
9563           case MachinePlaysBlack:
9564           case IcsPlayingBlack:
9565             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9566             break;
9567           case MachinePlaysWhite:
9568           case IcsPlayingWhite:
9569             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9570             break;
9571           case AnalyzeMode:
9572           case AnalyzeFile:
9573             break;
9574           case IcsObserving: /* [DM] icsEngineAnalyze */
9575             if (!appData.icsEngineAnalyze) ignore = TRUE;
9576             break;
9577           case TwoMachinesPlay:
9578             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9579                 ignore = TRUE;
9580             }
9581             break;
9582           default:
9583             ignore = TRUE;
9584             break;
9585         }
9586
9587         if (!ignore) {
9588             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9589             buf1[0] = NULLCHAR;
9590             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9591                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9592                 char score_buf[MSG_SIZ];
9593
9594                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9595                     nodes += u64Const(0x100000000);
9596
9597                 if (plyext != ' ' && plyext != '\t') {
9598                     time *= 100;
9599                 }
9600
9601                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9602                 if( cps->scoreIsAbsolute &&
9603                     ( gameMode == MachinePlaysBlack ||
9604                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9605                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9606                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9607                      !WhiteOnMove(currentMove)
9608                     ) )
9609                 {
9610                     curscore = -curscore;
9611                 }
9612
9613                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9614
9615                 if(*bestMove) { // rememer time best EPD move was first found
9616                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9617                     ChessMove mt;
9618                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9619                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9620                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9621                 }
9622
9623                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9624                         char buf[MSG_SIZ];
9625                         FILE *f;
9626                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9627                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9628                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9629                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9630                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9631                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9632                                 fclose(f);
9633                         }
9634                         else
9635                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9636                           DisplayError(_("failed writing PV"), 0);
9637                 }
9638
9639                 tempStats.depth = plylev;
9640                 tempStats.nodes = nodes;
9641                 tempStats.time = time;
9642                 tempStats.score = curscore;
9643                 tempStats.got_only_move = 0;
9644
9645                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9646                         int ticklen;
9647
9648                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9649                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9650                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9651                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9652                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9653                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9654                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9655                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9656                 }
9657
9658                 /* Buffer overflow protection */
9659                 if (pv[0] != NULLCHAR) {
9660                     if (strlen(pv) >= sizeof(tempStats.movelist)
9661                         && appData.debugMode) {
9662                         fprintf(debugFP,
9663                                 "PV is too long; using the first %u bytes.\n",
9664                                 (unsigned) sizeof(tempStats.movelist) - 1);
9665                     }
9666
9667                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9668                 } else {
9669                     sprintf(tempStats.movelist, " no PV\n");
9670                 }
9671
9672                 if (tempStats.seen_stat) {
9673                     tempStats.ok_to_send = 1;
9674                 }
9675
9676                 if (strchr(tempStats.movelist, '(') != NULL) {
9677                     tempStats.line_is_book = 1;
9678                     tempStats.nr_moves = 0;
9679                     tempStats.moves_left = 0;
9680                 } else {
9681                     tempStats.line_is_book = 0;
9682                 }
9683
9684                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9685                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9686
9687                 SendProgramStatsToFrontend( cps, &tempStats );
9688
9689                 /*
9690                     [AS] Protect the thinkOutput buffer from overflow... this
9691                     is only useful if buf1 hasn't overflowed first!
9692                 */
9693                 if(curscore >= MATE_SCORE) 
9694                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9695                 else if(curscore <= -MATE_SCORE) 
9696                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9697                 else
9698                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9699                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9700                          plylev,
9701                          (gameMode == TwoMachinesPlay ?
9702                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9703                          score_buf,
9704                          prefixHint ? lastHint : "",
9705                          prefixHint ? " " : "" );
9706
9707                 if( buf1[0] != NULLCHAR ) {
9708                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9709
9710                     if( strlen(pv) > max_len ) {
9711                         if( appData.debugMode) {
9712                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9713                         }
9714                         pv[max_len+1] = '\0';
9715                     }
9716
9717                     strcat( thinkOutput, pv);
9718                 }
9719
9720                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9721                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9722                     DisplayMove(currentMove - 1);
9723                 }
9724                 return;
9725
9726             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9727                 /* crafty (9.25+) says "(only move) <move>"
9728                  * if there is only 1 legal move
9729                  */
9730                 sscanf(p, "(only move) %s", buf1);
9731                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9732                 sprintf(programStats.movelist, "%s (only move)", buf1);
9733                 programStats.depth = 1;
9734                 programStats.nr_moves = 1;
9735                 programStats.moves_left = 1;
9736                 programStats.nodes = 1;
9737                 programStats.time = 1;
9738                 programStats.got_only_move = 1;
9739
9740                 /* Not really, but we also use this member to
9741                    mean "line isn't going to change" (Crafty
9742                    isn't searching, so stats won't change) */
9743                 programStats.line_is_book = 1;
9744
9745                 SendProgramStatsToFrontend( cps, &programStats );
9746
9747                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9748                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9749                     DisplayMove(currentMove - 1);
9750                 }
9751                 return;
9752             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9753                               &time, &nodes, &plylev, &mvleft,
9754                               &mvtot, mvname) >= 5) {
9755                 /* The stat01: line is from Crafty (9.29+) in response
9756                    to the "." command */
9757                 programStats.seen_stat = 1;
9758                 cps->maybeThinking = TRUE;
9759
9760                 if (programStats.got_only_move || !appData.periodicUpdates)
9761                   return;
9762
9763                 programStats.depth = plylev;
9764                 programStats.time = time;
9765                 programStats.nodes = nodes;
9766                 programStats.moves_left = mvleft;
9767                 programStats.nr_moves = mvtot;
9768                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9769                 programStats.ok_to_send = 1;
9770                 programStats.movelist[0] = '\0';
9771
9772                 SendProgramStatsToFrontend( cps, &programStats );
9773
9774                 return;
9775
9776             } else if (strncmp(message,"++",2) == 0) {
9777                 /* Crafty 9.29+ outputs this */
9778                 programStats.got_fail = 2;
9779                 return;
9780
9781             } else if (strncmp(message,"--",2) == 0) {
9782                 /* Crafty 9.29+ outputs this */
9783                 programStats.got_fail = 1;
9784                 return;
9785
9786             } else if (thinkOutput[0] != NULLCHAR &&
9787                        strncmp(message, "    ", 4) == 0) {
9788                 unsigned message_len;
9789
9790                 p = message;
9791                 while (*p && *p == ' ') p++;
9792
9793                 message_len = strlen( p );
9794
9795                 /* [AS] Avoid buffer overflow */
9796                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9797                     strcat(thinkOutput, " ");
9798                     strcat(thinkOutput, p);
9799                 }
9800
9801                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9802                     strcat(programStats.movelist, " ");
9803                     strcat(programStats.movelist, p);
9804                 }
9805
9806                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9807                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9808                     DisplayMove(currentMove - 1);
9809                 }
9810                 return;
9811             }
9812         }
9813         else {
9814             buf1[0] = NULLCHAR;
9815
9816             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9817                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9818             {
9819                 ChessProgramStats cpstats;
9820
9821                 if (plyext != ' ' && plyext != '\t') {
9822                     time *= 100;
9823                 }
9824
9825                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9826                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9827                     curscore = -curscore;
9828                 }
9829
9830                 cpstats.depth = plylev;
9831                 cpstats.nodes = nodes;
9832                 cpstats.time = time;
9833                 cpstats.score = curscore;
9834                 cpstats.got_only_move = 0;
9835                 cpstats.movelist[0] = '\0';
9836
9837                 if (buf1[0] != NULLCHAR) {
9838                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9839                 }
9840
9841                 cpstats.ok_to_send = 0;
9842                 cpstats.line_is_book = 0;
9843                 cpstats.nr_moves = 0;
9844                 cpstats.moves_left = 0;
9845
9846                 SendProgramStatsToFrontend( cps, &cpstats );
9847             }
9848         }
9849     }
9850 }
9851
9852
9853 /* Parse a game score from the character string "game", and
9854    record it as the history of the current game.  The game
9855    score is NOT assumed to start from the standard position.
9856    The display is not updated in any way.
9857    */
9858 void
9859 ParseGameHistory (char *game)
9860 {
9861     ChessMove moveType;
9862     int fromX, fromY, toX, toY, boardIndex;
9863     char promoChar;
9864     char *p, *q;
9865     char buf[MSG_SIZ];
9866
9867     if (appData.debugMode)
9868       fprintf(debugFP, "Parsing game history: %s\n", game);
9869
9870     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9871     gameInfo.site = StrSave(appData.icsHost);
9872     gameInfo.date = PGNDate();
9873     gameInfo.round = StrSave("-");
9874
9875     /* Parse out names of players */
9876     while (*game == ' ') game++;
9877     p = buf;
9878     while (*game != ' ') *p++ = *game++;
9879     *p = NULLCHAR;
9880     gameInfo.white = StrSave(buf);
9881     while (*game == ' ') game++;
9882     p = buf;
9883     while (*game != ' ' && *game != '\n') *p++ = *game++;
9884     *p = NULLCHAR;
9885     gameInfo.black = StrSave(buf);
9886
9887     /* Parse moves */
9888     boardIndex = blackPlaysFirst ? 1 : 0;
9889     yynewstr(game);
9890     for (;;) {
9891         yyboardindex = boardIndex;
9892         moveType = (ChessMove) Myylex();
9893         switch (moveType) {
9894           case IllegalMove:             /* maybe suicide chess, etc. */
9895   if (appData.debugMode) {
9896     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9897     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9898     setbuf(debugFP, NULL);
9899   }
9900           case WhitePromotion:
9901           case BlackPromotion:
9902           case WhiteNonPromotion:
9903           case BlackNonPromotion:
9904           case NormalMove:
9905           case FirstLeg:
9906           case WhiteCapturesEnPassant:
9907           case BlackCapturesEnPassant:
9908           case WhiteKingSideCastle:
9909           case WhiteQueenSideCastle:
9910           case BlackKingSideCastle:
9911           case BlackQueenSideCastle:
9912           case WhiteKingSideCastleWild:
9913           case WhiteQueenSideCastleWild:
9914           case BlackKingSideCastleWild:
9915           case BlackQueenSideCastleWild:
9916           /* PUSH Fabien */
9917           case WhiteHSideCastleFR:
9918           case WhiteASideCastleFR:
9919           case BlackHSideCastleFR:
9920           case BlackASideCastleFR:
9921           /* POP Fabien */
9922             fromX = currentMoveString[0] - AAA;
9923             fromY = currentMoveString[1] - ONE;
9924             toX = currentMoveString[2] - AAA;
9925             toY = currentMoveString[3] - ONE;
9926             promoChar = currentMoveString[4];
9927             break;
9928           case WhiteDrop:
9929           case BlackDrop:
9930             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9931             fromX = moveType == WhiteDrop ?
9932               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9933             (int) CharToPiece(ToLower(currentMoveString[0]));
9934             fromY = DROP_RANK;
9935             toX = currentMoveString[2] - AAA;
9936             toY = currentMoveString[3] - ONE;
9937             promoChar = NULLCHAR;
9938             break;
9939           case AmbiguousMove:
9940             /* bug? */
9941             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9942   if (appData.debugMode) {
9943     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
9950             /* bug? */
9951             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9952   if (appData.debugMode) {
9953     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9954     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9955     setbuf(debugFP, NULL);
9956   }
9957             DisplayError(buf, 0);
9958             return;
9959           case EndOfFile:
9960             if (boardIndex < backwardMostMove) {
9961                 /* Oops, gap.  How did that happen? */
9962                 DisplayError(_("Gap in move list"), 0);
9963                 return;
9964             }
9965             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9966             if (boardIndex > forwardMostMove) {
9967                 forwardMostMove = boardIndex;
9968             }
9969             return;
9970           case ElapsedTime:
9971             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9972                 strcat(parseList[boardIndex-1], " ");
9973                 strcat(parseList[boardIndex-1], yy_text);
9974             }
9975             continue;
9976           case Comment:
9977           case PGNTag:
9978           case NAG:
9979           default:
9980             /* ignore */
9981             continue;
9982           case WhiteWins:
9983           case BlackWins:
9984           case GameIsDrawn:
9985           case GameUnfinished:
9986             if (gameMode == IcsExamining) {
9987                 if (boardIndex < backwardMostMove) {
9988                     /* Oops, gap.  How did that happen? */
9989                     return;
9990                 }
9991                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9992                 return;
9993             }
9994             gameInfo.result = moveType;
9995             p = strchr(yy_text, '{');
9996             if (p == NULL) p = strchr(yy_text, '(');
9997             if (p == NULL) {
9998                 p = yy_text;
9999                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10000             } else {
10001                 q = strchr(p, *p == '{' ? '}' : ')');
10002                 if (q != NULL) *q = NULLCHAR;
10003                 p++;
10004             }
10005             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10006             gameInfo.resultDetails = StrSave(p);
10007             continue;
10008         }
10009         if (boardIndex >= forwardMostMove &&
10010             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10011             backwardMostMove = blackPlaysFirst ? 1 : 0;
10012             return;
10013         }
10014         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10015                                  fromY, fromX, toY, toX, promoChar,
10016                                  parseList[boardIndex]);
10017         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10018         /* currentMoveString is set as a side-effect of yylex */
10019         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10020         strcat(moveList[boardIndex], "\n");
10021         boardIndex++;
10022         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10023         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10024           case MT_NONE:
10025           case MT_STALEMATE:
10026           default:
10027             break;
10028           case MT_CHECK:
10029             if(!IS_SHOGI(gameInfo.variant))
10030                 strcat(parseList[boardIndex - 1], "+");
10031             break;
10032           case MT_CHECKMATE:
10033           case MT_STAINMATE:
10034             strcat(parseList[boardIndex - 1], "#");
10035             break;
10036         }
10037     }
10038 }
10039
10040
10041 /* Apply a move to the given board  */
10042 void
10043 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10044 {
10045   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10046   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10047
10048     /* [HGM] compute & store e.p. status and castling rights for new position */
10049     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10050
10051       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10052       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10053       board[EP_STATUS] = EP_NONE;
10054       board[EP_FILE] = board[EP_RANK] = 100;
10055
10056   if (fromY == DROP_RANK) {
10057         /* must be first */
10058         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10059             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10060             return;
10061         }
10062         piece = board[toY][toX] = (ChessSquare) fromX;
10063   } else {
10064 //      ChessSquare victim;
10065       int i;
10066
10067       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10068 //           victim = board[killY][killX],
10069            killed = board[killY][killX],
10070            board[killY][killX] = EmptySquare,
10071            board[EP_STATUS] = EP_CAPTURE;
10072            if( kill2X >= 0 && kill2Y >= 0)
10073              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10074       }
10075
10076       if( board[toY][toX] != EmptySquare ) {
10077            board[EP_STATUS] = EP_CAPTURE;
10078            if( (fromX != toX || fromY != toY) && // not igui!
10079                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10080                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10081                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10082            }
10083       }
10084
10085       pawn = board[fromY][fromX];
10086       if( pawn == WhiteLance || pawn == BlackLance ) {
10087            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10088                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10089                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10090            }
10091       }
10092       if( pawn == WhitePawn ) {
10093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10094                board[EP_STATUS] = EP_PAWN_MOVE;
10095            if( toY-fromY>=2) {
10096                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10097                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10098                         gameInfo.variant != VariantBerolina || toX < fromX)
10099                       board[EP_STATUS] = toX | berolina;
10100                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10101                         gameInfo.variant != VariantBerolina || toX > fromX)
10102                       board[EP_STATUS] = toX;
10103            }
10104       } else
10105       if( pawn == BlackPawn ) {
10106            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10107                board[EP_STATUS] = EP_PAWN_MOVE;
10108            if( toY-fromY<= -2) {
10109                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10110                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10111                         gameInfo.variant != VariantBerolina || toX < fromX)
10112                       board[EP_STATUS] = toX | berolina;
10113                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10114                         gameInfo.variant != VariantBerolina || toX > fromX)
10115                       board[EP_STATUS] = toX;
10116            }
10117        }
10118
10119        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10120        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10121        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10122        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10123
10124        for(i=0; i<nrCastlingRights; i++) {
10125            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10126               board[CASTLING][i] == toX   && castlingRank[i] == toY
10127              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10128        }
10129
10130        if(gameInfo.variant == VariantSChess) { // update virginity
10131            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10132            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10133            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10134            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10135        }
10136
10137      if (fromX == toX && fromY == toY) return;
10138
10139      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10140      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10141      if(gameInfo.variant == VariantKnightmate)
10142          king += (int) WhiteUnicorn - (int) WhiteKing;
10143
10144     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10145        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10146         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10147         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10148         board[EP_STATUS] = EP_NONE; // capture was fake!
10149     } else
10150     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10151         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10152         board[toY][toX] = piece;
10153         board[EP_STATUS] = EP_NONE; // capture was fake!
10154     } else
10155     /* Code added by Tord: */
10156     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10157     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10158         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10159       board[EP_STATUS] = EP_NONE; // capture was fake!
10160       board[fromY][fromX] = EmptySquare;
10161       board[toY][toX] = EmptySquare;
10162       if((toX > fromX) != (piece == WhiteRook)) {
10163         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10164       } else {
10165         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10166       }
10167     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10168                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10169       board[EP_STATUS] = EP_NONE;
10170       board[fromY][fromX] = EmptySquare;
10171       board[toY][toX] = EmptySquare;
10172       if((toX > fromX) != (piece == BlackRook)) {
10173         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10174       } else {
10175         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10176       }
10177     /* End of code added by Tord */
10178
10179     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10180         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10181         board[toY][toX] = piece;
10182     } else if (board[fromY][fromX] == king
10183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10184         && toY == fromY && toX > fromX+1) {
10185         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10186         board[fromY][toX-1] = board[fromY][rookX];
10187         board[fromY][rookX] = EmptySquare;
10188         board[fromY][fromX] = EmptySquare;
10189         board[toY][toX] = king;
10190     } else if (board[fromY][fromX] == king
10191         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10192                && toY == fromY && toX < fromX-1) {
10193         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10194         board[fromY][toX+1] = board[fromY][rookX];
10195         board[fromY][rookX] = EmptySquare;
10196         board[fromY][fromX] = EmptySquare;
10197         board[toY][toX] = king;
10198     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10199                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10200                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10201                ) {
10202         /* white pawn promotion */
10203         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10204         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10205             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10206         board[fromY][fromX] = EmptySquare;
10207     } else if ((fromY >= BOARD_HEIGHT>>1)
10208                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10209                && (toX != fromX)
10210                && gameInfo.variant != VariantXiangqi
10211                && gameInfo.variant != VariantBerolina
10212                && (pawn == WhitePawn)
10213                && (board[toY][toX] == EmptySquare)) {
10214         board[fromY][fromX] = EmptySquare;
10215         board[toY][toX] = piece;
10216         if(toY == epRank - 128 + 1)
10217             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10218         else
10219             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10220     } else if ((fromY == BOARD_HEIGHT-4)
10221                && (toX == fromX)
10222                && gameInfo.variant == VariantBerolina
10223                && (board[fromY][fromX] == WhitePawn)
10224                && (board[toY][toX] == EmptySquare)) {
10225         board[fromY][fromX] = EmptySquare;
10226         board[toY][toX] = WhitePawn;
10227         if(oldEP & EP_BEROLIN_A) {
10228                 captured = board[fromY][fromX-1];
10229                 board[fromY][fromX-1] = EmptySquare;
10230         }else{  captured = board[fromY][fromX+1];
10231                 board[fromY][fromX+1] = EmptySquare;
10232         }
10233     } else if (board[fromY][fromX] == king
10234         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10235                && toY == fromY && toX > fromX+1) {
10236         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10237         board[fromY][toX-1] = board[fromY][rookX];
10238         board[fromY][rookX] = EmptySquare;
10239         board[fromY][fromX] = EmptySquare;
10240         board[toY][toX] = king;
10241     } else if (board[fromY][fromX] == king
10242         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10243                && toY == fromY && toX < fromX-1) {
10244         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10245         board[fromY][toX+1] = board[fromY][rookX];
10246         board[fromY][rookX] = EmptySquare;
10247         board[fromY][fromX] = EmptySquare;
10248         board[toY][toX] = king;
10249     } else if (fromY == 7 && fromX == 3
10250                && board[fromY][fromX] == BlackKing
10251                && toY == 7 && toX == 5) {
10252         board[fromY][fromX] = EmptySquare;
10253         board[toY][toX] = BlackKing;
10254         board[fromY][7] = EmptySquare;
10255         board[toY][4] = BlackRook;
10256     } else if (fromY == 7 && fromX == 3
10257                && board[fromY][fromX] == BlackKing
10258                && toY == 7 && toX == 1) {
10259         board[fromY][fromX] = EmptySquare;
10260         board[toY][toX] = BlackKing;
10261         board[fromY][0] = EmptySquare;
10262         board[toY][2] = BlackRook;
10263     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10264                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10265                && toY < promoRank && promoChar
10266                ) {
10267         /* black pawn promotion */
10268         board[toY][toX] = CharToPiece(ToLower(promoChar));
10269         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10270             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10271         board[fromY][fromX] = EmptySquare;
10272     } else if ((fromY < BOARD_HEIGHT>>1)
10273                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10274                && (toX != fromX)
10275                && gameInfo.variant != VariantXiangqi
10276                && gameInfo.variant != VariantBerolina
10277                && (pawn == BlackPawn)
10278                && (board[toY][toX] == EmptySquare)) {
10279         board[fromY][fromX] = EmptySquare;
10280         board[toY][toX] = piece;
10281         if(toY == epRank - 128 - 1)
10282             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10283         else
10284             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10285     } else if ((fromY == 3)
10286                && (toX == fromX)
10287                && gameInfo.variant == VariantBerolina
10288                && (board[fromY][fromX] == BlackPawn)
10289                && (board[toY][toX] == EmptySquare)) {
10290         board[fromY][fromX] = EmptySquare;
10291         board[toY][toX] = BlackPawn;
10292         if(oldEP & EP_BEROLIN_A) {
10293                 captured = board[fromY][fromX-1];
10294                 board[fromY][fromX-1] = EmptySquare;
10295         }else{  captured = board[fromY][fromX+1];
10296                 board[fromY][fromX+1] = EmptySquare;
10297         }
10298     } else {
10299         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10300         board[fromY][fromX] = EmptySquare;
10301         board[toY][toX] = piece;
10302     }
10303   }
10304
10305     if (gameInfo.holdingsWidth != 0) {
10306
10307       /* !!A lot more code needs to be written to support holdings  */
10308       /* [HGM] OK, so I have written it. Holdings are stored in the */
10309       /* penultimate board files, so they are automaticlly stored   */
10310       /* in the game history.                                       */
10311       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10312                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10313         /* Delete from holdings, by decreasing count */
10314         /* and erasing image if necessary            */
10315         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10316         if(p < (int) BlackPawn) { /* white drop */
10317              p -= (int)WhitePawn;
10318                  p = PieceToNumber((ChessSquare)p);
10319              if(p >= gameInfo.holdingsSize) p = 0;
10320              if(--board[p][BOARD_WIDTH-2] <= 0)
10321                   board[p][BOARD_WIDTH-1] = EmptySquare;
10322              if((int)board[p][BOARD_WIDTH-2] < 0)
10323                         board[p][BOARD_WIDTH-2] = 0;
10324         } else {                  /* black drop */
10325              p -= (int)BlackPawn;
10326                  p = PieceToNumber((ChessSquare)p);
10327              if(p >= gameInfo.holdingsSize) p = 0;
10328              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10329                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10330              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10331                         board[BOARD_HEIGHT-1-p][1] = 0;
10332         }
10333       }
10334       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10335           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10336         /* [HGM] holdings: Add to holdings, if holdings exist */
10337         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10338                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10339                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10340         }
10341         p = (int) captured;
10342         if (p >= (int) BlackPawn) {
10343           p -= (int)BlackPawn;
10344           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10345                   /* Restore shogi-promoted piece to its original  first */
10346                   captured = (ChessSquare) (DEMOTED(captured));
10347                   p = DEMOTED(p);
10348           }
10349           p = PieceToNumber((ChessSquare)p);
10350           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10351           board[p][BOARD_WIDTH-2]++;
10352           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10353         } else {
10354           p -= (int)WhitePawn;
10355           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10356                   captured = (ChessSquare) (DEMOTED(captured));
10357                   p = DEMOTED(p);
10358           }
10359           p = PieceToNumber((ChessSquare)p);
10360           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10361           board[BOARD_HEIGHT-1-p][1]++;
10362           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10363         }
10364       }
10365     } else if (gameInfo.variant == VariantAtomic) {
10366       if (captured != EmptySquare) {
10367         int y, x;
10368         for (y = toY-1; y <= toY+1; y++) {
10369           for (x = toX-1; x <= toX+1; x++) {
10370             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10371                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10372               board[y][x] = EmptySquare;
10373             }
10374           }
10375         }
10376         board[toY][toX] = EmptySquare;
10377       }
10378     }
10379
10380     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10381         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10382     } else
10383     if(promoChar == '+') {
10384         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10385         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10386         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10387           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10388     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10389         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10390         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10391            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10392         board[toY][toX] = newPiece;
10393     }
10394     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10395                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10396         // [HGM] superchess: take promotion piece out of holdings
10397         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10398         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10399             if(!--board[k][BOARD_WIDTH-2])
10400                 board[k][BOARD_WIDTH-1] = EmptySquare;
10401         } else {
10402             if(!--board[BOARD_HEIGHT-1-k][1])
10403                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10404         }
10405     }
10406 }
10407
10408 /* Updates forwardMostMove */
10409 void
10410 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10411 {
10412     int x = toX, y = toY;
10413     char *s = parseList[forwardMostMove];
10414     ChessSquare p = boards[forwardMostMove][toY][toX];
10415 //    forwardMostMove++; // [HGM] bare: moved downstream
10416
10417     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10418     (void) CoordsToAlgebraic(boards[forwardMostMove],
10419                              PosFlags(forwardMostMove),
10420                              fromY, fromX, y, x, promoChar,
10421                              s);
10422     if(killX >= 0 && killY >= 0)
10423         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10424
10425     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10426         int timeLeft; static int lastLoadFlag=0; int king, piece;
10427         piece = boards[forwardMostMove][fromY][fromX];
10428         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10429         if(gameInfo.variant == VariantKnightmate)
10430             king += (int) WhiteUnicorn - (int) WhiteKing;
10431         if(forwardMostMove == 0) {
10432             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10433                 fprintf(serverMoves, "%s;", UserName());
10434             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10435                 fprintf(serverMoves, "%s;", second.tidy);
10436             fprintf(serverMoves, "%s;", first.tidy);
10437             if(gameMode == MachinePlaysWhite)
10438                 fprintf(serverMoves, "%s;", UserName());
10439             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10440                 fprintf(serverMoves, "%s;", second.tidy);
10441         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10442         lastLoadFlag = loadFlag;
10443         // print base move
10444         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10445         // print castling suffix
10446         if( toY == fromY && piece == king ) {
10447             if(toX-fromX > 1)
10448                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10449             if(fromX-toX >1)
10450                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10451         }
10452         // e.p. suffix
10453         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10454              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10455              boards[forwardMostMove][toY][toX] == EmptySquare
10456              && fromX != toX && fromY != toY)
10457                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10458         // promotion suffix
10459         if(promoChar != NULLCHAR) {
10460             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10461                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10462                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10463             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10464         }
10465         if(!loadFlag) {
10466                 char buf[MOVE_LEN*2], *p; int len;
10467             fprintf(serverMoves, "/%d/%d",
10468                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10469             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10470             else                      timeLeft = blackTimeRemaining/1000;
10471             fprintf(serverMoves, "/%d", timeLeft);
10472                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10473                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10474                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10475                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10476             fprintf(serverMoves, "/%s", buf);
10477         }
10478         fflush(serverMoves);
10479     }
10480
10481     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10482         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10483       return;
10484     }
10485     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10486     if (commentList[forwardMostMove+1] != NULL) {
10487         free(commentList[forwardMostMove+1]);
10488         commentList[forwardMostMove+1] = NULL;
10489     }
10490     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10491     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10492     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10493     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10494     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10495     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10496     adjustedClock = FALSE;
10497     gameInfo.result = GameUnfinished;
10498     if (gameInfo.resultDetails != NULL) {
10499         free(gameInfo.resultDetails);
10500         gameInfo.resultDetails = NULL;
10501     }
10502     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10503                               moveList[forwardMostMove - 1]);
10504     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10505       case MT_NONE:
10506       case MT_STALEMATE:
10507       default:
10508         break;
10509       case MT_CHECK:
10510         if(!IS_SHOGI(gameInfo.variant))
10511             strcat(parseList[forwardMostMove - 1], "+");
10512         break;
10513       case MT_CHECKMATE:
10514       case MT_STAINMATE:
10515         strcat(parseList[forwardMostMove - 1], "#");
10516         break;
10517     }
10518 }
10519
10520 /* Updates currentMove if not pausing */
10521 void
10522 ShowMove (int fromX, int fromY, int toX, int toY)
10523 {
10524     int instant = (gameMode == PlayFromGameFile) ?
10525         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10526     if(appData.noGUI) return;
10527     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10528         if (!instant) {
10529             if (forwardMostMove == currentMove + 1) {
10530                 AnimateMove(boards[forwardMostMove - 1],
10531                             fromX, fromY, toX, toY);
10532             }
10533         }
10534         currentMove = forwardMostMove;
10535     }
10536
10537     killX = killY = -1; // [HGM] lion: used up
10538
10539     if (instant) return;
10540
10541     DisplayMove(currentMove - 1);
10542     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10543             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10544                 SetHighlights(fromX, fromY, toX, toY);
10545             }
10546     }
10547     DrawPosition(FALSE, boards[currentMove]);
10548     DisplayBothClocks();
10549     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10550 }
10551
10552 void
10553 SendEgtPath (ChessProgramState *cps)
10554 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10555         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10556
10557         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10558
10559         while(*p) {
10560             char c, *q = name+1, *r, *s;
10561
10562             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10563             while(*p && *p != ',') *q++ = *p++;
10564             *q++ = ':'; *q = 0;
10565             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10566                 strcmp(name, ",nalimov:") == 0 ) {
10567                 // take nalimov path from the menu-changeable option first, if it is defined
10568               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10569                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10570             } else
10571             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10572                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10573                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10574                 s = r = StrStr(s, ":") + 1; // beginning of path info
10575                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10576                 c = *r; *r = 0;             // temporarily null-terminate path info
10577                     *--q = 0;               // strip of trailig ':' from name
10578                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10579                 *r = c;
10580                 SendToProgram(buf,cps);     // send egtbpath command for this format
10581             }
10582             if(*p == ',') p++; // read away comma to position for next format name
10583         }
10584 }
10585
10586 static int
10587 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10588 {
10589       int width = 8, height = 8, holdings = 0;             // most common sizes
10590       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10591       // correct the deviations default for each variant
10592       if( v == VariantXiangqi ) width = 9,  height = 10;
10593       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10594       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10595       if( v == VariantCapablanca || v == VariantCapaRandom ||
10596           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10597                                 width = 10;
10598       if( v == VariantCourier ) width = 12;
10599       if( v == VariantSuper )                            holdings = 8;
10600       if( v == VariantGreat )   width = 10,              holdings = 8;
10601       if( v == VariantSChess )                           holdings = 7;
10602       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10603       if( v == VariantChuChess) width = 10, height = 10;
10604       if( v == VariantChu )     width = 12, height = 12;
10605       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10606              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10607              holdingsSize >= 0 && holdingsSize != holdings;
10608 }
10609
10610 char variantError[MSG_SIZ];
10611
10612 char *
10613 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10614 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10615       char *p, *variant = VariantName(v);
10616       static char b[MSG_SIZ];
10617       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10618            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10619                                                holdingsSize, variant); // cook up sized variant name
10620            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10621            if(StrStr(list, b) == NULL) {
10622                // specific sized variant not known, check if general sizing allowed
10623                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10624                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10625                             boardWidth, boardHeight, holdingsSize, engine);
10626                    return NULL;
10627                }
10628                /* [HGM] here we really should compare with the maximum supported board size */
10629            }
10630       } else snprintf(b, MSG_SIZ,"%s", variant);
10631       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10632       p = StrStr(list, b);
10633       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10634       if(p == NULL) {
10635           // occurs not at all in list, or only as sub-string
10636           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10637           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10638               int l = strlen(variantError);
10639               char *q;
10640               while(p != list && p[-1] != ',') p--;
10641               q = strchr(p, ',');
10642               if(q) *q = NULLCHAR;
10643               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10644               if(q) *q= ',';
10645           }
10646           return NULL;
10647       }
10648       return b;
10649 }
10650
10651 void
10652 InitChessProgram (ChessProgramState *cps, int setup)
10653 /* setup needed to setup FRC opening position */
10654 {
10655     char buf[MSG_SIZ], *b;
10656     if (appData.noChessProgram) return;
10657     hintRequested = FALSE;
10658     bookRequested = FALSE;
10659
10660     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10661     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10662     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10663     if(cps->memSize) { /* [HGM] memory */
10664       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10665         SendToProgram(buf, cps);
10666     }
10667     SendEgtPath(cps); /* [HGM] EGT */
10668     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10669       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10670         SendToProgram(buf, cps);
10671     }
10672
10673     setboardSpoiledMachineBlack = FALSE;
10674     SendToProgram(cps->initString, cps);
10675     if (gameInfo.variant != VariantNormal &&
10676         gameInfo.variant != VariantLoadable
10677         /* [HGM] also send variant if board size non-standard */
10678         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10679
10680       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10681                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10682       if (b == NULL) {
10683         VariantClass v;
10684         char c, *q = cps->variants, *p = strchr(q, ',');
10685         if(p) *p = NULLCHAR;
10686         v = StringToVariant(q);
10687         DisplayError(variantError, 0);
10688         if(v != VariantUnknown && cps == &first) {
10689             int w, h, s;
10690             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10691                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10692             ASSIGN(appData.variant, q);
10693             Reset(TRUE, FALSE);
10694         }
10695         if(p) *p = ',';
10696         return;
10697       }
10698
10699       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10700       SendToProgram(buf, cps);
10701     }
10702     currentlyInitializedVariant = gameInfo.variant;
10703
10704     /* [HGM] send opening position in FRC to first engine */
10705     if(setup) {
10706           SendToProgram("force\n", cps);
10707           SendBoard(cps, 0);
10708           /* engine is now in force mode! Set flag to wake it up after first move. */
10709           setboardSpoiledMachineBlack = 1;
10710     }
10711
10712     if (cps->sendICS) {
10713       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10714       SendToProgram(buf, cps);
10715     }
10716     cps->maybeThinking = FALSE;
10717     cps->offeredDraw = 0;
10718     if (!appData.icsActive) {
10719         SendTimeControl(cps, movesPerSession, timeControl,
10720                         timeIncrement, appData.searchDepth,
10721                         searchTime);
10722     }
10723     if (appData.showThinking
10724         // [HGM] thinking: four options require thinking output to be sent
10725         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10726                                 ) {
10727         SendToProgram("post\n", cps);
10728     }
10729     SendToProgram("hard\n", cps);
10730     if (!appData.ponderNextMove) {
10731         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10732            it without being sure what state we are in first.  "hard"
10733            is not a toggle, so that one is OK.
10734          */
10735         SendToProgram("easy\n", cps);
10736     }
10737     if (cps->usePing) {
10738       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10739       SendToProgram(buf, cps);
10740     }
10741     cps->initDone = TRUE;
10742     ClearEngineOutputPane(cps == &second);
10743 }
10744
10745
10746 void
10747 ResendOptions (ChessProgramState *cps)
10748 { // send the stored value of the options
10749   int i;
10750   char buf[MSG_SIZ];
10751   Option *opt = cps->option;
10752   for(i=0; i<cps->nrOptions; i++, opt++) {
10753       switch(opt->type) {
10754         case Spin:
10755         case Slider:
10756         case CheckBox:
10757             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10758           break;
10759         case ComboBox:
10760           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10761           break;
10762         default:
10763             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10764           break;
10765         case Button:
10766         case SaveButton:
10767           continue;
10768       }
10769       SendToProgram(buf, cps);
10770   }
10771 }
10772
10773 void
10774 StartChessProgram (ChessProgramState *cps)
10775 {
10776     char buf[MSG_SIZ];
10777     int err;
10778
10779     if (appData.noChessProgram) return;
10780     cps->initDone = FALSE;
10781
10782     if (strcmp(cps->host, "localhost") == 0) {
10783         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10784     } else if (*appData.remoteShell == NULLCHAR) {
10785         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10786     } else {
10787         if (*appData.remoteUser == NULLCHAR) {
10788           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10789                     cps->program);
10790         } else {
10791           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10792                     cps->host, appData.remoteUser, cps->program);
10793         }
10794         err = StartChildProcess(buf, "", &cps->pr);
10795     }
10796
10797     if (err != 0) {
10798       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10799         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10800         if(cps != &first) return;
10801         appData.noChessProgram = TRUE;
10802         ThawUI();
10803         SetNCPMode();
10804 //      DisplayFatalError(buf, err, 1);
10805 //      cps->pr = NoProc;
10806 //      cps->isr = NULL;
10807         return;
10808     }
10809
10810     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10811     if (cps->protocolVersion > 1) {
10812       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10813       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10814         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10815         cps->comboCnt = 0;  //                and values of combo boxes
10816       }
10817       SendToProgram(buf, cps);
10818       if(cps->reload) ResendOptions(cps);
10819     } else {
10820       SendToProgram("xboard\n", cps);
10821     }
10822 }
10823
10824 void
10825 TwoMachinesEventIfReady P((void))
10826 {
10827   static int curMess = 0;
10828   if (first.lastPing != first.lastPong) {
10829     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10830     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10831     return;
10832   }
10833   if (second.lastPing != second.lastPong) {
10834     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10835     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10836     return;
10837   }
10838   DisplayMessage("", ""); curMess = 0;
10839   TwoMachinesEvent();
10840 }
10841
10842 char *
10843 MakeName (char *template)
10844 {
10845     time_t clock;
10846     struct tm *tm;
10847     static char buf[MSG_SIZ];
10848     char *p = buf;
10849     int i;
10850
10851     clock = time((time_t *)NULL);
10852     tm = localtime(&clock);
10853
10854     while(*p++ = *template++) if(p[-1] == '%') {
10855         switch(*template++) {
10856           case 0:   *p = 0; return buf;
10857           case 'Y': i = tm->tm_year+1900; break;
10858           case 'y': i = tm->tm_year-100; break;
10859           case 'M': i = tm->tm_mon+1; break;
10860           case 'd': i = tm->tm_mday; break;
10861           case 'h': i = tm->tm_hour; break;
10862           case 'm': i = tm->tm_min; break;
10863           case 's': i = tm->tm_sec; break;
10864           default:  i = 0;
10865         }
10866         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10867     }
10868     return buf;
10869 }
10870
10871 int
10872 CountPlayers (char *p)
10873 {
10874     int n = 0;
10875     while(p = strchr(p, '\n')) p++, n++; // count participants
10876     return n;
10877 }
10878
10879 FILE *
10880 WriteTourneyFile (char *results, FILE *f)
10881 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10882     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10883     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10884         // create a file with tournament description
10885         fprintf(f, "-participants {%s}\n", appData.participants);
10886         fprintf(f, "-seedBase %d\n", appData.seedBase);
10887         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10888         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10889         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10890         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10891         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10892         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10893         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10894         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10895         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10896         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10897         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10898         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10899         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10900         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10901         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10902         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10903         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10904         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10905         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10906         fprintf(f, "-smpCores %d\n", appData.smpCores);
10907         if(searchTime > 0)
10908                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10909         else {
10910                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10911                 fprintf(f, "-tc %s\n", appData.timeControl);
10912                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10913         }
10914         fprintf(f, "-results \"%s\"\n", results);
10915     }
10916     return f;
10917 }
10918
10919 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10920
10921 void
10922 Substitute (char *participants, int expunge)
10923 {
10924     int i, changed, changes=0, nPlayers=0;
10925     char *p, *q, *r, buf[MSG_SIZ];
10926     if(participants == NULL) return;
10927     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10928     r = p = participants; q = appData.participants;
10929     while(*p && *p == *q) {
10930         if(*p == '\n') r = p+1, nPlayers++;
10931         p++; q++;
10932     }
10933     if(*p) { // difference
10934         while(*p && *p++ != '\n');
10935         while(*q && *q++ != '\n');
10936       changed = nPlayers;
10937         changes = 1 + (strcmp(p, q) != 0);
10938     }
10939     if(changes == 1) { // a single engine mnemonic was changed
10940         q = r; while(*q) nPlayers += (*q++ == '\n');
10941         p = buf; while(*r && (*p = *r++) != '\n') p++;
10942         *p = NULLCHAR;
10943         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10944         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10945         if(mnemonic[i]) { // The substitute is valid
10946             FILE *f;
10947             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10948                 flock(fileno(f), LOCK_EX);
10949                 ParseArgsFromFile(f);
10950                 fseek(f, 0, SEEK_SET);
10951                 FREE(appData.participants); appData.participants = participants;
10952                 if(expunge) { // erase results of replaced engine
10953                     int len = strlen(appData.results), w, b, dummy;
10954                     for(i=0; i<len; i++) {
10955                         Pairing(i, nPlayers, &w, &b, &dummy);
10956                         if((w == changed || b == changed) && appData.results[i] == '*') {
10957                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10958                             fclose(f);
10959                             return;
10960                         }
10961                     }
10962                     for(i=0; i<len; i++) {
10963                         Pairing(i, nPlayers, &w, &b, &dummy);
10964                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10965                     }
10966                 }
10967                 WriteTourneyFile(appData.results, f);
10968                 fclose(f); // release lock
10969                 return;
10970             }
10971         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10972     }
10973     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10974     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10975     free(participants);
10976     return;
10977 }
10978
10979 int
10980 CheckPlayers (char *participants)
10981 {
10982         int i;
10983         char buf[MSG_SIZ], *p;
10984         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10985         while(p = strchr(participants, '\n')) {
10986             *p = NULLCHAR;
10987             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10988             if(!mnemonic[i]) {
10989                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10990                 *p = '\n';
10991                 DisplayError(buf, 0);
10992                 return 1;
10993             }
10994             *p = '\n';
10995             participants = p + 1;
10996         }
10997         return 0;
10998 }
10999
11000 int
11001 CreateTourney (char *name)
11002 {
11003         FILE *f;
11004         if(matchMode && strcmp(name, appData.tourneyFile)) {
11005              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11006         }
11007         if(name[0] == NULLCHAR) {
11008             if(appData.participants[0])
11009                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11010             return 0;
11011         }
11012         f = fopen(name, "r");
11013         if(f) { // file exists
11014             ASSIGN(appData.tourneyFile, name);
11015             ParseArgsFromFile(f); // parse it
11016         } else {
11017             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11018             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11019                 DisplayError(_("Not enough participants"), 0);
11020                 return 0;
11021             }
11022             if(CheckPlayers(appData.participants)) return 0;
11023             ASSIGN(appData.tourneyFile, name);
11024             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11025             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11026         }
11027         fclose(f);
11028         appData.noChessProgram = FALSE;
11029         appData.clockMode = TRUE;
11030         SetGNUMode();
11031         return 1;
11032 }
11033
11034 int
11035 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11036 {
11037     char buf[MSG_SIZ], *p, *q;
11038     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11039     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11040     skip = !all && group[0]; // if group requested, we start in skip mode
11041     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11042         p = names; q = buf; header = 0;
11043         while(*p && *p != '\n') *q++ = *p++;
11044         *q = 0;
11045         if(*p == '\n') p++;
11046         if(buf[0] == '#') {
11047             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11048             depth++; // we must be entering a new group
11049             if(all) continue; // suppress printing group headers when complete list requested
11050             header = 1;
11051             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11052         }
11053         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11054         if(engineList[i]) free(engineList[i]);
11055         engineList[i] = strdup(buf);
11056         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11057         if(engineMnemonic[i]) free(engineMnemonic[i]);
11058         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11059             strcat(buf, " (");
11060             sscanf(q + 8, "%s", buf + strlen(buf));
11061             strcat(buf, ")");
11062         }
11063         engineMnemonic[i] = strdup(buf);
11064         i++;
11065     }
11066     engineList[i] = engineMnemonic[i] = NULL;
11067     return i;
11068 }
11069
11070 // following implemented as macro to avoid type limitations
11071 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11072
11073 void
11074 SwapEngines (int n)
11075 {   // swap settings for first engine and other engine (so far only some selected options)
11076     int h;
11077     char *p;
11078     if(n == 0) return;
11079     SWAP(directory, p)
11080     SWAP(chessProgram, p)
11081     SWAP(isUCI, h)
11082     SWAP(hasOwnBookUCI, h)
11083     SWAP(protocolVersion, h)
11084     SWAP(reuse, h)
11085     SWAP(scoreIsAbsolute, h)
11086     SWAP(timeOdds, h)
11087     SWAP(logo, p)
11088     SWAP(pgnName, p)
11089     SWAP(pvSAN, h)
11090     SWAP(engOptions, p)
11091     SWAP(engInitString, p)
11092     SWAP(computerString, p)
11093     SWAP(features, p)
11094     SWAP(fenOverride, p)
11095     SWAP(NPS, h)
11096     SWAP(accumulateTC, h)
11097     SWAP(drawDepth, h)
11098     SWAP(host, p)
11099     SWAP(pseudo, h)
11100 }
11101
11102 int
11103 GetEngineLine (char *s, int n)
11104 {
11105     int i;
11106     char buf[MSG_SIZ];
11107     extern char *icsNames;
11108     if(!s || !*s) return 0;
11109     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11110     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11111     if(!mnemonic[i]) return 0;
11112     if(n == 11) return 1; // just testing if there was a match
11113     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11114     if(n == 1) SwapEngines(n);
11115     ParseArgsFromString(buf);
11116     if(n == 1) SwapEngines(n);
11117     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11118         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11119         ParseArgsFromString(buf);
11120     }
11121     return 1;
11122 }
11123
11124 int
11125 SetPlayer (int player, char *p)
11126 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11127     int i;
11128     char buf[MSG_SIZ], *engineName;
11129     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11130     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11131     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11132     if(mnemonic[i]) {
11133         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11134         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11135         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11136         ParseArgsFromString(buf);
11137     } else { // no engine with this nickname is installed!
11138         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11139         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11140         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11141         ModeHighlight();
11142         DisplayError(buf, 0);
11143         return 0;
11144     }
11145     free(engineName);
11146     return i;
11147 }
11148
11149 char *recentEngines;
11150
11151 void
11152 RecentEngineEvent (int nr)
11153 {
11154     int n;
11155 //    SwapEngines(1); // bump first to second
11156 //    ReplaceEngine(&second, 1); // and load it there
11157     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11158     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11159     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11160         ReplaceEngine(&first, 0);
11161         FloatToFront(&appData.recentEngineList, command[n]);
11162     }
11163 }
11164
11165 int
11166 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11167 {   // determine players from game number
11168     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11169
11170     if(appData.tourneyType == 0) {
11171         roundsPerCycle = (nPlayers - 1) | 1;
11172         pairingsPerRound = nPlayers / 2;
11173     } else if(appData.tourneyType > 0) {
11174         roundsPerCycle = nPlayers - appData.tourneyType;
11175         pairingsPerRound = appData.tourneyType;
11176     }
11177     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11178     gamesPerCycle = gamesPerRound * roundsPerCycle;
11179     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11180     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11181     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11182     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11183     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11184     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11185
11186     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11187     if(appData.roundSync) *syncInterval = gamesPerRound;
11188
11189     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11190
11191     if(appData.tourneyType == 0) {
11192         if(curPairing == (nPlayers-1)/2 ) {
11193             *whitePlayer = curRound;
11194             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11195         } else {
11196             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11197             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11198             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11199             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11200         }
11201     } else if(appData.tourneyType > 1) {
11202         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11203         *whitePlayer = curRound + appData.tourneyType;
11204     } else if(appData.tourneyType > 0) {
11205         *whitePlayer = curPairing;
11206         *blackPlayer = curRound + appData.tourneyType;
11207     }
11208
11209     // take care of white/black alternation per round.
11210     // For cycles and games this is already taken care of by default, derived from matchGame!
11211     return curRound & 1;
11212 }
11213
11214 int
11215 NextTourneyGame (int nr, int *swapColors)
11216 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11217     char *p, *q;
11218     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11219     FILE *tf;
11220     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11221     tf = fopen(appData.tourneyFile, "r");
11222     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11223     ParseArgsFromFile(tf); fclose(tf);
11224     InitTimeControls(); // TC might be altered from tourney file
11225
11226     nPlayers = CountPlayers(appData.participants); // count participants
11227     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11228     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11229
11230     if(syncInterval) {
11231         p = q = appData.results;
11232         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11233         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11234             DisplayMessage(_("Waiting for other game(s)"),"");
11235             waitingForGame = TRUE;
11236             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11237             return 0;
11238         }
11239         waitingForGame = FALSE;
11240     }
11241
11242     if(appData.tourneyType < 0) {
11243         if(nr>=0 && !pairingReceived) {
11244             char buf[1<<16];
11245             if(pairing.pr == NoProc) {
11246                 if(!appData.pairingEngine[0]) {
11247                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11248                     return 0;
11249                 }
11250                 StartChessProgram(&pairing); // starts the pairing engine
11251             }
11252             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11253             SendToProgram(buf, &pairing);
11254             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11255             SendToProgram(buf, &pairing);
11256             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11257         }
11258         pairingReceived = 0;                              // ... so we continue here
11259         *swapColors = 0;
11260         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11261         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11262         matchGame = 1; roundNr = nr / syncInterval + 1;
11263     }
11264
11265     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11266
11267     // redefine engines, engine dir, etc.
11268     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11269     if(first.pr == NoProc) {
11270       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11271       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11272     }
11273     if(second.pr == NoProc) {
11274       SwapEngines(1);
11275       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11276       SwapEngines(1);         // and make that valid for second engine by swapping
11277       InitEngine(&second, 1);
11278     }
11279     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11280     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11281     return OK;
11282 }
11283
11284 void
11285 NextMatchGame ()
11286 {   // performs game initialization that does not invoke engines, and then tries to start the game
11287     int res, firstWhite, swapColors = 0;
11288     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11289     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
11290         char buf[MSG_SIZ];
11291         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11292         if(strcmp(buf, currentDebugFile)) { // name has changed
11293             FILE *f = fopen(buf, "w");
11294             if(f) { // if opening the new file failed, just keep using the old one
11295                 ASSIGN(currentDebugFile, buf);
11296                 fclose(debugFP);
11297                 debugFP = f;
11298             }
11299             if(appData.serverFileName) {
11300                 if(serverFP) fclose(serverFP);
11301                 serverFP = fopen(appData.serverFileName, "w");
11302                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11303                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11304             }
11305         }
11306     }
11307     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11308     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11309     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11310     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11311     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11312     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11313     Reset(FALSE, first.pr != NoProc);
11314     res = LoadGameOrPosition(matchGame); // setup game
11315     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11316     if(!res) return; // abort when bad game/pos file
11317     TwoMachinesEvent();
11318 }
11319
11320 void
11321 UserAdjudicationEvent (int result)
11322 {
11323     ChessMove gameResult = GameIsDrawn;
11324
11325     if( result > 0 ) {
11326         gameResult = WhiteWins;
11327     }
11328     else if( result < 0 ) {
11329         gameResult = BlackWins;
11330     }
11331
11332     if( gameMode == TwoMachinesPlay ) {
11333         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11334     }
11335 }
11336
11337
11338 // [HGM] save: calculate checksum of game to make games easily identifiable
11339 int
11340 StringCheckSum (char *s)
11341 {
11342         int i = 0;
11343         if(s==NULL) return 0;
11344         while(*s) i = i*259 + *s++;
11345         return i;
11346 }
11347
11348 int
11349 GameCheckSum ()
11350 {
11351         int i, sum=0;
11352         for(i=backwardMostMove; i<forwardMostMove; i++) {
11353                 sum += pvInfoList[i].depth;
11354                 sum += StringCheckSum(parseList[i]);
11355                 sum += StringCheckSum(commentList[i]);
11356                 sum *= 261;
11357         }
11358         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11359         return sum + StringCheckSum(commentList[i]);
11360 } // end of save patch
11361
11362 void
11363 GameEnds (ChessMove result, char *resultDetails, int whosays)
11364 {
11365     GameMode nextGameMode;
11366     int isIcsGame;
11367     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11368
11369     if(endingGame) return; /* [HGM] crash: forbid recursion */
11370     endingGame = 1;
11371     if(twoBoards) { // [HGM] dual: switch back to one board
11372         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11373         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11374     }
11375     if (appData.debugMode) {
11376       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11377               result, resultDetails ? resultDetails : "(null)", whosays);
11378     }
11379
11380     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11381
11382     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11383
11384     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11385         /* If we are playing on ICS, the server decides when the
11386            game is over, but the engine can offer to draw, claim
11387            a draw, or resign.
11388          */
11389 #if ZIPPY
11390         if (appData.zippyPlay && first.initDone) {
11391             if (result == GameIsDrawn) {
11392                 /* In case draw still needs to be claimed */
11393                 SendToICS(ics_prefix);
11394                 SendToICS("draw\n");
11395             } else if (StrCaseStr(resultDetails, "resign")) {
11396                 SendToICS(ics_prefix);
11397                 SendToICS("resign\n");
11398             }
11399         }
11400 #endif
11401         endingGame = 0; /* [HGM] crash */
11402         return;
11403     }
11404
11405     /* If we're loading the game from a file, stop */
11406     if (whosays == GE_FILE) {
11407       (void) StopLoadGameTimer();
11408       gameFileFP = NULL;
11409     }
11410
11411     /* Cancel draw offers */
11412     first.offeredDraw = second.offeredDraw = 0;
11413
11414     /* If this is an ICS game, only ICS can really say it's done;
11415        if not, anyone can. */
11416     isIcsGame = (gameMode == IcsPlayingWhite ||
11417                  gameMode == IcsPlayingBlack ||
11418                  gameMode == IcsObserving    ||
11419                  gameMode == IcsExamining);
11420
11421     if (!isIcsGame || whosays == GE_ICS) {
11422         /* OK -- not an ICS game, or ICS said it was done */
11423         StopClocks();
11424         if (!isIcsGame && !appData.noChessProgram)
11425           SetUserThinkingEnables();
11426
11427         /* [HGM] if a machine claims the game end we verify this claim */
11428         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11429             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11430                 char claimer;
11431                 ChessMove trueResult = (ChessMove) -1;
11432
11433                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11434                                             first.twoMachinesColor[0] :
11435                                             second.twoMachinesColor[0] ;
11436
11437                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11438                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11439                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11440                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11441                 } else
11442                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11443                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11444                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11445                 } else
11446                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11447                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11448                 }
11449
11450                 // now verify win claims, but not in drop games, as we don't understand those yet
11451                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11452                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11453                     (result == WhiteWins && claimer == 'w' ||
11454                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11455                       if (appData.debugMode) {
11456                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11457                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11458                       }
11459                       if(result != trueResult) {
11460                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11461                               result = claimer == 'w' ? BlackWins : WhiteWins;
11462                               resultDetails = buf;
11463                       }
11464                 } else
11465                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11466                     && (forwardMostMove <= backwardMostMove ||
11467                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11468                         (claimer=='b')==(forwardMostMove&1))
11469                                                                                   ) {
11470                       /* [HGM] verify: draws that were not flagged are false claims */
11471                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11472                       result = claimer == 'w' ? BlackWins : WhiteWins;
11473                       resultDetails = buf;
11474                 }
11475                 /* (Claiming a loss is accepted no questions asked!) */
11476             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11477                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11478                 result = GameUnfinished;
11479                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11480             }
11481             /* [HGM] bare: don't allow bare King to win */
11482             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11483                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11484                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11485                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11486                && result != GameIsDrawn)
11487             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11488                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11489                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11490                         if(p >= 0 && p <= (int)WhiteKing) k++;
11491                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11492                 }
11493                 if (appData.debugMode) {
11494                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11495                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11496                 }
11497                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11498                         result = GameIsDrawn;
11499                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11500                         resultDetails = buf;
11501                 }
11502             }
11503         }
11504
11505
11506         if(serverMoves != NULL && !loadFlag) { char c = '=';
11507             if(result==WhiteWins) c = '+';
11508             if(result==BlackWins) c = '-';
11509             if(resultDetails != NULL)
11510                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11511         }
11512         if (resultDetails != NULL) {
11513             gameInfo.result = result;
11514             gameInfo.resultDetails = StrSave(resultDetails);
11515
11516             /* display last move only if game was not loaded from file */
11517             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11518                 DisplayMove(currentMove - 1);
11519
11520             if (forwardMostMove != 0) {
11521                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11522                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11523                                                                 ) {
11524                     if (*appData.saveGameFile != NULLCHAR) {
11525                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11526                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11527                         else
11528                         SaveGameToFile(appData.saveGameFile, TRUE);
11529                     } else if (appData.autoSaveGames) {
11530                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11531                     }
11532                     if (*appData.savePositionFile != NULLCHAR) {
11533                         SavePositionToFile(appData.savePositionFile);
11534                     }
11535                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11536                 }
11537             }
11538
11539             /* Tell program how game ended in case it is learning */
11540             /* [HGM] Moved this to after saving the PGN, just in case */
11541             /* engine died and we got here through time loss. In that */
11542             /* case we will get a fatal error writing the pipe, which */
11543             /* would otherwise lose us the PGN.                       */
11544             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11545             /* output during GameEnds should never be fatal anymore   */
11546             if (gameMode == MachinePlaysWhite ||
11547                 gameMode == MachinePlaysBlack ||
11548                 gameMode == TwoMachinesPlay ||
11549                 gameMode == IcsPlayingWhite ||
11550                 gameMode == IcsPlayingBlack ||
11551                 gameMode == BeginningOfGame) {
11552                 char buf[MSG_SIZ];
11553                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11554                         resultDetails);
11555                 if (first.pr != NoProc) {
11556                     SendToProgram(buf, &first);
11557                 }
11558                 if (second.pr != NoProc &&
11559                     gameMode == TwoMachinesPlay) {
11560                     SendToProgram(buf, &second);
11561                 }
11562             }
11563         }
11564
11565         if (appData.icsActive) {
11566             if (appData.quietPlay &&
11567                 (gameMode == IcsPlayingWhite ||
11568                  gameMode == IcsPlayingBlack)) {
11569                 SendToICS(ics_prefix);
11570                 SendToICS("set shout 1\n");
11571             }
11572             nextGameMode = IcsIdle;
11573             ics_user_moved = FALSE;
11574             /* clean up premove.  It's ugly when the game has ended and the
11575              * premove highlights are still on the board.
11576              */
11577             if (gotPremove) {
11578               gotPremove = FALSE;
11579               ClearPremoveHighlights();
11580               DrawPosition(FALSE, boards[currentMove]);
11581             }
11582             if (whosays == GE_ICS) {
11583                 switch (result) {
11584                 case WhiteWins:
11585                     if (gameMode == IcsPlayingWhite)
11586                         PlayIcsWinSound();
11587                     else if(gameMode == IcsPlayingBlack)
11588                         PlayIcsLossSound();
11589                     break;
11590                 case BlackWins:
11591                     if (gameMode == IcsPlayingBlack)
11592                         PlayIcsWinSound();
11593                     else if(gameMode == IcsPlayingWhite)
11594                         PlayIcsLossSound();
11595                     break;
11596                 case GameIsDrawn:
11597                     PlayIcsDrawSound();
11598                     break;
11599                 default:
11600                     PlayIcsUnfinishedSound();
11601                 }
11602             }
11603             if(appData.quitNext) { ExitEvent(0); return; }
11604         } else if (gameMode == EditGame ||
11605                    gameMode == PlayFromGameFile ||
11606                    gameMode == AnalyzeMode ||
11607                    gameMode == AnalyzeFile) {
11608             nextGameMode = gameMode;
11609         } else {
11610             nextGameMode = EndOfGame;
11611         }
11612         pausing = FALSE;
11613         ModeHighlight();
11614     } else {
11615         nextGameMode = gameMode;
11616     }
11617
11618     if (appData.noChessProgram) {
11619         gameMode = nextGameMode;
11620         ModeHighlight();
11621         endingGame = 0; /* [HGM] crash */
11622         return;
11623     }
11624
11625     if (first.reuse) {
11626         /* Put first chess program into idle state */
11627         if (first.pr != NoProc &&
11628             (gameMode == MachinePlaysWhite ||
11629              gameMode == MachinePlaysBlack ||
11630              gameMode == TwoMachinesPlay ||
11631              gameMode == IcsPlayingWhite ||
11632              gameMode == IcsPlayingBlack ||
11633              gameMode == BeginningOfGame)) {
11634             SendToProgram("force\n", &first);
11635             if (first.usePing) {
11636               char buf[MSG_SIZ];
11637               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11638               SendToProgram(buf, &first);
11639             }
11640         }
11641     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11642         /* Kill off first chess program */
11643         if (first.isr != NULL)
11644           RemoveInputSource(first.isr);
11645         first.isr = NULL;
11646
11647         if (first.pr != NoProc) {
11648             ExitAnalyzeMode();
11649             DoSleep( appData.delayBeforeQuit );
11650             SendToProgram("quit\n", &first);
11651             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11652             first.reload = TRUE;
11653         }
11654         first.pr = NoProc;
11655     }
11656     if (second.reuse) {
11657         /* Put second chess program into idle state */
11658         if (second.pr != NoProc &&
11659             gameMode == TwoMachinesPlay) {
11660             SendToProgram("force\n", &second);
11661             if (second.usePing) {
11662               char buf[MSG_SIZ];
11663               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11664               SendToProgram(buf, &second);
11665             }
11666         }
11667     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11668         /* Kill off second chess program */
11669         if (second.isr != NULL)
11670           RemoveInputSource(second.isr);
11671         second.isr = NULL;
11672
11673         if (second.pr != NoProc) {
11674             DoSleep( appData.delayBeforeQuit );
11675             SendToProgram("quit\n", &second);
11676             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11677             second.reload = TRUE;
11678         }
11679         second.pr = NoProc;
11680     }
11681
11682     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11683         char resChar = '=';
11684         switch (result) {
11685         case WhiteWins:
11686           resChar = '+';
11687           if (first.twoMachinesColor[0] == 'w') {
11688             first.matchWins++;
11689           } else {
11690             second.matchWins++;
11691           }
11692           break;
11693         case BlackWins:
11694           resChar = '-';
11695           if (first.twoMachinesColor[0] == 'b') {
11696             first.matchWins++;
11697           } else {
11698             second.matchWins++;
11699           }
11700           break;
11701         case GameUnfinished:
11702           resChar = ' ';
11703         default:
11704           break;
11705         }
11706
11707         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11708         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11709             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11710             ReserveGame(nextGame, resChar); // sets nextGame
11711             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11712             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11713         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11714
11715         if (nextGame <= appData.matchGames && !abortMatch) {
11716             gameMode = nextGameMode;
11717             matchGame = nextGame; // this will be overruled in tourney mode!
11718             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11719             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11720             endingGame = 0; /* [HGM] crash */
11721             return;
11722         } else {
11723             gameMode = nextGameMode;
11724             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11725                      first.tidy, second.tidy,
11726                      first.matchWins, second.matchWins,
11727                      appData.matchGames - (first.matchWins + second.matchWins));
11728             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11729             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11730             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11731             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11732                 first.twoMachinesColor = "black\n";
11733                 second.twoMachinesColor = "white\n";
11734             } else {
11735                 first.twoMachinesColor = "white\n";
11736                 second.twoMachinesColor = "black\n";
11737             }
11738         }
11739     }
11740     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11741         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11742       ExitAnalyzeMode();
11743     gameMode = nextGameMode;
11744     ModeHighlight();
11745     endingGame = 0;  /* [HGM] crash */
11746     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11747         if(matchMode == TRUE) { // match through command line: exit with or without popup
11748             if(ranking) {
11749                 ToNrEvent(forwardMostMove);
11750                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11751                 else ExitEvent(0);
11752             } else DisplayFatalError(buf, 0, 0);
11753         } else { // match through menu; just stop, with or without popup
11754             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11755             ModeHighlight();
11756             if(ranking){
11757                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11758             } else DisplayNote(buf);
11759       }
11760       if(ranking) free(ranking);
11761     }
11762 }
11763
11764 /* Assumes program was just initialized (initString sent).
11765    Leaves program in force mode. */
11766 void
11767 FeedMovesToProgram (ChessProgramState *cps, int upto)
11768 {
11769     int i;
11770
11771     if (appData.debugMode)
11772       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11773               startedFromSetupPosition ? "position and " : "",
11774               backwardMostMove, upto, cps->which);
11775     if(currentlyInitializedVariant != gameInfo.variant) {
11776       char buf[MSG_SIZ];
11777         // [HGM] variantswitch: make engine aware of new variant
11778         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11779                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11780                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11781         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11782         SendToProgram(buf, cps);
11783         currentlyInitializedVariant = gameInfo.variant;
11784     }
11785     SendToProgram("force\n", cps);
11786     if (startedFromSetupPosition) {
11787         SendBoard(cps, backwardMostMove);
11788     if (appData.debugMode) {
11789         fprintf(debugFP, "feedMoves\n");
11790     }
11791     }
11792     for (i = backwardMostMove; i < upto; i++) {
11793         SendMoveToProgram(i, cps);
11794     }
11795 }
11796
11797
11798 int
11799 ResurrectChessProgram ()
11800 {
11801      /* The chess program may have exited.
11802         If so, restart it and feed it all the moves made so far. */
11803     static int doInit = 0;
11804
11805     if (appData.noChessProgram) return 1;
11806
11807     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11808         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11809         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11810         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11811     } else {
11812         if (first.pr != NoProc) return 1;
11813         StartChessProgram(&first);
11814     }
11815     InitChessProgram(&first, FALSE);
11816     FeedMovesToProgram(&first, currentMove);
11817
11818     if (!first.sendTime) {
11819         /* can't tell gnuchess what its clock should read,
11820            so we bow to its notion. */
11821         ResetClocks();
11822         timeRemaining[0][currentMove] = whiteTimeRemaining;
11823         timeRemaining[1][currentMove] = blackTimeRemaining;
11824     }
11825
11826     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11827                 appData.icsEngineAnalyze) && first.analysisSupport) {
11828       SendToProgram("analyze\n", &first);
11829       first.analyzing = TRUE;
11830     }
11831     return 1;
11832 }
11833
11834 /*
11835  * Button procedures
11836  */
11837 void
11838 Reset (int redraw, int init)
11839 {
11840     int i;
11841
11842     if (appData.debugMode) {
11843         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11844                 redraw, init, gameMode);
11845     }
11846     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11847     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11848     CleanupTail(); // [HGM] vari: delete any stored variations
11849     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11850     pausing = pauseExamInvalid = FALSE;
11851     startedFromSetupPosition = blackPlaysFirst = FALSE;
11852     firstMove = TRUE;
11853     whiteFlag = blackFlag = FALSE;
11854     userOfferedDraw = FALSE;
11855     hintRequested = bookRequested = FALSE;
11856     first.maybeThinking = FALSE;
11857     second.maybeThinking = FALSE;
11858     first.bookSuspend = FALSE; // [HGM] book
11859     second.bookSuspend = FALSE;
11860     thinkOutput[0] = NULLCHAR;
11861     lastHint[0] = NULLCHAR;
11862     ClearGameInfo(&gameInfo);
11863     gameInfo.variant = StringToVariant(appData.variant);
11864     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11865     ics_user_moved = ics_clock_paused = FALSE;
11866     ics_getting_history = H_FALSE;
11867     ics_gamenum = -1;
11868     white_holding[0] = black_holding[0] = NULLCHAR;
11869     ClearProgramStats();
11870     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11871
11872     ResetFrontEnd();
11873     ClearHighlights();
11874     flipView = appData.flipView;
11875     ClearPremoveHighlights();
11876     gotPremove = FALSE;
11877     alarmSounded = FALSE;
11878     killX = killY = -1; // [HGM] lion
11879
11880     GameEnds(EndOfFile, NULL, GE_PLAYER);
11881     if(appData.serverMovesName != NULL) {
11882         /* [HGM] prepare to make moves file for broadcasting */
11883         clock_t t = clock();
11884         if(serverMoves != NULL) fclose(serverMoves);
11885         serverMoves = fopen(appData.serverMovesName, "r");
11886         if(serverMoves != NULL) {
11887             fclose(serverMoves);
11888             /* delay 15 sec before overwriting, so all clients can see end */
11889             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11890         }
11891         serverMoves = fopen(appData.serverMovesName, "w");
11892     }
11893
11894     ExitAnalyzeMode();
11895     gameMode = BeginningOfGame;
11896     ModeHighlight();
11897     if(appData.icsActive) gameInfo.variant = VariantNormal;
11898     currentMove = forwardMostMove = backwardMostMove = 0;
11899     MarkTargetSquares(1);
11900     InitPosition(redraw);
11901     for (i = 0; i < MAX_MOVES; i++) {
11902         if (commentList[i] != NULL) {
11903             free(commentList[i]);
11904             commentList[i] = NULL;
11905         }
11906     }
11907     ResetClocks();
11908     timeRemaining[0][0] = whiteTimeRemaining;
11909     timeRemaining[1][0] = blackTimeRemaining;
11910
11911     if (first.pr == NoProc) {
11912         StartChessProgram(&first);
11913     }
11914     if (init) {
11915             InitChessProgram(&first, startedFromSetupPosition);
11916     }
11917     DisplayTitle("");
11918     DisplayMessage("", "");
11919     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11920     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11921     ClearMap();        // [HGM] exclude: invalidate map
11922 }
11923
11924 void
11925 AutoPlayGameLoop ()
11926 {
11927     for (;;) {
11928         if (!AutoPlayOneMove())
11929           return;
11930         if (matchMode || appData.timeDelay == 0)
11931           continue;
11932         if (appData.timeDelay < 0)
11933           return;
11934         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11935         break;
11936     }
11937 }
11938
11939 void
11940 AnalyzeNextGame()
11941 {
11942     ReloadGame(1); // next game
11943 }
11944
11945 int
11946 AutoPlayOneMove ()
11947 {
11948     int fromX, fromY, toX, toY;
11949
11950     if (appData.debugMode) {
11951       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11952     }
11953
11954     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11955       return FALSE;
11956
11957     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11958       pvInfoList[currentMove].depth = programStats.depth;
11959       pvInfoList[currentMove].score = programStats.score;
11960       pvInfoList[currentMove].time  = 0;
11961       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11962       else { // append analysis of final position as comment
11963         char buf[MSG_SIZ];
11964         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11965         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11966       }
11967       programStats.depth = 0;
11968     }
11969
11970     if (currentMove >= forwardMostMove) {
11971       if(gameMode == AnalyzeFile) {
11972           if(appData.loadGameIndex == -1) {
11973             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11974           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11975           } else {
11976           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11977         }
11978       }
11979 //      gameMode = EndOfGame;
11980 //      ModeHighlight();
11981
11982       /* [AS] Clear current move marker at the end of a game */
11983       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11984
11985       return FALSE;
11986     }
11987
11988     toX = moveList[currentMove][2] - AAA;
11989     toY = moveList[currentMove][3] - ONE;
11990
11991     if (moveList[currentMove][1] == '@') {
11992         if (appData.highlightLastMove) {
11993             SetHighlights(-1, -1, toX, toY);
11994         }
11995     } else {
11996         int viaX = moveList[currentMove][5] - AAA;
11997         int viaY = moveList[currentMove][6] - ONE;
11998         fromX = moveList[currentMove][0] - AAA;
11999         fromY = moveList[currentMove][1] - ONE;
12000
12001         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12002
12003         if(moveList[currentMove][4] == ';') { // multi-leg
12004             ChessSquare piece = boards[currentMove][viaY][viaX];
12005             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12006             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12007             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12008             boards[currentMove][viaY][viaX] = piece;
12009         } else
12010         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12011
12012         if (appData.highlightLastMove) {
12013             SetHighlights(fromX, fromY, toX, toY);
12014         }
12015     }
12016     DisplayMove(currentMove);
12017     SendMoveToProgram(currentMove++, &first);
12018     DisplayBothClocks();
12019     DrawPosition(FALSE, boards[currentMove]);
12020     // [HGM] PV info: always display, routine tests if empty
12021     DisplayComment(currentMove - 1, commentList[currentMove]);
12022     return TRUE;
12023 }
12024
12025
12026 int
12027 LoadGameOneMove (ChessMove readAhead)
12028 {
12029     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12030     char promoChar = NULLCHAR;
12031     ChessMove moveType;
12032     char move[MSG_SIZ];
12033     char *p, *q;
12034
12035     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12036         gameMode != AnalyzeMode && gameMode != Training) {
12037         gameFileFP = NULL;
12038         return FALSE;
12039     }
12040
12041     yyboardindex = forwardMostMove;
12042     if (readAhead != EndOfFile) {
12043       moveType = readAhead;
12044     } else {
12045       if (gameFileFP == NULL)
12046           return FALSE;
12047       moveType = (ChessMove) Myylex();
12048     }
12049
12050     done = FALSE;
12051     switch (moveType) {
12052       case Comment:
12053         if (appData.debugMode)
12054           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12055         p = yy_text;
12056
12057         /* append the comment but don't display it */
12058         AppendComment(currentMove, p, FALSE);
12059         return TRUE;
12060
12061       case WhiteCapturesEnPassant:
12062       case BlackCapturesEnPassant:
12063       case WhitePromotion:
12064       case BlackPromotion:
12065       case WhiteNonPromotion:
12066       case BlackNonPromotion:
12067       case NormalMove:
12068       case FirstLeg:
12069       case WhiteKingSideCastle:
12070       case WhiteQueenSideCastle:
12071       case BlackKingSideCastle:
12072       case BlackQueenSideCastle:
12073       case WhiteKingSideCastleWild:
12074       case WhiteQueenSideCastleWild:
12075       case BlackKingSideCastleWild:
12076       case BlackQueenSideCastleWild:
12077       /* PUSH Fabien */
12078       case WhiteHSideCastleFR:
12079       case WhiteASideCastleFR:
12080       case BlackHSideCastleFR:
12081       case BlackASideCastleFR:
12082       /* POP Fabien */
12083         if (appData.debugMode)
12084           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12085         fromX = currentMoveString[0] - AAA;
12086         fromY = currentMoveString[1] - ONE;
12087         toX = currentMoveString[2] - AAA;
12088         toY = currentMoveString[3] - ONE;
12089         promoChar = currentMoveString[4];
12090         if(promoChar == ';') promoChar = NULLCHAR;
12091         break;
12092
12093       case WhiteDrop:
12094       case BlackDrop:
12095         if (appData.debugMode)
12096           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12097         fromX = moveType == WhiteDrop ?
12098           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12099         (int) CharToPiece(ToLower(currentMoveString[0]));
12100         fromY = DROP_RANK;
12101         toX = currentMoveString[2] - AAA;
12102         toY = currentMoveString[3] - ONE;
12103         break;
12104
12105       case WhiteWins:
12106       case BlackWins:
12107       case GameIsDrawn:
12108       case GameUnfinished:
12109         if (appData.debugMode)
12110           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12111         p = strchr(yy_text, '{');
12112         if (p == NULL) p = strchr(yy_text, '(');
12113         if (p == NULL) {
12114             p = yy_text;
12115             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12116         } else {
12117             q = strchr(p, *p == '{' ? '}' : ')');
12118             if (q != NULL) *q = NULLCHAR;
12119             p++;
12120         }
12121         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12122         GameEnds(moveType, p, GE_FILE);
12123         done = TRUE;
12124         if (cmailMsgLoaded) {
12125             ClearHighlights();
12126             flipView = WhiteOnMove(currentMove);
12127             if (moveType == GameUnfinished) flipView = !flipView;
12128             if (appData.debugMode)
12129               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12130         }
12131         break;
12132
12133       case EndOfFile:
12134         if (appData.debugMode)
12135           fprintf(debugFP, "Parser hit end of file\n");
12136         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12137           case MT_NONE:
12138           case MT_CHECK:
12139             break;
12140           case MT_CHECKMATE:
12141           case MT_STAINMATE:
12142             if (WhiteOnMove(currentMove)) {
12143                 GameEnds(BlackWins, "Black mates", GE_FILE);
12144             } else {
12145                 GameEnds(WhiteWins, "White mates", GE_FILE);
12146             }
12147             break;
12148           case MT_STALEMATE:
12149             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12150             break;
12151         }
12152         done = TRUE;
12153         break;
12154
12155       case MoveNumberOne:
12156         if (lastLoadGameStart == GNUChessGame) {
12157             /* GNUChessGames have numbers, but they aren't move numbers */
12158             if (appData.debugMode)
12159               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12160                       yy_text, (int) moveType);
12161             return LoadGameOneMove(EndOfFile); /* tail recursion */
12162         }
12163         /* else fall thru */
12164
12165       case XBoardGame:
12166       case GNUChessGame:
12167       case PGNTag:
12168         /* Reached start of next game in file */
12169         if (appData.debugMode)
12170           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12171         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12172           case MT_NONE:
12173           case MT_CHECK:
12174             break;
12175           case MT_CHECKMATE:
12176           case MT_STAINMATE:
12177             if (WhiteOnMove(currentMove)) {
12178                 GameEnds(BlackWins, "Black mates", GE_FILE);
12179             } else {
12180                 GameEnds(WhiteWins, "White mates", GE_FILE);
12181             }
12182             break;
12183           case MT_STALEMATE:
12184             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12185             break;
12186         }
12187         done = TRUE;
12188         break;
12189
12190       case PositionDiagram:     /* should not happen; ignore */
12191       case ElapsedTime:         /* ignore */
12192       case NAG:                 /* ignore */
12193         if (appData.debugMode)
12194           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12195                   yy_text, (int) moveType);
12196         return LoadGameOneMove(EndOfFile); /* tail recursion */
12197
12198       case IllegalMove:
12199         if (appData.testLegality) {
12200             if (appData.debugMode)
12201               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12202             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12203                     (forwardMostMove / 2) + 1,
12204                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12205             DisplayError(move, 0);
12206             done = TRUE;
12207         } else {
12208             if (appData.debugMode)
12209               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12210                       yy_text, currentMoveString);
12211             if(currentMoveString[1] == '@') {
12212                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12213                 fromY = DROP_RANK;
12214             } else {
12215                 fromX = currentMoveString[0] - AAA;
12216                 fromY = currentMoveString[1] - ONE;
12217             }
12218             toX = currentMoveString[2] - AAA;
12219             toY = currentMoveString[3] - ONE;
12220             promoChar = currentMoveString[4];
12221         }
12222         break;
12223
12224       case AmbiguousMove:
12225         if (appData.debugMode)
12226           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12227         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12228                 (forwardMostMove / 2) + 1,
12229                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12230         DisplayError(move, 0);
12231         done = TRUE;
12232         break;
12233
12234       default:
12235       case ImpossibleMove:
12236         if (appData.debugMode)
12237           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12238         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12239                 (forwardMostMove / 2) + 1,
12240                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12241         DisplayError(move, 0);
12242         done = TRUE;
12243         break;
12244     }
12245
12246     if (done) {
12247         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12248             DrawPosition(FALSE, boards[currentMove]);
12249             DisplayBothClocks();
12250             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12251               DisplayComment(currentMove - 1, commentList[currentMove]);
12252         }
12253         (void) StopLoadGameTimer();
12254         gameFileFP = NULL;
12255         cmailOldMove = forwardMostMove;
12256         return FALSE;
12257     } else {
12258         /* currentMoveString is set as a side-effect of yylex */
12259
12260         thinkOutput[0] = NULLCHAR;
12261         MakeMove(fromX, fromY, toX, toY, promoChar);
12262         killX = killY = -1; // [HGM] lion: used up
12263         currentMove = forwardMostMove;
12264         return TRUE;
12265     }
12266 }
12267
12268 /* Load the nth game from the given file */
12269 int
12270 LoadGameFromFile (char *filename, int n, char *title, int useList)
12271 {
12272     FILE *f;
12273     char buf[MSG_SIZ];
12274
12275     if (strcmp(filename, "-") == 0) {
12276         f = stdin;
12277         title = "stdin";
12278     } else {
12279         f = fopen(filename, "rb");
12280         if (f == NULL) {
12281           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12282             DisplayError(buf, errno);
12283             return FALSE;
12284         }
12285     }
12286     if (fseek(f, 0, 0) == -1) {
12287         /* f is not seekable; probably a pipe */
12288         useList = FALSE;
12289     }
12290     if (useList && n == 0) {
12291         int error = GameListBuild(f);
12292         if (error) {
12293             DisplayError(_("Cannot build game list"), error);
12294         } else if (!ListEmpty(&gameList) &&
12295                    ((ListGame *) gameList.tailPred)->number > 1) {
12296             GameListPopUp(f, title);
12297             return TRUE;
12298         }
12299         GameListDestroy();
12300         n = 1;
12301     }
12302     if (n == 0) n = 1;
12303     return LoadGame(f, n, title, FALSE);
12304 }
12305
12306
12307 void
12308 MakeRegisteredMove ()
12309 {
12310     int fromX, fromY, toX, toY;
12311     char promoChar;
12312     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12313         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12314           case CMAIL_MOVE:
12315           case CMAIL_DRAW:
12316             if (appData.debugMode)
12317               fprintf(debugFP, "Restoring %s for game %d\n",
12318                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12319
12320             thinkOutput[0] = NULLCHAR;
12321             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12322             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12323             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12324             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12325             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12326             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12327             MakeMove(fromX, fromY, toX, toY, promoChar);
12328             ShowMove(fromX, fromY, toX, toY);
12329
12330             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12331               case MT_NONE:
12332               case MT_CHECK:
12333                 break;
12334
12335               case MT_CHECKMATE:
12336               case MT_STAINMATE:
12337                 if (WhiteOnMove(currentMove)) {
12338                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12339                 } else {
12340                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12341                 }
12342                 break;
12343
12344               case MT_STALEMATE:
12345                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12346                 break;
12347             }
12348
12349             break;
12350
12351           case CMAIL_RESIGN:
12352             if (WhiteOnMove(currentMove)) {
12353                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12354             } else {
12355                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12356             }
12357             break;
12358
12359           case CMAIL_ACCEPT:
12360             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12361             break;
12362
12363           default:
12364             break;
12365         }
12366     }
12367
12368     return;
12369 }
12370
12371 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12372 int
12373 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12374 {
12375     int retVal;
12376
12377     if (gameNumber > nCmailGames) {
12378         DisplayError(_("No more games in this message"), 0);
12379         return FALSE;
12380     }
12381     if (f == lastLoadGameFP) {
12382         int offset = gameNumber - lastLoadGameNumber;
12383         if (offset == 0) {
12384             cmailMsg[0] = NULLCHAR;
12385             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12386                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12387                 nCmailMovesRegistered--;
12388             }
12389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12390             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12391                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12392             }
12393         } else {
12394             if (! RegisterMove()) return FALSE;
12395         }
12396     }
12397
12398     retVal = LoadGame(f, gameNumber, title, useList);
12399
12400     /* Make move registered during previous look at this game, if any */
12401     MakeRegisteredMove();
12402
12403     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12404         commentList[currentMove]
12405           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12406         DisplayComment(currentMove - 1, commentList[currentMove]);
12407     }
12408
12409     return retVal;
12410 }
12411
12412 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12413 int
12414 ReloadGame (int offset)
12415 {
12416     int gameNumber = lastLoadGameNumber + offset;
12417     if (lastLoadGameFP == NULL) {
12418         DisplayError(_("No game has been loaded yet"), 0);
12419         return FALSE;
12420     }
12421     if (gameNumber <= 0) {
12422         DisplayError(_("Can't back up any further"), 0);
12423         return FALSE;
12424     }
12425     if (cmailMsgLoaded) {
12426         return CmailLoadGame(lastLoadGameFP, gameNumber,
12427                              lastLoadGameTitle, lastLoadGameUseList);
12428     } else {
12429         return LoadGame(lastLoadGameFP, gameNumber,
12430                         lastLoadGameTitle, lastLoadGameUseList);
12431     }
12432 }
12433
12434 int keys[EmptySquare+1];
12435
12436 int
12437 PositionMatches (Board b1, Board b2)
12438 {
12439     int r, f, sum=0;
12440     switch(appData.searchMode) {
12441         case 1: return CompareWithRights(b1, b2);
12442         case 2:
12443             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12444                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12445             }
12446             return TRUE;
12447         case 3:
12448             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12449               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12450                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12451             }
12452             return sum==0;
12453         case 4:
12454             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12455                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12456             }
12457             return sum==0;
12458     }
12459     return TRUE;
12460 }
12461
12462 #define Q_PROMO  4
12463 #define Q_EP     3
12464 #define Q_BCASTL 2
12465 #define Q_WCASTL 1
12466
12467 int pieceList[256], quickBoard[256];
12468 ChessSquare pieceType[256] = { EmptySquare };
12469 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12470 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12471 int soughtTotal, turn;
12472 Boolean epOK, flipSearch;
12473
12474 typedef struct {
12475     unsigned char piece, to;
12476 } Move;
12477
12478 #define DSIZE (250000)
12479
12480 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12481 Move *moveDatabase = initialSpace;
12482 unsigned int movePtr, dataSize = DSIZE;
12483
12484 int
12485 MakePieceList (Board board, int *counts)
12486 {
12487     int r, f, n=Q_PROMO, total=0;
12488     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12489     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12490         int sq = f + (r<<4);
12491         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12492             quickBoard[sq] = ++n;
12493             pieceList[n] = sq;
12494             pieceType[n] = board[r][f];
12495             counts[board[r][f]]++;
12496             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12497             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12498             total++;
12499         }
12500     }
12501     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12502     return total;
12503 }
12504
12505 void
12506 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12507 {
12508     int sq = fromX + (fromY<<4);
12509     int piece = quickBoard[sq], rook;
12510     quickBoard[sq] = 0;
12511     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12512     if(piece == pieceList[1] && fromY == toY) {
12513       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12514         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12515         moveDatabase[movePtr++].piece = Q_WCASTL;
12516         quickBoard[sq] = piece;
12517         piece = quickBoard[from]; quickBoard[from] = 0;
12518         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12519       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12520         quickBoard[sq] = 0; // remove Rook
12521         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12522         moveDatabase[movePtr++].piece = Q_WCASTL;
12523         quickBoard[sq] = pieceList[1]; // put King
12524         piece = rook;
12525         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12526       }
12527     } else
12528     if(piece == pieceList[2] && fromY == toY) {
12529       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12530         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12531         moveDatabase[movePtr++].piece = Q_BCASTL;
12532         quickBoard[sq] = piece;
12533         piece = quickBoard[from]; quickBoard[from] = 0;
12534         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12535       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12536         quickBoard[sq] = 0; // remove Rook
12537         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12538         moveDatabase[movePtr++].piece = Q_BCASTL;
12539         quickBoard[sq] = pieceList[2]; // put King
12540         piece = rook;
12541         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12542       }
12543     } else
12544     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12545         quickBoard[(fromY<<4)+toX] = 0;
12546         moveDatabase[movePtr].piece = Q_EP;
12547         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12548         moveDatabase[movePtr].to = sq;
12549     } else
12550     if(promoPiece != pieceType[piece]) {
12551         moveDatabase[movePtr++].piece = Q_PROMO;
12552         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12553     }
12554     moveDatabase[movePtr].piece = piece;
12555     quickBoard[sq] = piece;
12556     movePtr++;
12557 }
12558
12559 int
12560 PackGame (Board board)
12561 {
12562     Move *newSpace = NULL;
12563     moveDatabase[movePtr].piece = 0; // terminate previous game
12564     if(movePtr > dataSize) {
12565         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12566         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12567         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12568         if(newSpace) {
12569             int i;
12570             Move *p = moveDatabase, *q = newSpace;
12571             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12572             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12573             moveDatabase = newSpace;
12574         } else { // calloc failed, we must be out of memory. Too bad...
12575             dataSize = 0; // prevent calloc events for all subsequent games
12576             return 0;     // and signal this one isn't cached
12577         }
12578     }
12579     movePtr++;
12580     MakePieceList(board, counts);
12581     return movePtr;
12582 }
12583
12584 int
12585 QuickCompare (Board board, int *minCounts, int *maxCounts)
12586 {   // compare according to search mode
12587     int r, f;
12588     switch(appData.searchMode)
12589     {
12590       case 1: // exact position match
12591         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12592         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12593             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12594         }
12595         break;
12596       case 2: // can have extra material on empty squares
12597         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12598             if(board[r][f] == EmptySquare) continue;
12599             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12600         }
12601         break;
12602       case 3: // material with exact Pawn structure
12603         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12604             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12605             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12606         } // fall through to material comparison
12607       case 4: // exact material
12608         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12609         break;
12610       case 6: // material range with given imbalance
12611         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12612         // fall through to range comparison
12613       case 5: // material range
12614         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12615     }
12616     return TRUE;
12617 }
12618
12619 int
12620 QuickScan (Board board, Move *move)
12621 {   // reconstruct game,and compare all positions in it
12622     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12623     do {
12624         int piece = move->piece;
12625         int to = move->to, from = pieceList[piece];
12626         if(found < 0) { // if already found just scan to game end for final piece count
12627           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12628            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12629            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12630                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12631             ) {
12632             static int lastCounts[EmptySquare+1];
12633             int i;
12634             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12635             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12636           } else stretch = 0;
12637           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12638           if(found >= 0 && !appData.minPieces) return found;
12639         }
12640         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12641           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12642           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12643             piece = (++move)->piece;
12644             from = pieceList[piece];
12645             counts[pieceType[piece]]--;
12646             pieceType[piece] = (ChessSquare) move->to;
12647             counts[move->to]++;
12648           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12649             counts[pieceType[quickBoard[to]]]--;
12650             quickBoard[to] = 0; total--;
12651             move++;
12652             continue;
12653           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12654             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12655             from  = pieceList[piece]; // so this must be King
12656             quickBoard[from] = 0;
12657             pieceList[piece] = to;
12658             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12659             quickBoard[from] = 0; // rook
12660             quickBoard[to] = piece;
12661             to = move->to; piece = move->piece;
12662             goto aftercastle;
12663           }
12664         }
12665         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12666         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12667         quickBoard[from] = 0;
12668       aftercastle:
12669         quickBoard[to] = piece;
12670         pieceList[piece] = to;
12671         cnt++; turn ^= 3;
12672         move++;
12673     } while(1);
12674 }
12675
12676 void
12677 InitSearch ()
12678 {
12679     int r, f;
12680     flipSearch = FALSE;
12681     CopyBoard(soughtBoard, boards[currentMove]);
12682     soughtTotal = MakePieceList(soughtBoard, maxSought);
12683     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12684     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12685     CopyBoard(reverseBoard, boards[currentMove]);
12686     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12687         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12688         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12689         reverseBoard[r][f] = piece;
12690     }
12691     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12692     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12693     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12694                  || (boards[currentMove][CASTLING][2] == NoRights ||
12695                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12696                  && (boards[currentMove][CASTLING][5] == NoRights ||
12697                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12698       ) {
12699         flipSearch = TRUE;
12700         CopyBoard(flipBoard, soughtBoard);
12701         CopyBoard(rotateBoard, reverseBoard);
12702         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12703             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12704             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12705         }
12706     }
12707     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12708     if(appData.searchMode >= 5) {
12709         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12710         MakePieceList(soughtBoard, minSought);
12711         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12712     }
12713     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12714         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12715 }
12716
12717 GameInfo dummyInfo;
12718 static int creatingBook;
12719
12720 int
12721 GameContainsPosition (FILE *f, ListGame *lg)
12722 {
12723     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12724     int fromX, fromY, toX, toY;
12725     char promoChar;
12726     static int initDone=FALSE;
12727
12728     // weed out games based on numerical tag comparison
12729     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12730     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12731     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12732     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12733     if(!initDone) {
12734         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12735         initDone = TRUE;
12736     }
12737     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12738     else CopyBoard(boards[scratch], initialPosition); // default start position
12739     if(lg->moves) {
12740         turn = btm + 1;
12741         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12742         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12743     }
12744     if(btm) plyNr++;
12745     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12746     fseek(f, lg->offset, 0);
12747     yynewfile(f);
12748     while(1) {
12749         yyboardindex = scratch;
12750         quickFlag = plyNr+1;
12751         next = Myylex();
12752         quickFlag = 0;
12753         switch(next) {
12754             case PGNTag:
12755                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12756             default:
12757                 continue;
12758
12759             case XBoardGame:
12760             case GNUChessGame:
12761                 if(plyNr) return -1; // after we have seen moves, this is for new game
12762               continue;
12763
12764             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12765             case ImpossibleMove:
12766             case WhiteWins: // game ends here with these four
12767             case BlackWins:
12768             case GameIsDrawn:
12769             case GameUnfinished:
12770                 return -1;
12771
12772             case IllegalMove:
12773                 if(appData.testLegality) return -1;
12774             case WhiteCapturesEnPassant:
12775             case BlackCapturesEnPassant:
12776             case WhitePromotion:
12777             case BlackPromotion:
12778             case WhiteNonPromotion:
12779             case BlackNonPromotion:
12780             case NormalMove:
12781             case FirstLeg:
12782             case WhiteKingSideCastle:
12783             case WhiteQueenSideCastle:
12784             case BlackKingSideCastle:
12785             case BlackQueenSideCastle:
12786             case WhiteKingSideCastleWild:
12787             case WhiteQueenSideCastleWild:
12788             case BlackKingSideCastleWild:
12789             case BlackQueenSideCastleWild:
12790             case WhiteHSideCastleFR:
12791             case WhiteASideCastleFR:
12792             case BlackHSideCastleFR:
12793             case BlackASideCastleFR:
12794                 fromX = currentMoveString[0] - AAA;
12795                 fromY = currentMoveString[1] - ONE;
12796                 toX = currentMoveString[2] - AAA;
12797                 toY = currentMoveString[3] - ONE;
12798                 promoChar = currentMoveString[4];
12799                 break;
12800             case WhiteDrop:
12801             case BlackDrop:
12802                 fromX = next == WhiteDrop ?
12803                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12804                   (int) CharToPiece(ToLower(currentMoveString[0]));
12805                 fromY = DROP_RANK;
12806                 toX = currentMoveString[2] - AAA;
12807                 toY = currentMoveString[3] - ONE;
12808                 promoChar = 0;
12809                 break;
12810         }
12811         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12812         plyNr++;
12813         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12814         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12815         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12816         if(appData.findMirror) {
12817             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12818             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12819         }
12820     }
12821 }
12822
12823 /* Load the nth game from open file f */
12824 int
12825 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12826 {
12827     ChessMove cm;
12828     char buf[MSG_SIZ];
12829     int gn = gameNumber;
12830     ListGame *lg = NULL;
12831     int numPGNTags = 0;
12832     int err, pos = -1;
12833     GameMode oldGameMode;
12834     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12835     char oldName[MSG_SIZ];
12836
12837     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12838
12839     if (appData.debugMode)
12840         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12841
12842     if (gameMode == Training )
12843         SetTrainingModeOff();
12844
12845     oldGameMode = gameMode;
12846     if (gameMode != BeginningOfGame) {
12847       Reset(FALSE, TRUE);
12848     }
12849     killX = killY = -1; // [HGM] lion: in case we did not Reset
12850
12851     gameFileFP = f;
12852     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12853         fclose(lastLoadGameFP);
12854     }
12855
12856     if (useList) {
12857         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12858
12859         if (lg) {
12860             fseek(f, lg->offset, 0);
12861             GameListHighlight(gameNumber);
12862             pos = lg->position;
12863             gn = 1;
12864         }
12865         else {
12866             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12867               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12868             else
12869             DisplayError(_("Game number out of range"), 0);
12870             return FALSE;
12871         }
12872     } else {
12873         GameListDestroy();
12874         if (fseek(f, 0, 0) == -1) {
12875             if (f == lastLoadGameFP ?
12876                 gameNumber == lastLoadGameNumber + 1 :
12877                 gameNumber == 1) {
12878                 gn = 1;
12879             } else {
12880                 DisplayError(_("Can't seek on game file"), 0);
12881                 return FALSE;
12882             }
12883         }
12884     }
12885     lastLoadGameFP = f;
12886     lastLoadGameNumber = gameNumber;
12887     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12888     lastLoadGameUseList = useList;
12889
12890     yynewfile(f);
12891
12892     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12893       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12894                 lg->gameInfo.black);
12895             DisplayTitle(buf);
12896     } else if (*title != NULLCHAR) {
12897         if (gameNumber > 1) {
12898           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12899             DisplayTitle(buf);
12900         } else {
12901             DisplayTitle(title);
12902         }
12903     }
12904
12905     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12906         gameMode = PlayFromGameFile;
12907         ModeHighlight();
12908     }
12909
12910     currentMove = forwardMostMove = backwardMostMove = 0;
12911     CopyBoard(boards[0], initialPosition);
12912     StopClocks();
12913
12914     /*
12915      * Skip the first gn-1 games in the file.
12916      * Also skip over anything that precedes an identifiable
12917      * start of game marker, to avoid being confused by
12918      * garbage at the start of the file.  Currently
12919      * recognized start of game markers are the move number "1",
12920      * the pattern "gnuchess .* game", the pattern
12921      * "^[#;%] [^ ]* game file", and a PGN tag block.
12922      * A game that starts with one of the latter two patterns
12923      * will also have a move number 1, possibly
12924      * following a position diagram.
12925      * 5-4-02: Let's try being more lenient and allowing a game to
12926      * start with an unnumbered move.  Does that break anything?
12927      */
12928     cm = lastLoadGameStart = EndOfFile;
12929     while (gn > 0) {
12930         yyboardindex = forwardMostMove;
12931         cm = (ChessMove) Myylex();
12932         switch (cm) {
12933           case EndOfFile:
12934             if (cmailMsgLoaded) {
12935                 nCmailGames = CMAIL_MAX_GAMES - gn;
12936             } else {
12937                 Reset(TRUE, TRUE);
12938                 DisplayError(_("Game not found in file"), 0);
12939             }
12940             return FALSE;
12941
12942           case GNUChessGame:
12943           case XBoardGame:
12944             gn--;
12945             lastLoadGameStart = cm;
12946             break;
12947
12948           case MoveNumberOne:
12949             switch (lastLoadGameStart) {
12950               case GNUChessGame:
12951               case XBoardGame:
12952               case PGNTag:
12953                 break;
12954               case MoveNumberOne:
12955               case EndOfFile:
12956                 gn--;           /* count this game */
12957                 lastLoadGameStart = cm;
12958                 break;
12959               default:
12960                 /* impossible */
12961                 break;
12962             }
12963             break;
12964
12965           case PGNTag:
12966             switch (lastLoadGameStart) {
12967               case GNUChessGame:
12968               case PGNTag:
12969               case MoveNumberOne:
12970               case EndOfFile:
12971                 gn--;           /* count this game */
12972                 lastLoadGameStart = cm;
12973                 break;
12974               case XBoardGame:
12975                 lastLoadGameStart = cm; /* game counted already */
12976                 break;
12977               default:
12978                 /* impossible */
12979                 break;
12980             }
12981             if (gn > 0) {
12982                 do {
12983                     yyboardindex = forwardMostMove;
12984                     cm = (ChessMove) Myylex();
12985                 } while (cm == PGNTag || cm == Comment);
12986             }
12987             break;
12988
12989           case WhiteWins:
12990           case BlackWins:
12991           case GameIsDrawn:
12992             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12993                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12994                     != CMAIL_OLD_RESULT) {
12995                     nCmailResults ++ ;
12996                     cmailResult[  CMAIL_MAX_GAMES
12997                                 - gn - 1] = CMAIL_OLD_RESULT;
12998                 }
12999             }
13000             break;
13001
13002           case NormalMove:
13003           case FirstLeg:
13004             /* Only a NormalMove can be at the start of a game
13005              * without a position diagram. */
13006             if (lastLoadGameStart == EndOfFile ) {
13007               gn--;
13008               lastLoadGameStart = MoveNumberOne;
13009             }
13010             break;
13011
13012           default:
13013             break;
13014         }
13015     }
13016
13017     if (appData.debugMode)
13018       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13019
13020     if (cm == XBoardGame) {
13021         /* Skip any header junk before position diagram and/or move 1 */
13022         for (;;) {
13023             yyboardindex = forwardMostMove;
13024             cm = (ChessMove) Myylex();
13025
13026             if (cm == EndOfFile ||
13027                 cm == GNUChessGame || cm == XBoardGame) {
13028                 /* Empty game; pretend end-of-file and handle later */
13029                 cm = EndOfFile;
13030                 break;
13031             }
13032
13033             if (cm == MoveNumberOne || cm == PositionDiagram ||
13034                 cm == PGNTag || cm == Comment)
13035               break;
13036         }
13037     } else if (cm == GNUChessGame) {
13038         if (gameInfo.event != NULL) {
13039             free(gameInfo.event);
13040         }
13041         gameInfo.event = StrSave(yy_text);
13042     }
13043
13044     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13045     while (cm == PGNTag) {
13046         if (appData.debugMode)
13047           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13048         err = ParsePGNTag(yy_text, &gameInfo);
13049         if (!err) numPGNTags++;
13050
13051         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13052         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13053             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13054             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13055             InitPosition(TRUE);
13056             oldVariant = gameInfo.variant;
13057             if (appData.debugMode)
13058               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13059         }
13060
13061
13062         if (gameInfo.fen != NULL) {
13063           Board initial_position;
13064           startedFromSetupPosition = TRUE;
13065           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13066             Reset(TRUE, TRUE);
13067             DisplayError(_("Bad FEN position in file"), 0);
13068             return FALSE;
13069           }
13070           CopyBoard(boards[0], initial_position);
13071           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13072             CopyBoard(initialPosition, initial_position);
13073           if (blackPlaysFirst) {
13074             currentMove = forwardMostMove = backwardMostMove = 1;
13075             CopyBoard(boards[1], initial_position);
13076             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13077             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13078             timeRemaining[0][1] = whiteTimeRemaining;
13079             timeRemaining[1][1] = blackTimeRemaining;
13080             if (commentList[0] != NULL) {
13081               commentList[1] = commentList[0];
13082               commentList[0] = NULL;
13083             }
13084           } else {
13085             currentMove = forwardMostMove = backwardMostMove = 0;
13086           }
13087           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13088           {   int i;
13089               initialRulePlies = FENrulePlies;
13090               for( i=0; i< nrCastlingRights; i++ )
13091                   initialRights[i] = initial_position[CASTLING][i];
13092           }
13093           yyboardindex = forwardMostMove;
13094           free(gameInfo.fen);
13095           gameInfo.fen = NULL;
13096         }
13097
13098         yyboardindex = forwardMostMove;
13099         cm = (ChessMove) Myylex();
13100
13101         /* Handle comments interspersed among the tags */
13102         while (cm == Comment) {
13103             char *p;
13104             if (appData.debugMode)
13105               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13106             p = yy_text;
13107             AppendComment(currentMove, p, FALSE);
13108             yyboardindex = forwardMostMove;
13109             cm = (ChessMove) Myylex();
13110         }
13111     }
13112
13113     /* don't rely on existence of Event tag since if game was
13114      * pasted from clipboard the Event tag may not exist
13115      */
13116     if (numPGNTags > 0){
13117         char *tags;
13118         if (gameInfo.variant == VariantNormal) {
13119           VariantClass v = StringToVariant(gameInfo.event);
13120           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13121           if(v < VariantShogi) gameInfo.variant = v;
13122         }
13123         if (!matchMode) {
13124           if( appData.autoDisplayTags ) {
13125             tags = PGNTags(&gameInfo);
13126             TagsPopUp(tags, CmailMsg());
13127             free(tags);
13128           }
13129         }
13130     } else {
13131         /* Make something up, but don't display it now */
13132         SetGameInfo();
13133         TagsPopDown();
13134     }
13135
13136     if (cm == PositionDiagram) {
13137         int i, j;
13138         char *p;
13139         Board initial_position;
13140
13141         if (appData.debugMode)
13142           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13143
13144         if (!startedFromSetupPosition) {
13145             p = yy_text;
13146             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13147               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13148                 switch (*p) {
13149                   case '{':
13150                   case '[':
13151                   case '-':
13152                   case ' ':
13153                   case '\t':
13154                   case '\n':
13155                   case '\r':
13156                     break;
13157                   default:
13158                     initial_position[i][j++] = CharToPiece(*p);
13159                     break;
13160                 }
13161             while (*p == ' ' || *p == '\t' ||
13162                    *p == '\n' || *p == '\r') p++;
13163
13164             if (strncmp(p, "black", strlen("black"))==0)
13165               blackPlaysFirst = TRUE;
13166             else
13167               blackPlaysFirst = FALSE;
13168             startedFromSetupPosition = TRUE;
13169
13170             CopyBoard(boards[0], initial_position);
13171             if (blackPlaysFirst) {
13172                 currentMove = forwardMostMove = backwardMostMove = 1;
13173                 CopyBoard(boards[1], initial_position);
13174                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13175                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13176                 timeRemaining[0][1] = whiteTimeRemaining;
13177                 timeRemaining[1][1] = blackTimeRemaining;
13178                 if (commentList[0] != NULL) {
13179                     commentList[1] = commentList[0];
13180                     commentList[0] = NULL;
13181                 }
13182             } else {
13183                 currentMove = forwardMostMove = backwardMostMove = 0;
13184             }
13185         }
13186         yyboardindex = forwardMostMove;
13187         cm = (ChessMove) Myylex();
13188     }
13189
13190   if(!creatingBook) {
13191     if (first.pr == NoProc) {
13192         StartChessProgram(&first);
13193     }
13194     InitChessProgram(&first, FALSE);
13195     if(gameInfo.variant == VariantUnknown && *oldName) {
13196         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13197         gameInfo.variant = v;
13198     }
13199     SendToProgram("force\n", &first);
13200     if (startedFromSetupPosition) {
13201         SendBoard(&first, forwardMostMove);
13202     if (appData.debugMode) {
13203         fprintf(debugFP, "Load Game\n");
13204     }
13205         DisplayBothClocks();
13206     }
13207   }
13208
13209     /* [HGM] server: flag to write setup moves in broadcast file as one */
13210     loadFlag = appData.suppressLoadMoves;
13211
13212     while (cm == Comment) {
13213         char *p;
13214         if (appData.debugMode)
13215           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13216         p = yy_text;
13217         AppendComment(currentMove, p, FALSE);
13218         yyboardindex = forwardMostMove;
13219         cm = (ChessMove) Myylex();
13220     }
13221
13222     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13223         cm == WhiteWins || cm == BlackWins ||
13224         cm == GameIsDrawn || cm == GameUnfinished) {
13225         DisplayMessage("", _("No moves in game"));
13226         if (cmailMsgLoaded) {
13227             if (appData.debugMode)
13228               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13229             ClearHighlights();
13230             flipView = FALSE;
13231         }
13232         DrawPosition(FALSE, boards[currentMove]);
13233         DisplayBothClocks();
13234         gameMode = EditGame;
13235         ModeHighlight();
13236         gameFileFP = NULL;
13237         cmailOldMove = 0;
13238         return TRUE;
13239     }
13240
13241     // [HGM] PV info: routine tests if comment empty
13242     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13243         DisplayComment(currentMove - 1, commentList[currentMove]);
13244     }
13245     if (!matchMode && appData.timeDelay != 0)
13246       DrawPosition(FALSE, boards[currentMove]);
13247
13248     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13249       programStats.ok_to_send = 1;
13250     }
13251
13252     /* if the first token after the PGN tags is a move
13253      * and not move number 1, retrieve it from the parser
13254      */
13255     if (cm != MoveNumberOne)
13256         LoadGameOneMove(cm);
13257
13258     /* load the remaining moves from the file */
13259     while (LoadGameOneMove(EndOfFile)) {
13260       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13261       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13262     }
13263
13264     /* rewind to the start of the game */
13265     currentMove = backwardMostMove;
13266
13267     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13268
13269     if (oldGameMode == AnalyzeFile) {
13270       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13271       AnalyzeFileEvent();
13272     } else
13273     if (oldGameMode == AnalyzeMode) {
13274       AnalyzeFileEvent();
13275     }
13276
13277     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13278         long int w, b; // [HGM] adjourn: restore saved clock times
13279         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13280         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13281             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13282             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13283         }
13284     }
13285
13286     if(creatingBook) return TRUE;
13287     if (!matchMode && pos > 0) {
13288         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13289     } else
13290     if (matchMode || appData.timeDelay == 0) {
13291       ToEndEvent();
13292     } else if (appData.timeDelay > 0) {
13293       AutoPlayGameLoop();
13294     }
13295
13296     if (appData.debugMode)
13297         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13298
13299     loadFlag = 0; /* [HGM] true game starts */
13300     return TRUE;
13301 }
13302
13303 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13304 int
13305 ReloadPosition (int offset)
13306 {
13307     int positionNumber = lastLoadPositionNumber + offset;
13308     if (lastLoadPositionFP == NULL) {
13309         DisplayError(_("No position has been loaded yet"), 0);
13310         return FALSE;
13311     }
13312     if (positionNumber <= 0) {
13313         DisplayError(_("Can't back up any further"), 0);
13314         return FALSE;
13315     }
13316     return LoadPosition(lastLoadPositionFP, positionNumber,
13317                         lastLoadPositionTitle);
13318 }
13319
13320 /* Load the nth position from the given file */
13321 int
13322 LoadPositionFromFile (char *filename, int n, char *title)
13323 {
13324     FILE *f;
13325     char buf[MSG_SIZ];
13326
13327     if (strcmp(filename, "-") == 0) {
13328         return LoadPosition(stdin, n, "stdin");
13329     } else {
13330         f = fopen(filename, "rb");
13331         if (f == NULL) {
13332             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13333             DisplayError(buf, errno);
13334             return FALSE;
13335         } else {
13336             return LoadPosition(f, n, title);
13337         }
13338     }
13339 }
13340
13341 /* Load the nth position from the given open file, and close it */
13342 int
13343 LoadPosition (FILE *f, int positionNumber, char *title)
13344 {
13345     char *p, line[MSG_SIZ];
13346     Board initial_position;
13347     int i, j, fenMode, pn;
13348
13349     if (gameMode == Training )
13350         SetTrainingModeOff();
13351
13352     if (gameMode != BeginningOfGame) {
13353         Reset(FALSE, TRUE);
13354     }
13355     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13356         fclose(lastLoadPositionFP);
13357     }
13358     if (positionNumber == 0) positionNumber = 1;
13359     lastLoadPositionFP = f;
13360     lastLoadPositionNumber = positionNumber;
13361     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13362     if (first.pr == NoProc && !appData.noChessProgram) {
13363       StartChessProgram(&first);
13364       InitChessProgram(&first, FALSE);
13365     }
13366     pn = positionNumber;
13367     if (positionNumber < 0) {
13368         /* Negative position number means to seek to that byte offset */
13369         if (fseek(f, -positionNumber, 0) == -1) {
13370             DisplayError(_("Can't seek on position file"), 0);
13371             return FALSE;
13372         };
13373         pn = 1;
13374     } else {
13375         if (fseek(f, 0, 0) == -1) {
13376             if (f == lastLoadPositionFP ?
13377                 positionNumber == lastLoadPositionNumber + 1 :
13378                 positionNumber == 1) {
13379                 pn = 1;
13380             } else {
13381                 DisplayError(_("Can't seek on position file"), 0);
13382                 return FALSE;
13383             }
13384         }
13385     }
13386     /* See if this file is FEN or old-style xboard */
13387     if (fgets(line, MSG_SIZ, f) == NULL) {
13388         DisplayError(_("Position not found in file"), 0);
13389         return FALSE;
13390     }
13391     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13392     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13393
13394     if (pn >= 2) {
13395         if (fenMode || line[0] == '#') pn--;
13396         while (pn > 0) {
13397             /* skip positions before number pn */
13398             if (fgets(line, MSG_SIZ, f) == NULL) {
13399                 Reset(TRUE, TRUE);
13400                 DisplayError(_("Position not found in file"), 0);
13401                 return FALSE;
13402             }
13403             if (fenMode || line[0] == '#') pn--;
13404         }
13405     }
13406
13407     if (fenMode) {
13408         char *p;
13409         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13410             DisplayError(_("Bad FEN position in file"), 0);
13411             return FALSE;
13412         }
13413         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13414             sscanf(p+3, "%s", bestMove);
13415         } else *bestMove = NULLCHAR;
13416     } else {
13417         (void) fgets(line, MSG_SIZ, f);
13418         (void) fgets(line, MSG_SIZ, f);
13419
13420         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13421             (void) fgets(line, MSG_SIZ, f);
13422             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13423                 if (*p == ' ')
13424                   continue;
13425                 initial_position[i][j++] = CharToPiece(*p);
13426             }
13427         }
13428
13429         blackPlaysFirst = FALSE;
13430         if (!feof(f)) {
13431             (void) fgets(line, MSG_SIZ, f);
13432             if (strncmp(line, "black", strlen("black"))==0)
13433               blackPlaysFirst = TRUE;
13434         }
13435     }
13436     startedFromSetupPosition = TRUE;
13437
13438     CopyBoard(boards[0], initial_position);
13439     if (blackPlaysFirst) {
13440         currentMove = forwardMostMove = backwardMostMove = 1;
13441         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13442         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13443         CopyBoard(boards[1], initial_position);
13444         DisplayMessage("", _("Black to play"));
13445     } else {
13446         currentMove = forwardMostMove = backwardMostMove = 0;
13447         DisplayMessage("", _("White to play"));
13448     }
13449     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13450     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13451         SendToProgram("force\n", &first);
13452         SendBoard(&first, forwardMostMove);
13453     }
13454     if (appData.debugMode) {
13455 int i, j;
13456   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13457   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13458         fprintf(debugFP, "Load Position\n");
13459     }
13460
13461     if (positionNumber > 1) {
13462       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13463         DisplayTitle(line);
13464     } else {
13465         DisplayTitle(title);
13466     }
13467     gameMode = EditGame;
13468     ModeHighlight();
13469     ResetClocks();
13470     timeRemaining[0][1] = whiteTimeRemaining;
13471     timeRemaining[1][1] = blackTimeRemaining;
13472     DrawPosition(FALSE, boards[currentMove]);
13473
13474     return TRUE;
13475 }
13476
13477
13478 void
13479 CopyPlayerNameIntoFileName (char **dest, char *src)
13480 {
13481     while (*src != NULLCHAR && *src != ',') {
13482         if (*src == ' ') {
13483             *(*dest)++ = '_';
13484             src++;
13485         } else {
13486             *(*dest)++ = *src++;
13487         }
13488     }
13489 }
13490
13491 char *
13492 DefaultFileName (char *ext)
13493 {
13494     static char def[MSG_SIZ];
13495     char *p;
13496
13497     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13498         p = def;
13499         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13500         *p++ = '-';
13501         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13502         *p++ = '.';
13503         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13504     } else {
13505         def[0] = NULLCHAR;
13506     }
13507     return def;
13508 }
13509
13510 /* Save the current game to the given file */
13511 int
13512 SaveGameToFile (char *filename, int append)
13513 {
13514     FILE *f;
13515     char buf[MSG_SIZ];
13516     int result, i, t,tot=0;
13517
13518     if (strcmp(filename, "-") == 0) {
13519         return SaveGame(stdout, 0, NULL);
13520     } else {
13521         for(i=0; i<10; i++) { // upto 10 tries
13522              f = fopen(filename, append ? "a" : "w");
13523              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13524              if(f || errno != 13) break;
13525              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13526              tot += t;
13527         }
13528         if (f == NULL) {
13529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13530             DisplayError(buf, errno);
13531             return FALSE;
13532         } else {
13533             safeStrCpy(buf, lastMsg, MSG_SIZ);
13534             DisplayMessage(_("Waiting for access to save file"), "");
13535             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13536             DisplayMessage(_("Saving game"), "");
13537             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13538             result = SaveGame(f, 0, NULL);
13539             DisplayMessage(buf, "");
13540             return result;
13541         }
13542     }
13543 }
13544
13545 char *
13546 SavePart (char *str)
13547 {
13548     static char buf[MSG_SIZ];
13549     char *p;
13550
13551     p = strchr(str, ' ');
13552     if (p == NULL) return str;
13553     strncpy(buf, str, p - str);
13554     buf[p - str] = NULLCHAR;
13555     return buf;
13556 }
13557
13558 #define PGN_MAX_LINE 75
13559
13560 #define PGN_SIDE_WHITE  0
13561 #define PGN_SIDE_BLACK  1
13562
13563 static int
13564 FindFirstMoveOutOfBook (int side)
13565 {
13566     int result = -1;
13567
13568     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13569         int index = backwardMostMove;
13570         int has_book_hit = 0;
13571
13572         if( (index % 2) != side ) {
13573             index++;
13574         }
13575
13576         while( index < forwardMostMove ) {
13577             /* Check to see if engine is in book */
13578             int depth = pvInfoList[index].depth;
13579             int score = pvInfoList[index].score;
13580             int in_book = 0;
13581
13582             if( depth <= 2 ) {
13583                 in_book = 1;
13584             }
13585             else if( score == 0 && depth == 63 ) {
13586                 in_book = 1; /* Zappa */
13587             }
13588             else if( score == 2 && depth == 99 ) {
13589                 in_book = 1; /* Abrok */
13590             }
13591
13592             has_book_hit += in_book;
13593
13594             if( ! in_book ) {
13595                 result = index;
13596
13597                 break;
13598             }
13599
13600             index += 2;
13601         }
13602     }
13603
13604     return result;
13605 }
13606
13607 void
13608 GetOutOfBookInfo (char * buf)
13609 {
13610     int oob[2];
13611     int i;
13612     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13613
13614     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13615     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13616
13617     *buf = '\0';
13618
13619     if( oob[0] >= 0 || oob[1] >= 0 ) {
13620         for( i=0; i<2; i++ ) {
13621             int idx = oob[i];
13622
13623             if( idx >= 0 ) {
13624                 if( i > 0 && oob[0] >= 0 ) {
13625                     strcat( buf, "   " );
13626                 }
13627
13628                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13629                 sprintf( buf+strlen(buf), "%s%.2f",
13630                     pvInfoList[idx].score >= 0 ? "+" : "",
13631                     pvInfoList[idx].score / 100.0 );
13632             }
13633         }
13634     }
13635 }
13636
13637 /* Save game in PGN style */
13638 static void
13639 SaveGamePGN2 (FILE *f)
13640 {
13641     int i, offset, linelen, newblock;
13642 //    char *movetext;
13643     char numtext[32];
13644     int movelen, numlen, blank;
13645     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13646
13647     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13648
13649     PrintPGNTags(f, &gameInfo);
13650
13651     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13652
13653     if (backwardMostMove > 0 || startedFromSetupPosition) {
13654         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13655         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13656         fprintf(f, "\n{--------------\n");
13657         PrintPosition(f, backwardMostMove);
13658         fprintf(f, "--------------}\n");
13659         free(fen);
13660     }
13661     else {
13662         /* [AS] Out of book annotation */
13663         if( appData.saveOutOfBookInfo ) {
13664             char buf[64];
13665
13666             GetOutOfBookInfo( buf );
13667
13668             if( buf[0] != '\0' ) {
13669                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13670             }
13671         }
13672
13673         fprintf(f, "\n");
13674     }
13675
13676     i = backwardMostMove;
13677     linelen = 0;
13678     newblock = TRUE;
13679
13680     while (i < forwardMostMove) {
13681         /* Print comments preceding this move */
13682         if (commentList[i] != NULL) {
13683             if (linelen > 0) fprintf(f, "\n");
13684             fprintf(f, "%s", commentList[i]);
13685             linelen = 0;
13686             newblock = TRUE;
13687         }
13688
13689         /* Format move number */
13690         if ((i % 2) == 0)
13691           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13692         else
13693           if (newblock)
13694             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13695           else
13696             numtext[0] = NULLCHAR;
13697
13698         numlen = strlen(numtext);
13699         newblock = FALSE;
13700
13701         /* Print move number */
13702         blank = linelen > 0 && numlen > 0;
13703         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13704             fprintf(f, "\n");
13705             linelen = 0;
13706             blank = 0;
13707         }
13708         if (blank) {
13709             fprintf(f, " ");
13710             linelen++;
13711         }
13712         fprintf(f, "%s", numtext);
13713         linelen += numlen;
13714
13715         /* Get move */
13716         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13717         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13718
13719         /* Print move */
13720         blank = linelen > 0 && movelen > 0;
13721         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13722             fprintf(f, "\n");
13723             linelen = 0;
13724             blank = 0;
13725         }
13726         if (blank) {
13727             fprintf(f, " ");
13728             linelen++;
13729         }
13730         fprintf(f, "%s", move_buffer);
13731         linelen += movelen;
13732
13733         /* [AS] Add PV info if present */
13734         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13735             /* [HGM] add time */
13736             char buf[MSG_SIZ]; int seconds;
13737
13738             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13739
13740             if( seconds <= 0)
13741               buf[0] = 0;
13742             else
13743               if( seconds < 30 )
13744                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13745               else
13746                 {
13747                   seconds = (seconds + 4)/10; // round to full seconds
13748                   if( seconds < 60 )
13749                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13750                   else
13751                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13752                 }
13753
13754             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13755                       pvInfoList[i].score >= 0 ? "+" : "",
13756                       pvInfoList[i].score / 100.0,
13757                       pvInfoList[i].depth,
13758                       buf );
13759
13760             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13761
13762             /* Print score/depth */
13763             blank = linelen > 0 && movelen > 0;
13764             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13765                 fprintf(f, "\n");
13766                 linelen = 0;
13767                 blank = 0;
13768             }
13769             if (blank) {
13770                 fprintf(f, " ");
13771                 linelen++;
13772             }
13773             fprintf(f, "%s", move_buffer);
13774             linelen += movelen;
13775         }
13776
13777         i++;
13778     }
13779
13780     /* Start a new line */
13781     if (linelen > 0) fprintf(f, "\n");
13782
13783     /* Print comments after last move */
13784     if (commentList[i] != NULL) {
13785         fprintf(f, "%s\n", commentList[i]);
13786     }
13787
13788     /* Print result */
13789     if (gameInfo.resultDetails != NULL &&
13790         gameInfo.resultDetails[0] != NULLCHAR) {
13791         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13792         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13793            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13794             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13795         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13796     } else {
13797         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13798     }
13799 }
13800
13801 /* Save game in PGN style and close the file */
13802 int
13803 SaveGamePGN (FILE *f)
13804 {
13805     SaveGamePGN2(f);
13806     fclose(f);
13807     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13808     return TRUE;
13809 }
13810
13811 /* Save game in old style and close the file */
13812 int
13813 SaveGameOldStyle (FILE *f)
13814 {
13815     int i, offset;
13816     time_t tm;
13817
13818     tm = time((time_t *) NULL);
13819
13820     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13821     PrintOpponents(f);
13822
13823     if (backwardMostMove > 0 || startedFromSetupPosition) {
13824         fprintf(f, "\n[--------------\n");
13825         PrintPosition(f, backwardMostMove);
13826         fprintf(f, "--------------]\n");
13827     } else {
13828         fprintf(f, "\n");
13829     }
13830
13831     i = backwardMostMove;
13832     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13833
13834     while (i < forwardMostMove) {
13835         if (commentList[i] != NULL) {
13836             fprintf(f, "[%s]\n", commentList[i]);
13837         }
13838
13839         if ((i % 2) == 1) {
13840             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13841             i++;
13842         } else {
13843             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13844             i++;
13845             if (commentList[i] != NULL) {
13846                 fprintf(f, "\n");
13847                 continue;
13848             }
13849             if (i >= forwardMostMove) {
13850                 fprintf(f, "\n");
13851                 break;
13852             }
13853             fprintf(f, "%s\n", parseList[i]);
13854             i++;
13855         }
13856     }
13857
13858     if (commentList[i] != NULL) {
13859         fprintf(f, "[%s]\n", commentList[i]);
13860     }
13861
13862     /* This isn't really the old style, but it's close enough */
13863     if (gameInfo.resultDetails != NULL &&
13864         gameInfo.resultDetails[0] != NULLCHAR) {
13865         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13866                 gameInfo.resultDetails);
13867     } else {
13868         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13869     }
13870
13871     fclose(f);
13872     return TRUE;
13873 }
13874
13875 /* Save the current game to open file f and close the file */
13876 int
13877 SaveGame (FILE *f, int dummy, char *dummy2)
13878 {
13879     if (gameMode == EditPosition) EditPositionDone(TRUE);
13880     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13881     if (appData.oldSaveStyle)
13882       return SaveGameOldStyle(f);
13883     else
13884       return SaveGamePGN(f);
13885 }
13886
13887 /* Save the current position to the given file */
13888 int
13889 SavePositionToFile (char *filename)
13890 {
13891     FILE *f;
13892     char buf[MSG_SIZ];
13893
13894     if (strcmp(filename, "-") == 0) {
13895         return SavePosition(stdout, 0, NULL);
13896     } else {
13897         f = fopen(filename, "a");
13898         if (f == NULL) {
13899             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13900             DisplayError(buf, errno);
13901             return FALSE;
13902         } else {
13903             safeStrCpy(buf, lastMsg, MSG_SIZ);
13904             DisplayMessage(_("Waiting for access to save file"), "");
13905             flock(fileno(f), LOCK_EX); // [HGM] lock
13906             DisplayMessage(_("Saving position"), "");
13907             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13908             SavePosition(f, 0, NULL);
13909             DisplayMessage(buf, "");
13910             return TRUE;
13911         }
13912     }
13913 }
13914
13915 /* Save the current position to the given open file and close the file */
13916 int
13917 SavePosition (FILE *f, int dummy, char *dummy2)
13918 {
13919     time_t tm;
13920     char *fen;
13921
13922     if (gameMode == EditPosition) EditPositionDone(TRUE);
13923     if (appData.oldSaveStyle) {
13924         tm = time((time_t *) NULL);
13925
13926         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13927         PrintOpponents(f);
13928         fprintf(f, "[--------------\n");
13929         PrintPosition(f, currentMove);
13930         fprintf(f, "--------------]\n");
13931     } else {
13932         fen = PositionToFEN(currentMove, NULL, 1);
13933         fprintf(f, "%s\n", fen);
13934         free(fen);
13935     }
13936     fclose(f);
13937     return TRUE;
13938 }
13939
13940 void
13941 ReloadCmailMsgEvent (int unregister)
13942 {
13943 #if !WIN32
13944     static char *inFilename = NULL;
13945     static char *outFilename;
13946     int i;
13947     struct stat inbuf, outbuf;
13948     int status;
13949
13950     /* Any registered moves are unregistered if unregister is set, */
13951     /* i.e. invoked by the signal handler */
13952     if (unregister) {
13953         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13954             cmailMoveRegistered[i] = FALSE;
13955             if (cmailCommentList[i] != NULL) {
13956                 free(cmailCommentList[i]);
13957                 cmailCommentList[i] = NULL;
13958             }
13959         }
13960         nCmailMovesRegistered = 0;
13961     }
13962
13963     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13964         cmailResult[i] = CMAIL_NOT_RESULT;
13965     }
13966     nCmailResults = 0;
13967
13968     if (inFilename == NULL) {
13969         /* Because the filenames are static they only get malloced once  */
13970         /* and they never get freed                                      */
13971         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13972         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13973
13974         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13975         sprintf(outFilename, "%s.out", appData.cmailGameName);
13976     }
13977
13978     status = stat(outFilename, &outbuf);
13979     if (status < 0) {
13980         cmailMailedMove = FALSE;
13981     } else {
13982         status = stat(inFilename, &inbuf);
13983         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13984     }
13985
13986     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13987        counts the games, notes how each one terminated, etc.
13988
13989        It would be nice to remove this kludge and instead gather all
13990        the information while building the game list.  (And to keep it
13991        in the game list nodes instead of having a bunch of fixed-size
13992        parallel arrays.)  Note this will require getting each game's
13993        termination from the PGN tags, as the game list builder does
13994        not process the game moves.  --mann
13995        */
13996     cmailMsgLoaded = TRUE;
13997     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13998
13999     /* Load first game in the file or popup game menu */
14000     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14001
14002 #endif /* !WIN32 */
14003     return;
14004 }
14005
14006 int
14007 RegisterMove ()
14008 {
14009     FILE *f;
14010     char string[MSG_SIZ];
14011
14012     if (   cmailMailedMove
14013         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14014         return TRUE;            /* Allow free viewing  */
14015     }
14016
14017     /* Unregister move to ensure that we don't leave RegisterMove        */
14018     /* with the move registered when the conditions for registering no   */
14019     /* longer hold                                                       */
14020     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14021         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14022         nCmailMovesRegistered --;
14023
14024         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14025           {
14026               free(cmailCommentList[lastLoadGameNumber - 1]);
14027               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14028           }
14029     }
14030
14031     if (cmailOldMove == -1) {
14032         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14033         return FALSE;
14034     }
14035
14036     if (currentMove > cmailOldMove + 1) {
14037         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14038         return FALSE;
14039     }
14040
14041     if (currentMove < cmailOldMove) {
14042         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14043         return FALSE;
14044     }
14045
14046     if (forwardMostMove > currentMove) {
14047         /* Silently truncate extra moves */
14048         TruncateGame();
14049     }
14050
14051     if (   (currentMove == cmailOldMove + 1)
14052         || (   (currentMove == cmailOldMove)
14053             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14054                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14055         if (gameInfo.result != GameUnfinished) {
14056             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14057         }
14058
14059         if (commentList[currentMove] != NULL) {
14060             cmailCommentList[lastLoadGameNumber - 1]
14061               = StrSave(commentList[currentMove]);
14062         }
14063         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14064
14065         if (appData.debugMode)
14066           fprintf(debugFP, "Saving %s for game %d\n",
14067                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14068
14069         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14070
14071         f = fopen(string, "w");
14072         if (appData.oldSaveStyle) {
14073             SaveGameOldStyle(f); /* also closes the file */
14074
14075             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14076             f = fopen(string, "w");
14077             SavePosition(f, 0, NULL); /* also closes the file */
14078         } else {
14079             fprintf(f, "{--------------\n");
14080             PrintPosition(f, currentMove);
14081             fprintf(f, "--------------}\n\n");
14082
14083             SaveGame(f, 0, NULL); /* also closes the file*/
14084         }
14085
14086         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14087         nCmailMovesRegistered ++;
14088     } else if (nCmailGames == 1) {
14089         DisplayError(_("You have not made a move yet"), 0);
14090         return FALSE;
14091     }
14092
14093     return TRUE;
14094 }
14095
14096 void
14097 MailMoveEvent ()
14098 {
14099 #if !WIN32
14100     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14101     FILE *commandOutput;
14102     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14103     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14104     int nBuffers;
14105     int i;
14106     int archived;
14107     char *arcDir;
14108
14109     if (! cmailMsgLoaded) {
14110         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14111         return;
14112     }
14113
14114     if (nCmailGames == nCmailResults) {
14115         DisplayError(_("No unfinished games"), 0);
14116         return;
14117     }
14118
14119 #if CMAIL_PROHIBIT_REMAIL
14120     if (cmailMailedMove) {
14121       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);
14122         DisplayError(msg, 0);
14123         return;
14124     }
14125 #endif
14126
14127     if (! (cmailMailedMove || RegisterMove())) return;
14128
14129     if (   cmailMailedMove
14130         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14131       snprintf(string, MSG_SIZ, partCommandString,
14132                appData.debugMode ? " -v" : "", appData.cmailGameName);
14133         commandOutput = popen(string, "r");
14134
14135         if (commandOutput == NULL) {
14136             DisplayError(_("Failed to invoke cmail"), 0);
14137         } else {
14138             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14139                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14140             }
14141             if (nBuffers > 1) {
14142                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14143                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14144                 nBytes = MSG_SIZ - 1;
14145             } else {
14146                 (void) memcpy(msg, buffer, nBytes);
14147             }
14148             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14149
14150             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14151                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14152
14153                 archived = TRUE;
14154                 for (i = 0; i < nCmailGames; i ++) {
14155                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14156                         archived = FALSE;
14157                     }
14158                 }
14159                 if (   archived
14160                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14161                         != NULL)) {
14162                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14163                            arcDir,
14164                            appData.cmailGameName,
14165                            gameInfo.date);
14166                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14167                     cmailMsgLoaded = FALSE;
14168                 }
14169             }
14170
14171             DisplayInformation(msg);
14172             pclose(commandOutput);
14173         }
14174     } else {
14175         if ((*cmailMsg) != '\0') {
14176             DisplayInformation(cmailMsg);
14177         }
14178     }
14179
14180     return;
14181 #endif /* !WIN32 */
14182 }
14183
14184 char *
14185 CmailMsg ()
14186 {
14187 #if WIN32
14188     return NULL;
14189 #else
14190     int  prependComma = 0;
14191     char number[5];
14192     char string[MSG_SIZ];       /* Space for game-list */
14193     int  i;
14194
14195     if (!cmailMsgLoaded) return "";
14196
14197     if (cmailMailedMove) {
14198       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14199     } else {
14200         /* Create a list of games left */
14201       snprintf(string, MSG_SIZ, "[");
14202         for (i = 0; i < nCmailGames; i ++) {
14203             if (! (   cmailMoveRegistered[i]
14204                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14205                 if (prependComma) {
14206                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14207                 } else {
14208                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14209                     prependComma = 1;
14210                 }
14211
14212                 strcat(string, number);
14213             }
14214         }
14215         strcat(string, "]");
14216
14217         if (nCmailMovesRegistered + nCmailResults == 0) {
14218             switch (nCmailGames) {
14219               case 1:
14220                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14221                 break;
14222
14223               case 2:
14224                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14225                 break;
14226
14227               default:
14228                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14229                          nCmailGames);
14230                 break;
14231             }
14232         } else {
14233             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14234               case 1:
14235                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14236                          string);
14237                 break;
14238
14239               case 0:
14240                 if (nCmailResults == nCmailGames) {
14241                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14242                 } else {
14243                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14244                 }
14245                 break;
14246
14247               default:
14248                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14249                          string);
14250             }
14251         }
14252     }
14253     return cmailMsg;
14254 #endif /* WIN32 */
14255 }
14256
14257 void
14258 ResetGameEvent ()
14259 {
14260     if (gameMode == Training)
14261       SetTrainingModeOff();
14262
14263     Reset(TRUE, TRUE);
14264     cmailMsgLoaded = FALSE;
14265     if (appData.icsActive) {
14266       SendToICS(ics_prefix);
14267       SendToICS("refresh\n");
14268     }
14269 }
14270
14271 void
14272 ExitEvent (int status)
14273 {
14274     exiting++;
14275     if (exiting > 2) {
14276       /* Give up on clean exit */
14277       exit(status);
14278     }
14279     if (exiting > 1) {
14280       /* Keep trying for clean exit */
14281       return;
14282     }
14283
14284     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14285     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14286
14287     if (telnetISR != NULL) {
14288       RemoveInputSource(telnetISR);
14289     }
14290     if (icsPR != NoProc) {
14291       DestroyChildProcess(icsPR, TRUE);
14292     }
14293
14294     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14295     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14296
14297     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14298     /* make sure this other one finishes before killing it!                  */
14299     if(endingGame) { int count = 0;
14300         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14301         while(endingGame && count++ < 10) DoSleep(1);
14302         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14303     }
14304
14305     /* Kill off chess programs */
14306     if (first.pr != NoProc) {
14307         ExitAnalyzeMode();
14308
14309         DoSleep( appData.delayBeforeQuit );
14310         SendToProgram("quit\n", &first);
14311         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14312     }
14313     if (second.pr != NoProc) {
14314         DoSleep( appData.delayBeforeQuit );
14315         SendToProgram("quit\n", &second);
14316         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14317     }
14318     if (first.isr != NULL) {
14319         RemoveInputSource(first.isr);
14320     }
14321     if (second.isr != NULL) {
14322         RemoveInputSource(second.isr);
14323     }
14324
14325     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14326     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14327
14328     ShutDownFrontEnd();
14329     exit(status);
14330 }
14331
14332 void
14333 PauseEngine (ChessProgramState *cps)
14334 {
14335     SendToProgram("pause\n", cps);
14336     cps->pause = 2;
14337 }
14338
14339 void
14340 UnPauseEngine (ChessProgramState *cps)
14341 {
14342     SendToProgram("resume\n", cps);
14343     cps->pause = 1;
14344 }
14345
14346 void
14347 PauseEvent ()
14348 {
14349     if (appData.debugMode)
14350         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14351     if (pausing) {
14352         pausing = FALSE;
14353         ModeHighlight();
14354         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14355             StartClocks();
14356             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14357                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14358                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14359             }
14360             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14361             HandleMachineMove(stashedInputMove, stalledEngine);
14362             stalledEngine = NULL;
14363             return;
14364         }
14365         if (gameMode == MachinePlaysWhite ||
14366             gameMode == TwoMachinesPlay   ||
14367             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14368             if(first.pause)  UnPauseEngine(&first);
14369             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14370             if(second.pause) UnPauseEngine(&second);
14371             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14372             StartClocks();
14373         } else {
14374             DisplayBothClocks();
14375         }
14376         if (gameMode == PlayFromGameFile) {
14377             if (appData.timeDelay >= 0)
14378                 AutoPlayGameLoop();
14379         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14380             Reset(FALSE, TRUE);
14381             SendToICS(ics_prefix);
14382             SendToICS("refresh\n");
14383         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14384             ForwardInner(forwardMostMove);
14385         }
14386         pauseExamInvalid = FALSE;
14387     } else {
14388         switch (gameMode) {
14389           default:
14390             return;
14391           case IcsExamining:
14392             pauseExamForwardMostMove = forwardMostMove;
14393             pauseExamInvalid = FALSE;
14394             /* fall through */
14395           case IcsObserving:
14396           case IcsPlayingWhite:
14397           case IcsPlayingBlack:
14398             pausing = TRUE;
14399             ModeHighlight();
14400             return;
14401           case PlayFromGameFile:
14402             (void) StopLoadGameTimer();
14403             pausing = TRUE;
14404             ModeHighlight();
14405             break;
14406           case BeginningOfGame:
14407             if (appData.icsActive) return;
14408             /* else fall through */
14409           case MachinePlaysWhite:
14410           case MachinePlaysBlack:
14411           case TwoMachinesPlay:
14412             if (forwardMostMove == 0)
14413               return;           /* don't pause if no one has moved */
14414             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14415                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14416                 if(onMove->pause) {           // thinking engine can be paused
14417                     PauseEngine(onMove);      // do it
14418                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14419                         PauseEngine(onMove->other);
14420                     else
14421                         SendToProgram("easy\n", onMove->other);
14422                     StopClocks();
14423                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14424             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14425                 if(first.pause) {
14426                     PauseEngine(&first);
14427                     StopClocks();
14428                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14429             } else { // human on move, pause pondering by either method
14430                 if(first.pause)
14431                     PauseEngine(&first);
14432                 else if(appData.ponderNextMove)
14433                     SendToProgram("easy\n", &first);
14434                 StopClocks();
14435             }
14436             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14437           case AnalyzeMode:
14438             pausing = TRUE;
14439             ModeHighlight();
14440             break;
14441         }
14442     }
14443 }
14444
14445 void
14446 EditCommentEvent ()
14447 {
14448     char title[MSG_SIZ];
14449
14450     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14451       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14452     } else {
14453       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14454                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14455                parseList[currentMove - 1]);
14456     }
14457
14458     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14459 }
14460
14461
14462 void
14463 EditTagsEvent ()
14464 {
14465     char *tags = PGNTags(&gameInfo);
14466     bookUp = FALSE;
14467     EditTagsPopUp(tags, NULL);
14468     free(tags);
14469 }
14470
14471 void
14472 ToggleSecond ()
14473 {
14474   if(second.analyzing) {
14475     SendToProgram("exit\n", &second);
14476     second.analyzing = FALSE;
14477   } else {
14478     if (second.pr == NoProc) StartChessProgram(&second);
14479     InitChessProgram(&second, FALSE);
14480     FeedMovesToProgram(&second, currentMove);
14481
14482     SendToProgram("analyze\n", &second);
14483     second.analyzing = TRUE;
14484   }
14485 }
14486
14487 /* Toggle ShowThinking */
14488 void
14489 ToggleShowThinking()
14490 {
14491   appData.showThinking = !appData.showThinking;
14492   ShowThinkingEvent();
14493 }
14494
14495 int
14496 AnalyzeModeEvent ()
14497 {
14498     char buf[MSG_SIZ];
14499
14500     if (!first.analysisSupport) {
14501       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14502       DisplayError(buf, 0);
14503       return 0;
14504     }
14505     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14506     if (appData.icsActive) {
14507         if (gameMode != IcsObserving) {
14508           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14509             DisplayError(buf, 0);
14510             /* secure check */
14511             if (appData.icsEngineAnalyze) {
14512                 if (appData.debugMode)
14513                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14514                 ExitAnalyzeMode();
14515                 ModeHighlight();
14516             }
14517             return 0;
14518         }
14519         /* if enable, user wants to disable icsEngineAnalyze */
14520         if (appData.icsEngineAnalyze) {
14521                 ExitAnalyzeMode();
14522                 ModeHighlight();
14523                 return 0;
14524         }
14525         appData.icsEngineAnalyze = TRUE;
14526         if (appData.debugMode)
14527             fprintf(debugFP, "ICS engine analyze starting... \n");
14528     }
14529
14530     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14531     if (appData.noChessProgram || gameMode == AnalyzeMode)
14532       return 0;
14533
14534     if (gameMode != AnalyzeFile) {
14535         if (!appData.icsEngineAnalyze) {
14536                EditGameEvent();
14537                if (gameMode != EditGame) return 0;
14538         }
14539         if (!appData.showThinking) ToggleShowThinking();
14540         ResurrectChessProgram();
14541         SendToProgram("analyze\n", &first);
14542         first.analyzing = TRUE;
14543         /*first.maybeThinking = TRUE;*/
14544         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14545         EngineOutputPopUp();
14546     }
14547     if (!appData.icsEngineAnalyze) {
14548         gameMode = AnalyzeMode;
14549         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14550     }
14551     pausing = FALSE;
14552     ModeHighlight();
14553     SetGameInfo();
14554
14555     StartAnalysisClock();
14556     GetTimeMark(&lastNodeCountTime);
14557     lastNodeCount = 0;
14558     return 1;
14559 }
14560
14561 void
14562 AnalyzeFileEvent ()
14563 {
14564     if (appData.noChessProgram || gameMode == AnalyzeFile)
14565       return;
14566
14567     if (!first.analysisSupport) {
14568       char buf[MSG_SIZ];
14569       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14570       DisplayError(buf, 0);
14571       return;
14572     }
14573
14574     if (gameMode != AnalyzeMode) {
14575         keepInfo = 1; // mere annotating should not alter PGN tags
14576         EditGameEvent();
14577         keepInfo = 0;
14578         if (gameMode != EditGame) return;
14579         if (!appData.showThinking) ToggleShowThinking();
14580         ResurrectChessProgram();
14581         SendToProgram("analyze\n", &first);
14582         first.analyzing = TRUE;
14583         /*first.maybeThinking = TRUE;*/
14584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14585         EngineOutputPopUp();
14586     }
14587     gameMode = AnalyzeFile;
14588     pausing = FALSE;
14589     ModeHighlight();
14590
14591     StartAnalysisClock();
14592     GetTimeMark(&lastNodeCountTime);
14593     lastNodeCount = 0;
14594     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14595     AnalysisPeriodicEvent(1);
14596 }
14597
14598 void
14599 MachineWhiteEvent ()
14600 {
14601     char buf[MSG_SIZ];
14602     char *bookHit = NULL;
14603
14604     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14605       return;
14606
14607
14608     if (gameMode == PlayFromGameFile ||
14609         gameMode == TwoMachinesPlay  ||
14610         gameMode == Training         ||
14611         gameMode == AnalyzeMode      ||
14612         gameMode == EndOfGame)
14613         EditGameEvent();
14614
14615     if (gameMode == EditPosition)
14616         EditPositionDone(TRUE);
14617
14618     if (!WhiteOnMove(currentMove)) {
14619         DisplayError(_("It is not White's turn"), 0);
14620         return;
14621     }
14622
14623     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14624       ExitAnalyzeMode();
14625
14626     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14627         gameMode == AnalyzeFile)
14628         TruncateGame();
14629
14630     ResurrectChessProgram();    /* in case it isn't running */
14631     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14632         gameMode = MachinePlaysWhite;
14633         ResetClocks();
14634     } else
14635     gameMode = MachinePlaysWhite;
14636     pausing = FALSE;
14637     ModeHighlight();
14638     SetGameInfo();
14639     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14640     DisplayTitle(buf);
14641     if (first.sendName) {
14642       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14643       SendToProgram(buf, &first);
14644     }
14645     if (first.sendTime) {
14646       if (first.useColors) {
14647         SendToProgram("black\n", &first); /*gnu kludge*/
14648       }
14649       SendTimeRemaining(&first, TRUE);
14650     }
14651     if (first.useColors) {
14652       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14653     }
14654     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14655     SetMachineThinkingEnables();
14656     first.maybeThinking = TRUE;
14657     StartClocks();
14658     firstMove = FALSE;
14659
14660     if (appData.autoFlipView && !flipView) {
14661       flipView = !flipView;
14662       DrawPosition(FALSE, NULL);
14663       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14664     }
14665
14666     if(bookHit) { // [HGM] book: simulate book reply
14667         static char bookMove[MSG_SIZ]; // a bit generous?
14668
14669         programStats.nodes = programStats.depth = programStats.time =
14670         programStats.score = programStats.got_only_move = 0;
14671         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14672
14673         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14674         strcat(bookMove, bookHit);
14675         HandleMachineMove(bookMove, &first);
14676     }
14677 }
14678
14679 void
14680 MachineBlackEvent ()
14681 {
14682   char buf[MSG_SIZ];
14683   char *bookHit = NULL;
14684
14685     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14686         return;
14687
14688
14689     if (gameMode == PlayFromGameFile ||
14690         gameMode == TwoMachinesPlay  ||
14691         gameMode == Training         ||
14692         gameMode == AnalyzeMode      ||
14693         gameMode == EndOfGame)
14694         EditGameEvent();
14695
14696     if (gameMode == EditPosition)
14697         EditPositionDone(TRUE);
14698
14699     if (WhiteOnMove(currentMove)) {
14700         DisplayError(_("It is not Black's turn"), 0);
14701         return;
14702     }
14703
14704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14705       ExitAnalyzeMode();
14706
14707     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14708         gameMode == AnalyzeFile)
14709         TruncateGame();
14710
14711     ResurrectChessProgram();    /* in case it isn't running */
14712     gameMode = MachinePlaysBlack;
14713     pausing = FALSE;
14714     ModeHighlight();
14715     SetGameInfo();
14716     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14717     DisplayTitle(buf);
14718     if (first.sendName) {
14719       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14720       SendToProgram(buf, &first);
14721     }
14722     if (first.sendTime) {
14723       if (first.useColors) {
14724         SendToProgram("white\n", &first); /*gnu kludge*/
14725       }
14726       SendTimeRemaining(&first, FALSE);
14727     }
14728     if (first.useColors) {
14729       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14730     }
14731     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14732     SetMachineThinkingEnables();
14733     first.maybeThinking = TRUE;
14734     StartClocks();
14735
14736     if (appData.autoFlipView && flipView) {
14737       flipView = !flipView;
14738       DrawPosition(FALSE, NULL);
14739       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14740     }
14741     if(bookHit) { // [HGM] book: simulate book reply
14742         static char bookMove[MSG_SIZ]; // a bit generous?
14743
14744         programStats.nodes = programStats.depth = programStats.time =
14745         programStats.score = programStats.got_only_move = 0;
14746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14747
14748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14749         strcat(bookMove, bookHit);
14750         HandleMachineMove(bookMove, &first);
14751     }
14752 }
14753
14754
14755 void
14756 DisplayTwoMachinesTitle ()
14757 {
14758     char buf[MSG_SIZ];
14759     if (appData.matchGames > 0) {
14760         if(appData.tourneyFile[0]) {
14761           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14762                    gameInfo.white, _("vs."), gameInfo.black,
14763                    nextGame+1, appData.matchGames+1,
14764                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14765         } else
14766         if (first.twoMachinesColor[0] == 'w') {
14767           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14768                    gameInfo.white, _("vs."),  gameInfo.black,
14769                    first.matchWins, second.matchWins,
14770                    matchGame - 1 - (first.matchWins + second.matchWins));
14771         } else {
14772           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14773                    gameInfo.white, _("vs."), gameInfo.black,
14774                    second.matchWins, first.matchWins,
14775                    matchGame - 1 - (first.matchWins + second.matchWins));
14776         }
14777     } else {
14778       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14779     }
14780     DisplayTitle(buf);
14781 }
14782
14783 void
14784 SettingsMenuIfReady ()
14785 {
14786   if (second.lastPing != second.lastPong) {
14787     DisplayMessage("", _("Waiting for second chess program"));
14788     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14789     return;
14790   }
14791   ThawUI();
14792   DisplayMessage("", "");
14793   SettingsPopUp(&second);
14794 }
14795
14796 int
14797 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14798 {
14799     char buf[MSG_SIZ];
14800     if (cps->pr == NoProc) {
14801         StartChessProgram(cps);
14802         if (cps->protocolVersion == 1) {
14803           retry();
14804           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14805         } else {
14806           /* kludge: allow timeout for initial "feature" command */
14807           if(retry != TwoMachinesEventIfReady) FreezeUI();
14808           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14809           DisplayMessage("", buf);
14810           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14811         }
14812         return 1;
14813     }
14814     return 0;
14815 }
14816
14817 void
14818 TwoMachinesEvent P((void))
14819 {
14820     int i;
14821     char buf[MSG_SIZ];
14822     ChessProgramState *onmove;
14823     char *bookHit = NULL;
14824     static int stalling = 0;
14825     TimeMark now;
14826     long wait;
14827
14828     if (appData.noChessProgram) return;
14829
14830     switch (gameMode) {
14831       case TwoMachinesPlay:
14832         return;
14833       case MachinePlaysWhite:
14834       case MachinePlaysBlack:
14835         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14836             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14837             return;
14838         }
14839         /* fall through */
14840       case BeginningOfGame:
14841       case PlayFromGameFile:
14842       case EndOfGame:
14843         EditGameEvent();
14844         if (gameMode != EditGame) return;
14845         break;
14846       case EditPosition:
14847         EditPositionDone(TRUE);
14848         break;
14849       case AnalyzeMode:
14850       case AnalyzeFile:
14851         ExitAnalyzeMode();
14852         break;
14853       case EditGame:
14854       default:
14855         break;
14856     }
14857
14858 //    forwardMostMove = currentMove;
14859     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14860     startingEngine = TRUE;
14861
14862     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14863
14864     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14865     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14866       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14867       return;
14868     }
14869     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14870
14871     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14872                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14873         startingEngine = matchMode = FALSE;
14874         DisplayError("second engine does not play this", 0);
14875         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14876         EditGameEvent(); // switch back to EditGame mode
14877         return;
14878     }
14879
14880     if(!stalling) {
14881       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14882       SendToProgram("force\n", &second);
14883       stalling = 1;
14884       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14885       return;
14886     }
14887     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14888     if(appData.matchPause>10000 || appData.matchPause<10)
14889                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14890     wait = SubtractTimeMarks(&now, &pauseStart);
14891     if(wait < appData.matchPause) {
14892         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14893         return;
14894     }
14895     // we are now committed to starting the game
14896     stalling = 0;
14897     DisplayMessage("", "");
14898     if (startedFromSetupPosition) {
14899         SendBoard(&second, backwardMostMove);
14900     if (appData.debugMode) {
14901         fprintf(debugFP, "Two Machines\n");
14902     }
14903     }
14904     for (i = backwardMostMove; i < forwardMostMove; i++) {
14905         SendMoveToProgram(i, &second);
14906     }
14907
14908     gameMode = TwoMachinesPlay;
14909     pausing = startingEngine = FALSE;
14910     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14911     SetGameInfo();
14912     DisplayTwoMachinesTitle();
14913     firstMove = TRUE;
14914     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14915         onmove = &first;
14916     } else {
14917         onmove = &second;
14918     }
14919     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14920     SendToProgram(first.computerString, &first);
14921     if (first.sendName) {
14922       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14923       SendToProgram(buf, &first);
14924     }
14925     SendToProgram(second.computerString, &second);
14926     if (second.sendName) {
14927       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14928       SendToProgram(buf, &second);
14929     }
14930
14931     ResetClocks();
14932     if (!first.sendTime || !second.sendTime) {
14933         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14934         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14935     }
14936     if (onmove->sendTime) {
14937       if (onmove->useColors) {
14938         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14939       }
14940       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14941     }
14942     if (onmove->useColors) {
14943       SendToProgram(onmove->twoMachinesColor, onmove);
14944     }
14945     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14946 //    SendToProgram("go\n", onmove);
14947     onmove->maybeThinking = TRUE;
14948     SetMachineThinkingEnables();
14949
14950     StartClocks();
14951
14952     if(bookHit) { // [HGM] book: simulate book reply
14953         static char bookMove[MSG_SIZ]; // a bit generous?
14954
14955         programStats.nodes = programStats.depth = programStats.time =
14956         programStats.score = programStats.got_only_move = 0;
14957         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14958
14959         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14960         strcat(bookMove, bookHit);
14961         savedMessage = bookMove; // args for deferred call
14962         savedState = onmove;
14963         ScheduleDelayedEvent(DeferredBookMove, 1);
14964     }
14965 }
14966
14967 void
14968 TrainingEvent ()
14969 {
14970     if (gameMode == Training) {
14971       SetTrainingModeOff();
14972       gameMode = PlayFromGameFile;
14973       DisplayMessage("", _("Training mode off"));
14974     } else {
14975       gameMode = Training;
14976       animateTraining = appData.animate;
14977
14978       /* make sure we are not already at the end of the game */
14979       if (currentMove < forwardMostMove) {
14980         SetTrainingModeOn();
14981         DisplayMessage("", _("Training mode on"));
14982       } else {
14983         gameMode = PlayFromGameFile;
14984         DisplayError(_("Already at end of game"), 0);
14985       }
14986     }
14987     ModeHighlight();
14988 }
14989
14990 void
14991 IcsClientEvent ()
14992 {
14993     if (!appData.icsActive) return;
14994     switch (gameMode) {
14995       case IcsPlayingWhite:
14996       case IcsPlayingBlack:
14997       case IcsObserving:
14998       case IcsIdle:
14999       case BeginningOfGame:
15000       case IcsExamining:
15001         return;
15002
15003       case EditGame:
15004         break;
15005
15006       case EditPosition:
15007         EditPositionDone(TRUE);
15008         break;
15009
15010       case AnalyzeMode:
15011       case AnalyzeFile:
15012         ExitAnalyzeMode();
15013         break;
15014
15015       default:
15016         EditGameEvent();
15017         break;
15018     }
15019
15020     gameMode = IcsIdle;
15021     ModeHighlight();
15022     return;
15023 }
15024
15025 void
15026 EditGameEvent ()
15027 {
15028     int i;
15029
15030     switch (gameMode) {
15031       case Training:
15032         SetTrainingModeOff();
15033         break;
15034       case MachinePlaysWhite:
15035       case MachinePlaysBlack:
15036       case BeginningOfGame:
15037         SendToProgram("force\n", &first);
15038         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15039             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15040                 char buf[MSG_SIZ];
15041                 abortEngineThink = TRUE;
15042                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15043                 SendToProgram(buf, &first);
15044                 DisplayMessage("Aborting engine think", "");
15045                 FreezeUI();
15046             }
15047         }
15048         SetUserThinkingEnables();
15049         break;
15050       case PlayFromGameFile:
15051         (void) StopLoadGameTimer();
15052         if (gameFileFP != NULL) {
15053             gameFileFP = NULL;
15054         }
15055         break;
15056       case EditPosition:
15057         EditPositionDone(TRUE);
15058         break;
15059       case AnalyzeMode:
15060       case AnalyzeFile:
15061         ExitAnalyzeMode();
15062         SendToProgram("force\n", &first);
15063         break;
15064       case TwoMachinesPlay:
15065         GameEnds(EndOfFile, NULL, GE_PLAYER);
15066         ResurrectChessProgram();
15067         SetUserThinkingEnables();
15068         break;
15069       case EndOfGame:
15070         ResurrectChessProgram();
15071         break;
15072       case IcsPlayingBlack:
15073       case IcsPlayingWhite:
15074         DisplayError(_("Warning: You are still playing a game"), 0);
15075         break;
15076       case IcsObserving:
15077         DisplayError(_("Warning: You are still observing a game"), 0);
15078         break;
15079       case IcsExamining:
15080         DisplayError(_("Warning: You are still examining a game"), 0);
15081         break;
15082       case IcsIdle:
15083         break;
15084       case EditGame:
15085       default:
15086         return;
15087     }
15088
15089     pausing = FALSE;
15090     StopClocks();
15091     first.offeredDraw = second.offeredDraw = 0;
15092
15093     if (gameMode == PlayFromGameFile) {
15094         whiteTimeRemaining = timeRemaining[0][currentMove];
15095         blackTimeRemaining = timeRemaining[1][currentMove];
15096         DisplayTitle("");
15097     }
15098
15099     if (gameMode == MachinePlaysWhite ||
15100         gameMode == MachinePlaysBlack ||
15101         gameMode == TwoMachinesPlay ||
15102         gameMode == EndOfGame) {
15103         i = forwardMostMove;
15104         while (i > currentMove) {
15105             SendToProgram("undo\n", &first);
15106             i--;
15107         }
15108         if(!adjustedClock) {
15109         whiteTimeRemaining = timeRemaining[0][currentMove];
15110         blackTimeRemaining = timeRemaining[1][currentMove];
15111         DisplayBothClocks();
15112         }
15113         if (whiteFlag || blackFlag) {
15114             whiteFlag = blackFlag = 0;
15115         }
15116         DisplayTitle("");
15117     }
15118
15119     gameMode = EditGame;
15120     ModeHighlight();
15121     SetGameInfo();
15122 }
15123
15124
15125 void
15126 EditPositionEvent ()
15127 {
15128     if (gameMode == EditPosition) {
15129         EditGameEvent();
15130         return;
15131     }
15132
15133     EditGameEvent();
15134     if (gameMode != EditGame) return;
15135
15136     gameMode = EditPosition;
15137     ModeHighlight();
15138     SetGameInfo();
15139     if (currentMove > 0)
15140       CopyBoard(boards[0], boards[currentMove]);
15141
15142     blackPlaysFirst = !WhiteOnMove(currentMove);
15143     ResetClocks();
15144     currentMove = forwardMostMove = backwardMostMove = 0;
15145     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15146     DisplayMove(-1);
15147     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15148 }
15149
15150 void
15151 ExitAnalyzeMode ()
15152 {
15153     /* [DM] icsEngineAnalyze - possible call from other functions */
15154     if (appData.icsEngineAnalyze) {
15155         appData.icsEngineAnalyze = FALSE;
15156
15157         DisplayMessage("",_("Close ICS engine analyze..."));
15158     }
15159     if (first.analysisSupport && first.analyzing) {
15160       SendToBoth("exit\n");
15161       first.analyzing = second.analyzing = FALSE;
15162     }
15163     thinkOutput[0] = NULLCHAR;
15164 }
15165
15166 void
15167 EditPositionDone (Boolean fakeRights)
15168 {
15169     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15170
15171     startedFromSetupPosition = TRUE;
15172     InitChessProgram(&first, FALSE);
15173     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15174       boards[0][EP_STATUS] = EP_NONE;
15175       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15176       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15177         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15178         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15179       } else boards[0][CASTLING][2] = NoRights;
15180       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15181         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15182         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15183       } else boards[0][CASTLING][5] = NoRights;
15184       if(gameInfo.variant == VariantSChess) {
15185         int i;
15186         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15187           boards[0][VIRGIN][i] = 0;
15188           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15189           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15190         }
15191       }
15192     }
15193     SendToProgram("force\n", &first);
15194     if (blackPlaysFirst) {
15195         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15196         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15197         currentMove = forwardMostMove = backwardMostMove = 1;
15198         CopyBoard(boards[1], boards[0]);
15199     } else {
15200         currentMove = forwardMostMove = backwardMostMove = 0;
15201     }
15202     SendBoard(&first, forwardMostMove);
15203     if (appData.debugMode) {
15204         fprintf(debugFP, "EditPosDone\n");
15205     }
15206     DisplayTitle("");
15207     DisplayMessage("", "");
15208     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15209     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15210     gameMode = EditGame;
15211     ModeHighlight();
15212     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15213     ClearHighlights(); /* [AS] */
15214 }
15215
15216 /* Pause for `ms' milliseconds */
15217 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15218 void
15219 TimeDelay (long ms)
15220 {
15221     TimeMark m1, m2;
15222
15223     GetTimeMark(&m1);
15224     do {
15225         GetTimeMark(&m2);
15226     } while (SubtractTimeMarks(&m2, &m1) < ms);
15227 }
15228
15229 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15230 void
15231 SendMultiLineToICS (char *buf)
15232 {
15233     char temp[MSG_SIZ+1], *p;
15234     int len;
15235
15236     len = strlen(buf);
15237     if (len > MSG_SIZ)
15238       len = MSG_SIZ;
15239
15240     strncpy(temp, buf, len);
15241     temp[len] = 0;
15242
15243     p = temp;
15244     while (*p) {
15245         if (*p == '\n' || *p == '\r')
15246           *p = ' ';
15247         ++p;
15248     }
15249
15250     strcat(temp, "\n");
15251     SendToICS(temp);
15252     SendToPlayer(temp, strlen(temp));
15253 }
15254
15255 void
15256 SetWhiteToPlayEvent ()
15257 {
15258     if (gameMode == EditPosition) {
15259         blackPlaysFirst = FALSE;
15260         DisplayBothClocks();    /* works because currentMove is 0 */
15261     } else if (gameMode == IcsExamining) {
15262         SendToICS(ics_prefix);
15263         SendToICS("tomove white\n");
15264     }
15265 }
15266
15267 void
15268 SetBlackToPlayEvent ()
15269 {
15270     if (gameMode == EditPosition) {
15271         blackPlaysFirst = TRUE;
15272         currentMove = 1;        /* kludge */
15273         DisplayBothClocks();
15274         currentMove = 0;
15275     } else if (gameMode == IcsExamining) {
15276         SendToICS(ics_prefix);
15277         SendToICS("tomove black\n");
15278     }
15279 }
15280
15281 void
15282 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15283 {
15284     char buf[MSG_SIZ];
15285     ChessSquare piece = boards[0][y][x];
15286     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15287     static int lastVariant;
15288
15289     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15290
15291     switch (selection) {
15292       case ClearBoard:
15293         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15294         MarkTargetSquares(1);
15295         CopyBoard(currentBoard, boards[0]);
15296         CopyBoard(menuBoard, initialPosition);
15297         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15298             SendToICS(ics_prefix);
15299             SendToICS("bsetup clear\n");
15300         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15301             SendToICS(ics_prefix);
15302             SendToICS("clearboard\n");
15303         } else {
15304             int nonEmpty = 0;
15305             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15306                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15307                 for (y = 0; y < BOARD_HEIGHT; y++) {
15308                     if (gameMode == IcsExamining) {
15309                         if (boards[currentMove][y][x] != EmptySquare) {
15310                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15311                                     AAA + x, ONE + y);
15312                             SendToICS(buf);
15313                         }
15314                     } else if(boards[0][y][x] != DarkSquare) {
15315                         if(boards[0][y][x] != p) nonEmpty++;
15316                         boards[0][y][x] = p;
15317                     }
15318                 }
15319             }
15320             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15321                 int r;
15322                 for(r = 0; r < BOARD_HEIGHT; r++) {
15323                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15324                     ChessSquare p = menuBoard[r][x];
15325                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15326                   }
15327                 }
15328                 DisplayMessage("Clicking clock again restores position", "");
15329                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15330                 if(!nonEmpty) { // asked to clear an empty board
15331                     CopyBoard(boards[0], menuBoard);
15332                 } else
15333                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15334                     CopyBoard(boards[0], initialPosition);
15335                 } else
15336                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15337                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15338                     CopyBoard(boards[0], erasedBoard);
15339                 } else
15340                     CopyBoard(erasedBoard, currentBoard);
15341
15342             }
15343         }
15344         if (gameMode == EditPosition) {
15345             DrawPosition(FALSE, boards[0]);
15346         }
15347         break;
15348
15349       case WhitePlay:
15350         SetWhiteToPlayEvent();
15351         break;
15352
15353       case BlackPlay:
15354         SetBlackToPlayEvent();
15355         break;
15356
15357       case EmptySquare:
15358         if (gameMode == IcsExamining) {
15359             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15360             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15361             SendToICS(buf);
15362         } else {
15363             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15364                 if(x == BOARD_LEFT-2) {
15365                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15366                     boards[0][y][1] = 0;
15367                 } else
15368                 if(x == BOARD_RGHT+1) {
15369                     if(y >= gameInfo.holdingsSize) break;
15370                     boards[0][y][BOARD_WIDTH-2] = 0;
15371                 } else break;
15372             }
15373             boards[0][y][x] = EmptySquare;
15374             DrawPosition(FALSE, boards[0]);
15375         }
15376         break;
15377
15378       case PromotePiece:
15379         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15380            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15381             selection = (ChessSquare) (PROMOTED(piece));
15382         } else if(piece == EmptySquare) selection = WhiteSilver;
15383         else selection = (ChessSquare)((int)piece - 1);
15384         goto defaultlabel;
15385
15386       case DemotePiece:
15387         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15388            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15389             selection = (ChessSquare) (DEMOTED(piece));
15390         } else if(piece == EmptySquare) selection = BlackSilver;
15391         else selection = (ChessSquare)((int)piece + 1);
15392         goto defaultlabel;
15393
15394       case WhiteQueen:
15395       case BlackQueen:
15396         if(gameInfo.variant == VariantShatranj ||
15397            gameInfo.variant == VariantXiangqi  ||
15398            gameInfo.variant == VariantCourier  ||
15399            gameInfo.variant == VariantASEAN    ||
15400            gameInfo.variant == VariantMakruk     )
15401             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15402         goto defaultlabel;
15403
15404       case WhiteKing:
15405       case BlackKing:
15406         if(gameInfo.variant == VariantXiangqi)
15407             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15408         if(gameInfo.variant == VariantKnightmate)
15409             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15410       default:
15411         defaultlabel:
15412         if (gameMode == IcsExamining) {
15413             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15414             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15415                      PieceToChar(selection), AAA + x, ONE + y);
15416             SendToICS(buf);
15417         } else {
15418             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15419                 int n;
15420                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15421                     n = PieceToNumber(selection - BlackPawn);
15422                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15423                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15424                     boards[0][BOARD_HEIGHT-1-n][1]++;
15425                 } else
15426                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15427                     n = PieceToNumber(selection);
15428                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15429                     boards[0][n][BOARD_WIDTH-1] = selection;
15430                     boards[0][n][BOARD_WIDTH-2]++;
15431                 }
15432             } else
15433             boards[0][y][x] = selection;
15434             DrawPosition(TRUE, boards[0]);
15435             ClearHighlights();
15436             fromX = fromY = -1;
15437         }
15438         break;
15439     }
15440 }
15441
15442
15443 void
15444 DropMenuEvent (ChessSquare selection, int x, int y)
15445 {
15446     ChessMove moveType;
15447
15448     switch (gameMode) {
15449       case IcsPlayingWhite:
15450       case MachinePlaysBlack:
15451         if (!WhiteOnMove(currentMove)) {
15452             DisplayMoveError(_("It is Black's turn"));
15453             return;
15454         }
15455         moveType = WhiteDrop;
15456         break;
15457       case IcsPlayingBlack:
15458       case MachinePlaysWhite:
15459         if (WhiteOnMove(currentMove)) {
15460             DisplayMoveError(_("It is White's turn"));
15461             return;
15462         }
15463         moveType = BlackDrop;
15464         break;
15465       case EditGame:
15466         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15467         break;
15468       default:
15469         return;
15470     }
15471
15472     if (moveType == BlackDrop && selection < BlackPawn) {
15473       selection = (ChessSquare) ((int) selection
15474                                  + (int) BlackPawn - (int) WhitePawn);
15475     }
15476     if (boards[currentMove][y][x] != EmptySquare) {
15477         DisplayMoveError(_("That square is occupied"));
15478         return;
15479     }
15480
15481     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15482 }
15483
15484 void
15485 AcceptEvent ()
15486 {
15487     /* Accept a pending offer of any kind from opponent */
15488
15489     if (appData.icsActive) {
15490         SendToICS(ics_prefix);
15491         SendToICS("accept\n");
15492     } else if (cmailMsgLoaded) {
15493         if (currentMove == cmailOldMove &&
15494             commentList[cmailOldMove] != NULL &&
15495             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15496                    "Black offers a draw" : "White offers a draw")) {
15497             TruncateGame();
15498             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15500         } else {
15501             DisplayError(_("There is no pending offer on this move"), 0);
15502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15503         }
15504     } else {
15505         /* Not used for offers from chess program */
15506     }
15507 }
15508
15509 void
15510 DeclineEvent ()
15511 {
15512     /* Decline a pending offer of any kind from opponent */
15513
15514     if (appData.icsActive) {
15515         SendToICS(ics_prefix);
15516         SendToICS("decline\n");
15517     } else if (cmailMsgLoaded) {
15518         if (currentMove == cmailOldMove &&
15519             commentList[cmailOldMove] != NULL &&
15520             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15521                    "Black offers a draw" : "White offers a draw")) {
15522 #ifdef NOTDEF
15523             AppendComment(cmailOldMove, "Draw declined", TRUE);
15524             DisplayComment(cmailOldMove - 1, "Draw declined");
15525 #endif /*NOTDEF*/
15526         } else {
15527             DisplayError(_("There is no pending offer on this move"), 0);
15528         }
15529     } else {
15530         /* Not used for offers from chess program */
15531     }
15532 }
15533
15534 void
15535 RematchEvent ()
15536 {
15537     /* Issue ICS rematch command */
15538     if (appData.icsActive) {
15539         SendToICS(ics_prefix);
15540         SendToICS("rematch\n");
15541     }
15542 }
15543
15544 void
15545 CallFlagEvent ()
15546 {
15547     /* Call your opponent's flag (claim a win on time) */
15548     if (appData.icsActive) {
15549         SendToICS(ics_prefix);
15550         SendToICS("flag\n");
15551     } else {
15552         switch (gameMode) {
15553           default:
15554             return;
15555           case MachinePlaysWhite:
15556             if (whiteFlag) {
15557                 if (blackFlag)
15558                   GameEnds(GameIsDrawn, "Both players ran out of time",
15559                            GE_PLAYER);
15560                 else
15561                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15562             } else {
15563                 DisplayError(_("Your opponent is not out of time"), 0);
15564             }
15565             break;
15566           case MachinePlaysBlack:
15567             if (blackFlag) {
15568                 if (whiteFlag)
15569                   GameEnds(GameIsDrawn, "Both players ran out of time",
15570                            GE_PLAYER);
15571                 else
15572                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15573             } else {
15574                 DisplayError(_("Your opponent is not out of time"), 0);
15575             }
15576             break;
15577         }
15578     }
15579 }
15580
15581 void
15582 ClockClick (int which)
15583 {       // [HGM] code moved to back-end from winboard.c
15584         if(which) { // black clock
15585           if (gameMode == EditPosition || gameMode == IcsExamining) {
15586             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15587             SetBlackToPlayEvent();
15588           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15589                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15590           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15591           } else if (shiftKey) {
15592             AdjustClock(which, -1);
15593           } else if (gameMode == IcsPlayingWhite ||
15594                      gameMode == MachinePlaysBlack) {
15595             CallFlagEvent();
15596           }
15597         } else { // white clock
15598           if (gameMode == EditPosition || gameMode == IcsExamining) {
15599             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15600             SetWhiteToPlayEvent();
15601           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15602                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15603           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15604           } else if (shiftKey) {
15605             AdjustClock(which, -1);
15606           } else if (gameMode == IcsPlayingBlack ||
15607                    gameMode == MachinePlaysWhite) {
15608             CallFlagEvent();
15609           }
15610         }
15611 }
15612
15613 void
15614 DrawEvent ()
15615 {
15616     /* Offer draw or accept pending draw offer from opponent */
15617
15618     if (appData.icsActive) {
15619         /* Note: tournament rules require draw offers to be
15620            made after you make your move but before you punch
15621            your clock.  Currently ICS doesn't let you do that;
15622            instead, you immediately punch your clock after making
15623            a move, but you can offer a draw at any time. */
15624
15625         SendToICS(ics_prefix);
15626         SendToICS("draw\n");
15627         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15628     } else if (cmailMsgLoaded) {
15629         if (currentMove == cmailOldMove &&
15630             commentList[cmailOldMove] != NULL &&
15631             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15632                    "Black offers a draw" : "White offers a draw")) {
15633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15635         } else if (currentMove == cmailOldMove + 1) {
15636             char *offer = WhiteOnMove(cmailOldMove) ?
15637               "White offers a draw" : "Black offers a draw";
15638             AppendComment(currentMove, offer, TRUE);
15639             DisplayComment(currentMove - 1, offer);
15640             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15641         } else {
15642             DisplayError(_("You must make your move before offering a draw"), 0);
15643             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15644         }
15645     } else if (first.offeredDraw) {
15646         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15647     } else {
15648         if (first.sendDrawOffers) {
15649             SendToProgram("draw\n", &first);
15650             userOfferedDraw = TRUE;
15651         }
15652     }
15653 }
15654
15655 void
15656 AdjournEvent ()
15657 {
15658     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15659
15660     if (appData.icsActive) {
15661         SendToICS(ics_prefix);
15662         SendToICS("adjourn\n");
15663     } else {
15664         /* Currently GNU Chess doesn't offer or accept Adjourns */
15665     }
15666 }
15667
15668
15669 void
15670 AbortEvent ()
15671 {
15672     /* Offer Abort or accept pending Abort offer from opponent */
15673
15674     if (appData.icsActive) {
15675         SendToICS(ics_prefix);
15676         SendToICS("abort\n");
15677     } else {
15678         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15679     }
15680 }
15681
15682 void
15683 ResignEvent ()
15684 {
15685     /* Resign.  You can do this even if it's not your turn. */
15686
15687     if (appData.icsActive) {
15688         SendToICS(ics_prefix);
15689         SendToICS("resign\n");
15690     } else {
15691         switch (gameMode) {
15692           case MachinePlaysWhite:
15693             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15694             break;
15695           case MachinePlaysBlack:
15696             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15697             break;
15698           case EditGame:
15699             if (cmailMsgLoaded) {
15700                 TruncateGame();
15701                 if (WhiteOnMove(cmailOldMove)) {
15702                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15703                 } else {
15704                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15705                 }
15706                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15707             }
15708             break;
15709           default:
15710             break;
15711         }
15712     }
15713 }
15714
15715
15716 void
15717 StopObservingEvent ()
15718 {
15719     /* Stop observing current games */
15720     SendToICS(ics_prefix);
15721     SendToICS("unobserve\n");
15722 }
15723
15724 void
15725 StopExaminingEvent ()
15726 {
15727     /* Stop observing current game */
15728     SendToICS(ics_prefix);
15729     SendToICS("unexamine\n");
15730 }
15731
15732 void
15733 ForwardInner (int target)
15734 {
15735     int limit; int oldSeekGraphUp = seekGraphUp;
15736
15737     if (appData.debugMode)
15738         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15739                 target, currentMove, forwardMostMove);
15740
15741     if (gameMode == EditPosition)
15742       return;
15743
15744     seekGraphUp = FALSE;
15745     MarkTargetSquares(1);
15746     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15747
15748     if (gameMode == PlayFromGameFile && !pausing)
15749       PauseEvent();
15750
15751     if (gameMode == IcsExamining && pausing)
15752       limit = pauseExamForwardMostMove;
15753     else
15754       limit = forwardMostMove;
15755
15756     if (target > limit) target = limit;
15757
15758     if (target > 0 && moveList[target - 1][0]) {
15759         int fromX, fromY, toX, toY;
15760         toX = moveList[target - 1][2] - AAA;
15761         toY = moveList[target - 1][3] - ONE;
15762         if (moveList[target - 1][1] == '@') {
15763             if (appData.highlightLastMove) {
15764                 SetHighlights(-1, -1, toX, toY);
15765             }
15766         } else {
15767             int viaX = moveList[target - 1][5] - AAA;
15768             int viaY = moveList[target - 1][6] - ONE;
15769             fromX = moveList[target - 1][0] - AAA;
15770             fromY = moveList[target - 1][1] - ONE;
15771             if (target == currentMove + 1) {
15772                 if(moveList[target - 1][4] == ';') { // multi-leg
15773                     ChessSquare piece = boards[currentMove][viaY][viaX];
15774                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15775                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15776                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15777                     boards[currentMove][viaY][viaX] = piece;
15778                 } else
15779                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15780             }
15781             if (appData.highlightLastMove) {
15782                 SetHighlights(fromX, fromY, toX, toY);
15783             }
15784         }
15785     }
15786     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15787         gameMode == Training || gameMode == PlayFromGameFile ||
15788         gameMode == AnalyzeFile) {
15789         while (currentMove < target) {
15790             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15791             SendMoveToProgram(currentMove++, &first);
15792         }
15793     } else {
15794         currentMove = target;
15795     }
15796
15797     if (gameMode == EditGame || gameMode == EndOfGame) {
15798         whiteTimeRemaining = timeRemaining[0][currentMove];
15799         blackTimeRemaining = timeRemaining[1][currentMove];
15800     }
15801     DisplayBothClocks();
15802     DisplayMove(currentMove - 1);
15803     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15804     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15805     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15806         DisplayComment(currentMove - 1, commentList[currentMove]);
15807     }
15808     ClearMap(); // [HGM] exclude: invalidate map
15809 }
15810
15811
15812 void
15813 ForwardEvent ()
15814 {
15815     if (gameMode == IcsExamining && !pausing) {
15816         SendToICS(ics_prefix);
15817         SendToICS("forward\n");
15818     } else {
15819         ForwardInner(currentMove + 1);
15820     }
15821 }
15822
15823 void
15824 ToEndEvent ()
15825 {
15826     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15827         /* to optimze, we temporarily turn off analysis mode while we feed
15828          * the remaining moves to the engine. Otherwise we get analysis output
15829          * after each move.
15830          */
15831         if (first.analysisSupport) {
15832           SendToProgram("exit\nforce\n", &first);
15833           first.analyzing = FALSE;
15834         }
15835     }
15836
15837     if (gameMode == IcsExamining && !pausing) {
15838         SendToICS(ics_prefix);
15839         SendToICS("forward 999999\n");
15840     } else {
15841         ForwardInner(forwardMostMove);
15842     }
15843
15844     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15845         /* we have fed all the moves, so reactivate analysis mode */
15846         SendToProgram("analyze\n", &first);
15847         first.analyzing = TRUE;
15848         /*first.maybeThinking = TRUE;*/
15849         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15850     }
15851 }
15852
15853 void
15854 BackwardInner (int target)
15855 {
15856     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15857
15858     if (appData.debugMode)
15859         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15860                 target, currentMove, forwardMostMove);
15861
15862     if (gameMode == EditPosition) return;
15863     seekGraphUp = FALSE;
15864     MarkTargetSquares(1);
15865     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15866     if (currentMove <= backwardMostMove) {
15867         ClearHighlights();
15868         DrawPosition(full_redraw, boards[currentMove]);
15869         return;
15870     }
15871     if (gameMode == PlayFromGameFile && !pausing)
15872       PauseEvent();
15873
15874     if (moveList[target][0]) {
15875         int fromX, fromY, toX, toY;
15876         toX = moveList[target][2] - AAA;
15877         toY = moveList[target][3] - ONE;
15878         if (moveList[target][1] == '@') {
15879             if (appData.highlightLastMove) {
15880                 SetHighlights(-1, -1, toX, toY);
15881             }
15882         } else {
15883             fromX = moveList[target][0] - AAA;
15884             fromY = moveList[target][1] - ONE;
15885             if (target == currentMove - 1) {
15886                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15887             }
15888             if (appData.highlightLastMove) {
15889                 SetHighlights(fromX, fromY, toX, toY);
15890             }
15891         }
15892     }
15893     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15894         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15895         while (currentMove > target) {
15896             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15897                 // null move cannot be undone. Reload program with move history before it.
15898                 int i;
15899                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15900                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15901                 }
15902                 SendBoard(&first, i);
15903               if(second.analyzing) SendBoard(&second, i);
15904                 for(currentMove=i; currentMove<target; currentMove++) {
15905                     SendMoveToProgram(currentMove, &first);
15906                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15907                 }
15908                 break;
15909             }
15910             SendToBoth("undo\n");
15911             currentMove--;
15912         }
15913     } else {
15914         currentMove = target;
15915     }
15916
15917     if (gameMode == EditGame || gameMode == EndOfGame) {
15918         whiteTimeRemaining = timeRemaining[0][currentMove];
15919         blackTimeRemaining = timeRemaining[1][currentMove];
15920     }
15921     DisplayBothClocks();
15922     DisplayMove(currentMove - 1);
15923     DrawPosition(full_redraw, boards[currentMove]);
15924     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15925     // [HGM] PV info: routine tests if comment empty
15926     DisplayComment(currentMove - 1, commentList[currentMove]);
15927     ClearMap(); // [HGM] exclude: invalidate map
15928 }
15929
15930 void
15931 BackwardEvent ()
15932 {
15933     if (gameMode == IcsExamining && !pausing) {
15934         SendToICS(ics_prefix);
15935         SendToICS("backward\n");
15936     } else {
15937         BackwardInner(currentMove - 1);
15938     }
15939 }
15940
15941 void
15942 ToStartEvent ()
15943 {
15944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15945         /* to optimize, we temporarily turn off analysis mode while we undo
15946          * all the moves. Otherwise we get analysis output after each undo.
15947          */
15948         if (first.analysisSupport) {
15949           SendToProgram("exit\nforce\n", &first);
15950           first.analyzing = FALSE;
15951         }
15952     }
15953
15954     if (gameMode == IcsExamining && !pausing) {
15955         SendToICS(ics_prefix);
15956         SendToICS("backward 999999\n");
15957     } else {
15958         BackwardInner(backwardMostMove);
15959     }
15960
15961     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15962         /* we have fed all the moves, so reactivate analysis mode */
15963         SendToProgram("analyze\n", &first);
15964         first.analyzing = TRUE;
15965         /*first.maybeThinking = TRUE;*/
15966         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15967     }
15968 }
15969
15970 void
15971 ToNrEvent (int to)
15972 {
15973   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15974   if (to >= forwardMostMove) to = forwardMostMove;
15975   if (to <= backwardMostMove) to = backwardMostMove;
15976   if (to < currentMove) {
15977     BackwardInner(to);
15978   } else {
15979     ForwardInner(to);
15980   }
15981 }
15982
15983 void
15984 RevertEvent (Boolean annotate)
15985 {
15986     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15987         return;
15988     }
15989     if (gameMode != IcsExamining) {
15990         DisplayError(_("You are not examining a game"), 0);
15991         return;
15992     }
15993     if (pausing) {
15994         DisplayError(_("You can't revert while pausing"), 0);
15995         return;
15996     }
15997     SendToICS(ics_prefix);
15998     SendToICS("revert\n");
15999 }
16000
16001 void
16002 RetractMoveEvent ()
16003 {
16004     switch (gameMode) {
16005       case MachinePlaysWhite:
16006       case MachinePlaysBlack:
16007         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16008             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16009             return;
16010         }
16011         if (forwardMostMove < 2) return;
16012         currentMove = forwardMostMove = forwardMostMove - 2;
16013         whiteTimeRemaining = timeRemaining[0][currentMove];
16014         blackTimeRemaining = timeRemaining[1][currentMove];
16015         DisplayBothClocks();
16016         DisplayMove(currentMove - 1);
16017         ClearHighlights();/*!! could figure this out*/
16018         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16019         SendToProgram("remove\n", &first);
16020         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16021         break;
16022
16023       case BeginningOfGame:
16024       default:
16025         break;
16026
16027       case IcsPlayingWhite:
16028       case IcsPlayingBlack:
16029         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16030             SendToICS(ics_prefix);
16031             SendToICS("takeback 2\n");
16032         } else {
16033             SendToICS(ics_prefix);
16034             SendToICS("takeback 1\n");
16035         }
16036         break;
16037     }
16038 }
16039
16040 void
16041 MoveNowEvent ()
16042 {
16043     ChessProgramState *cps;
16044
16045     switch (gameMode) {
16046       case MachinePlaysWhite:
16047         if (!WhiteOnMove(forwardMostMove)) {
16048             DisplayError(_("It is your turn"), 0);
16049             return;
16050         }
16051         cps = &first;
16052         break;
16053       case MachinePlaysBlack:
16054         if (WhiteOnMove(forwardMostMove)) {
16055             DisplayError(_("It is your turn"), 0);
16056             return;
16057         }
16058         cps = &first;
16059         break;
16060       case TwoMachinesPlay:
16061         if (WhiteOnMove(forwardMostMove) ==
16062             (first.twoMachinesColor[0] == 'w')) {
16063             cps = &first;
16064         } else {
16065             cps = &second;
16066         }
16067         break;
16068       case BeginningOfGame:
16069       default:
16070         return;
16071     }
16072     SendToProgram("?\n", cps);
16073 }
16074
16075 void
16076 TruncateGameEvent ()
16077 {
16078     EditGameEvent();
16079     if (gameMode != EditGame) return;
16080     TruncateGame();
16081 }
16082
16083 void
16084 TruncateGame ()
16085 {
16086     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16087     if (forwardMostMove > currentMove) {
16088         if (gameInfo.resultDetails != NULL) {
16089             free(gameInfo.resultDetails);
16090             gameInfo.resultDetails = NULL;
16091             gameInfo.result = GameUnfinished;
16092         }
16093         forwardMostMove = currentMove;
16094         HistorySet(parseList, backwardMostMove, forwardMostMove,
16095                    currentMove-1);
16096     }
16097 }
16098
16099 void
16100 HintEvent ()
16101 {
16102     if (appData.noChessProgram) return;
16103     switch (gameMode) {
16104       case MachinePlaysWhite:
16105         if (WhiteOnMove(forwardMostMove)) {
16106             DisplayError(_("Wait until your turn."), 0);
16107             return;
16108         }
16109         break;
16110       case BeginningOfGame:
16111       case MachinePlaysBlack:
16112         if (!WhiteOnMove(forwardMostMove)) {
16113             DisplayError(_("Wait until your turn."), 0);
16114             return;
16115         }
16116         break;
16117       default:
16118         DisplayError(_("No hint available"), 0);
16119         return;
16120     }
16121     SendToProgram("hint\n", &first);
16122     hintRequested = TRUE;
16123 }
16124
16125 int
16126 SaveSelected (FILE *g, int dummy, char *dummy2)
16127 {
16128     ListGame * lg = (ListGame *) gameList.head;
16129     int nItem, cnt=0;
16130     FILE *f;
16131
16132     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16133         DisplayError(_("Game list not loaded or empty"), 0);
16134         return 0;
16135     }
16136
16137     creatingBook = TRUE; // suppresses stuff during load game
16138
16139     /* Get list size */
16140     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16141         if(lg->position >= 0) { // selected?
16142             LoadGame(f, nItem, "", TRUE);
16143             SaveGamePGN2(g); // leaves g open
16144             cnt++; DoEvents();
16145         }
16146         lg = (ListGame *) lg->node.succ;
16147     }
16148
16149     fclose(g);
16150     creatingBook = FALSE;
16151
16152     return cnt;
16153 }
16154
16155 void
16156 CreateBookEvent ()
16157 {
16158     ListGame * lg = (ListGame *) gameList.head;
16159     FILE *f, *g;
16160     int nItem;
16161     static int secondTime = FALSE;
16162
16163     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16164         DisplayError(_("Game list not loaded or empty"), 0);
16165         return;
16166     }
16167
16168     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16169         fclose(g);
16170         secondTime++;
16171         DisplayNote(_("Book file exists! Try again for overwrite."));
16172         return;
16173     }
16174
16175     creatingBook = TRUE;
16176     secondTime = FALSE;
16177
16178     /* Get list size */
16179     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16180         if(lg->position >= 0) {
16181             LoadGame(f, nItem, "", TRUE);
16182             AddGameToBook(TRUE);
16183             DoEvents();
16184         }
16185         lg = (ListGame *) lg->node.succ;
16186     }
16187
16188     creatingBook = FALSE;
16189     FlushBook();
16190 }
16191
16192 void
16193 BookEvent ()
16194 {
16195     if (appData.noChessProgram) return;
16196     switch (gameMode) {
16197       case MachinePlaysWhite:
16198         if (WhiteOnMove(forwardMostMove)) {
16199             DisplayError(_("Wait until your turn."), 0);
16200             return;
16201         }
16202         break;
16203       case BeginningOfGame:
16204       case MachinePlaysBlack:
16205         if (!WhiteOnMove(forwardMostMove)) {
16206             DisplayError(_("Wait until your turn."), 0);
16207             return;
16208         }
16209         break;
16210       case EditPosition:
16211         EditPositionDone(TRUE);
16212         break;
16213       case TwoMachinesPlay:
16214         return;
16215       default:
16216         break;
16217     }
16218     SendToProgram("bk\n", &first);
16219     bookOutput[0] = NULLCHAR;
16220     bookRequested = TRUE;
16221 }
16222
16223 void
16224 AboutGameEvent ()
16225 {
16226     char *tags = PGNTags(&gameInfo);
16227     TagsPopUp(tags, CmailMsg());
16228     free(tags);
16229 }
16230
16231 /* end button procedures */
16232
16233 void
16234 PrintPosition (FILE *fp, int move)
16235 {
16236     int i, j;
16237
16238     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16239         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16240             char c = PieceToChar(boards[move][i][j]);
16241             fputc(c == 'x' ? '.' : c, fp);
16242             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16243         }
16244     }
16245     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16246       fprintf(fp, "white to play\n");
16247     else
16248       fprintf(fp, "black to play\n");
16249 }
16250
16251 void
16252 PrintOpponents (FILE *fp)
16253 {
16254     if (gameInfo.white != NULL) {
16255         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16256     } else {
16257         fprintf(fp, "\n");
16258     }
16259 }
16260
16261 /* Find last component of program's own name, using some heuristics */
16262 void
16263 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16264 {
16265     char *p, *q, c;
16266     int local = (strcmp(host, "localhost") == 0);
16267     while (!local && (p = strchr(prog, ';')) != NULL) {
16268         p++;
16269         while (*p == ' ') p++;
16270         prog = p;
16271     }
16272     if (*prog == '"' || *prog == '\'') {
16273         q = strchr(prog + 1, *prog);
16274     } else {
16275         q = strchr(prog, ' ');
16276     }
16277     if (q == NULL) q = prog + strlen(prog);
16278     p = q;
16279     while (p >= prog && *p != '/' && *p != '\\') p--;
16280     p++;
16281     if(p == prog && *p == '"') p++;
16282     c = *q; *q = 0;
16283     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16284     memcpy(buf, p, q - p);
16285     buf[q - p] = NULLCHAR;
16286     if (!local) {
16287         strcat(buf, "@");
16288         strcat(buf, host);
16289     }
16290 }
16291
16292 char *
16293 TimeControlTagValue ()
16294 {
16295     char buf[MSG_SIZ];
16296     if (!appData.clockMode) {
16297       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16298     } else if (movesPerSession > 0) {
16299       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16300     } else if (timeIncrement == 0) {
16301       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16302     } else {
16303       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16304     }
16305     return StrSave(buf);
16306 }
16307
16308 void
16309 SetGameInfo ()
16310 {
16311     /* This routine is used only for certain modes */
16312     VariantClass v = gameInfo.variant;
16313     ChessMove r = GameUnfinished;
16314     char *p = NULL;
16315
16316     if(keepInfo) return;
16317
16318     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16319         r = gameInfo.result;
16320         p = gameInfo.resultDetails;
16321         gameInfo.resultDetails = NULL;
16322     }
16323     ClearGameInfo(&gameInfo);
16324     gameInfo.variant = v;
16325
16326     switch (gameMode) {
16327       case MachinePlaysWhite:
16328         gameInfo.event = StrSave( appData.pgnEventHeader );
16329         gameInfo.site = StrSave(HostName());
16330         gameInfo.date = PGNDate();
16331         gameInfo.round = StrSave("-");
16332         gameInfo.white = StrSave(first.tidy);
16333         gameInfo.black = StrSave(UserName());
16334         gameInfo.timeControl = TimeControlTagValue();
16335         break;
16336
16337       case MachinePlaysBlack:
16338         gameInfo.event = StrSave( appData.pgnEventHeader );
16339         gameInfo.site = StrSave(HostName());
16340         gameInfo.date = PGNDate();
16341         gameInfo.round = StrSave("-");
16342         gameInfo.white = StrSave(UserName());
16343         gameInfo.black = StrSave(first.tidy);
16344         gameInfo.timeControl = TimeControlTagValue();
16345         break;
16346
16347       case TwoMachinesPlay:
16348         gameInfo.event = StrSave( appData.pgnEventHeader );
16349         gameInfo.site = StrSave(HostName());
16350         gameInfo.date = PGNDate();
16351         if (roundNr > 0) {
16352             char buf[MSG_SIZ];
16353             snprintf(buf, MSG_SIZ, "%d", roundNr);
16354             gameInfo.round = StrSave(buf);
16355         } else {
16356             gameInfo.round = StrSave("-");
16357         }
16358         if (first.twoMachinesColor[0] == 'w') {
16359             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16360             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16361         } else {
16362             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16363             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16364         }
16365         gameInfo.timeControl = TimeControlTagValue();
16366         break;
16367
16368       case EditGame:
16369         gameInfo.event = StrSave("Edited game");
16370         gameInfo.site = StrSave(HostName());
16371         gameInfo.date = PGNDate();
16372         gameInfo.round = StrSave("-");
16373         gameInfo.white = StrSave("-");
16374         gameInfo.black = StrSave("-");
16375         gameInfo.result = r;
16376         gameInfo.resultDetails = p;
16377         break;
16378
16379       case EditPosition:
16380         gameInfo.event = StrSave("Edited position");
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       case IcsPlayingWhite:
16389       case IcsPlayingBlack:
16390       case IcsObserving:
16391       case IcsExamining:
16392         break;
16393
16394       case PlayFromGameFile:
16395         gameInfo.event = StrSave("Game from non-PGN file");
16396         gameInfo.site = StrSave(HostName());
16397         gameInfo.date = PGNDate();
16398         gameInfo.round = StrSave("-");
16399         gameInfo.white = StrSave("?");
16400         gameInfo.black = StrSave("?");
16401         break;
16402
16403       default:
16404         break;
16405     }
16406 }
16407
16408 void
16409 ReplaceComment (int index, char *text)
16410 {
16411     int len;
16412     char *p;
16413     float score;
16414
16415     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16416        pvInfoList[index-1].depth == len &&
16417        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16418        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16419     while (*text == '\n') text++;
16420     len = strlen(text);
16421     while (len > 0 && text[len - 1] == '\n') len--;
16422
16423     if (commentList[index] != NULL)
16424       free(commentList[index]);
16425
16426     if (len == 0) {
16427         commentList[index] = NULL;
16428         return;
16429     }
16430   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16431       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16432       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16433     commentList[index] = (char *) malloc(len + 2);
16434     strncpy(commentList[index], text, len);
16435     commentList[index][len] = '\n';
16436     commentList[index][len + 1] = NULLCHAR;
16437   } else {
16438     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16439     char *p;
16440     commentList[index] = (char *) malloc(len + 7);
16441     safeStrCpy(commentList[index], "{\n", 3);
16442     safeStrCpy(commentList[index]+2, text, len+1);
16443     commentList[index][len+2] = NULLCHAR;
16444     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16445     strcat(commentList[index], "\n}\n");
16446   }
16447 }
16448
16449 void
16450 CrushCRs (char *text)
16451 {
16452   char *p = text;
16453   char *q = text;
16454   char ch;
16455
16456   do {
16457     ch = *p++;
16458     if (ch == '\r') continue;
16459     *q++ = ch;
16460   } while (ch != '\0');
16461 }
16462
16463 void
16464 AppendComment (int index, char *text, Boolean addBraces)
16465 /* addBraces  tells if we should add {} */
16466 {
16467     int oldlen, len;
16468     char *old;
16469
16470 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16471     if(addBraces == 3) addBraces = 0; else // force appending literally
16472     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16473
16474     CrushCRs(text);
16475     while (*text == '\n') text++;
16476     len = strlen(text);
16477     while (len > 0 && text[len - 1] == '\n') len--;
16478     text[len] = NULLCHAR;
16479
16480     if (len == 0) return;
16481
16482     if (commentList[index] != NULL) {
16483       Boolean addClosingBrace = addBraces;
16484         old = commentList[index];
16485         oldlen = strlen(old);
16486         while(commentList[index][oldlen-1] ==  '\n')
16487           commentList[index][--oldlen] = NULLCHAR;
16488         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16489         safeStrCpy(commentList[index], old, oldlen + len + 6);
16490         free(old);
16491         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16492         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16493           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16494           while (*text == '\n') { text++; len--; }
16495           commentList[index][--oldlen] = NULLCHAR;
16496       }
16497         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16498         else          strcat(commentList[index], "\n");
16499         strcat(commentList[index], text);
16500         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16501         else          strcat(commentList[index], "\n");
16502     } else {
16503         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16504         if(addBraces)
16505           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16506         else commentList[index][0] = NULLCHAR;
16507         strcat(commentList[index], text);
16508         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16509         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16510     }
16511 }
16512
16513 static char *
16514 FindStr (char * text, char * sub_text)
16515 {
16516     char * result = strstr( text, sub_text );
16517
16518     if( result != NULL ) {
16519         result += strlen( sub_text );
16520     }
16521
16522     return result;
16523 }
16524
16525 /* [AS] Try to extract PV info from PGN comment */
16526 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16527 char *
16528 GetInfoFromComment (int index, char * text)
16529 {
16530     char * sep = text, *p;
16531
16532     if( text != NULL && index > 0 ) {
16533         int score = 0;
16534         int depth = 0;
16535         int time = -1, sec = 0, deci;
16536         char * s_eval = FindStr( text, "[%eval " );
16537         char * s_emt = FindStr( text, "[%emt " );
16538 #if 0
16539         if( s_eval != NULL || s_emt != NULL ) {
16540 #else
16541         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16542 #endif
16543             /* New style */
16544             char delim;
16545
16546             if( s_eval != NULL ) {
16547                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16548                     return text;
16549                 }
16550
16551                 if( delim != ']' ) {
16552                     return text;
16553                 }
16554             }
16555
16556             if( s_emt != NULL ) {
16557             }
16558                 return text;
16559         }
16560         else {
16561             /* We expect something like: [+|-]nnn.nn/dd */
16562             int score_lo = 0;
16563
16564             if(*text != '{') return text; // [HGM] braces: must be normal comment
16565
16566             sep = strchr( text, '/' );
16567             if( sep == NULL || sep < (text+4) ) {
16568                 return text;
16569             }
16570
16571             p = text;
16572             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16573             if(p[1] == '(') { // comment starts with PV
16574                p = strchr(p, ')'); // locate end of PV
16575                if(p == NULL || sep < p+5) return text;
16576                // at this point we have something like "{(.*) +0.23/6 ..."
16577                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16578                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16579                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16580             }
16581             time = -1; sec = -1; deci = -1;
16582             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16583                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16584                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16585                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16586                 return text;
16587             }
16588
16589             if( score_lo < 0 || score_lo >= 100 ) {
16590                 return text;
16591             }
16592
16593             if(sec >= 0) time = 600*time + 10*sec; else
16594             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16595
16596             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16597
16598             /* [HGM] PV time: now locate end of PV info */
16599             while( *++sep >= '0' && *sep <= '9'); // strip depth
16600             if(time >= 0)
16601             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16602             if(sec >= 0)
16603             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16604             if(deci >= 0)
16605             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16606             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16607         }
16608
16609         if( depth <= 0 ) {
16610             return text;
16611         }
16612
16613         if( time < 0 ) {
16614             time = -1;
16615         }
16616
16617         pvInfoList[index-1].depth = depth;
16618         pvInfoList[index-1].score = score;
16619         pvInfoList[index-1].time  = 10*time; // centi-sec
16620         if(*sep == '}') *sep = 0; else *--sep = '{';
16621         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16622     }
16623     return sep;
16624 }
16625
16626 void
16627 SendToProgram (char *message, ChessProgramState *cps)
16628 {
16629     int count, outCount, error;
16630     char buf[MSG_SIZ];
16631
16632     if (cps->pr == NoProc) return;
16633     Attention(cps);
16634
16635     if (appData.debugMode) {
16636         TimeMark now;
16637         GetTimeMark(&now);
16638         fprintf(debugFP, "%ld >%-6s: %s",
16639                 SubtractTimeMarks(&now, &programStartTime),
16640                 cps->which, message);
16641         if(serverFP)
16642             fprintf(serverFP, "%ld >%-6s: %s",
16643                 SubtractTimeMarks(&now, &programStartTime),
16644                 cps->which, message), fflush(serverFP);
16645     }
16646
16647     count = strlen(message);
16648     outCount = OutputToProcess(cps->pr, message, count, &error);
16649     if (outCount < count && !exiting
16650                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16651       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16652       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16653         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16654             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16655                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16656                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16657                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16658             } else {
16659                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16660                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16661                 gameInfo.result = res;
16662             }
16663             gameInfo.resultDetails = StrSave(buf);
16664         }
16665         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16666         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16667     }
16668 }
16669
16670 void
16671 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16672 {
16673     char *end_str;
16674     char buf[MSG_SIZ];
16675     ChessProgramState *cps = (ChessProgramState *)closure;
16676
16677     if (isr != cps->isr) return; /* Killed intentionally */
16678     if (count <= 0) {
16679         if (count == 0) {
16680             RemoveInputSource(cps->isr);
16681             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16682                     _(cps->which), cps->program);
16683             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16684             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16685                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16686                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16687                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16688                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16689                 } else {
16690                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16691                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16692                     gameInfo.result = res;
16693                 }
16694                 gameInfo.resultDetails = StrSave(buf);
16695             }
16696             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16697             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16698         } else {
16699             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16700                     _(cps->which), cps->program);
16701             RemoveInputSource(cps->isr);
16702
16703             /* [AS] Program is misbehaving badly... kill it */
16704             if( count == -2 ) {
16705                 DestroyChildProcess( cps->pr, 9 );
16706                 cps->pr = NoProc;
16707             }
16708
16709             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16710         }
16711         return;
16712     }
16713
16714     if ((end_str = strchr(message, '\r')) != NULL)
16715       *end_str = NULLCHAR;
16716     if ((end_str = strchr(message, '\n')) != NULL)
16717       *end_str = NULLCHAR;
16718
16719     if (appData.debugMode) {
16720         TimeMark now; int print = 1;
16721         char *quote = ""; char c; int i;
16722
16723         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16724                 char start = message[0];
16725                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16726                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16727                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16728                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16729                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16730                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16731                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16732                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16733                    sscanf(message, "hint: %c", &c)!=1 &&
16734                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16735                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16736                     print = (appData.engineComments >= 2);
16737                 }
16738                 message[0] = start; // restore original message
16739         }
16740         if(print) {
16741                 GetTimeMark(&now);
16742                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16743                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16744                         quote,
16745                         message);
16746                 if(serverFP)
16747                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16748                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16749                         quote,
16750                         message), fflush(serverFP);
16751         }
16752     }
16753
16754     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16755     if (appData.icsEngineAnalyze) {
16756         if (strstr(message, "whisper") != NULL ||
16757              strstr(message, "kibitz") != NULL ||
16758             strstr(message, "tellics") != NULL) return;
16759     }
16760
16761     HandleMachineMove(message, cps);
16762 }
16763
16764
16765 void
16766 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16767 {
16768     char buf[MSG_SIZ];
16769     int seconds;
16770
16771     if( timeControl_2 > 0 ) {
16772         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16773             tc = timeControl_2;
16774         }
16775     }
16776     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16777     inc /= cps->timeOdds;
16778     st  /= cps->timeOdds;
16779
16780     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16781
16782     if (st > 0) {
16783       /* Set exact time per move, normally using st command */
16784       if (cps->stKludge) {
16785         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16786         seconds = st % 60;
16787         if (seconds == 0) {
16788           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16789         } else {
16790           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16791         }
16792       } else {
16793         snprintf(buf, MSG_SIZ, "st %d\n", st);
16794       }
16795     } else {
16796       /* Set conventional or incremental time control, using level command */
16797       if (seconds == 0) {
16798         /* Note old gnuchess bug -- minutes:seconds used to not work.
16799            Fixed in later versions, but still avoid :seconds
16800            when seconds is 0. */
16801         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16802       } else {
16803         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16804                  seconds, inc/1000.);
16805       }
16806     }
16807     SendToProgram(buf, cps);
16808
16809     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16810     /* Orthogonally, limit search to given depth */
16811     if (sd > 0) {
16812       if (cps->sdKludge) {
16813         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16814       } else {
16815         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16816       }
16817       SendToProgram(buf, cps);
16818     }
16819
16820     if(cps->nps >= 0) { /* [HGM] nps */
16821         if(cps->supportsNPS == FALSE)
16822           cps->nps = -1; // don't use if engine explicitly says not supported!
16823         else {
16824           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16825           SendToProgram(buf, cps);
16826         }
16827     }
16828 }
16829
16830 ChessProgramState *
16831 WhitePlayer ()
16832 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16833 {
16834     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16835        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16836         return &second;
16837     return &first;
16838 }
16839
16840 void
16841 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16842 {
16843     char message[MSG_SIZ];
16844     long time, otime;
16845
16846     /* Note: this routine must be called when the clocks are stopped
16847        or when they have *just* been set or switched; otherwise
16848        it will be off by the time since the current tick started.
16849     */
16850     if (machineWhite) {
16851         time = whiteTimeRemaining / 10;
16852         otime = blackTimeRemaining / 10;
16853     } else {
16854         time = blackTimeRemaining / 10;
16855         otime = whiteTimeRemaining / 10;
16856     }
16857     /* [HGM] translate opponent's time by time-odds factor */
16858     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16859
16860     if (time <= 0) time = 1;
16861     if (otime <= 0) otime = 1;
16862
16863     snprintf(message, MSG_SIZ, "time %ld\n", time);
16864     SendToProgram(message, cps);
16865
16866     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16867     SendToProgram(message, cps);
16868 }
16869
16870 char *
16871 EngineDefinedVariant (ChessProgramState *cps, int n)
16872 {   // return name of n-th unknown variant that engine supports
16873     static char buf[MSG_SIZ];
16874     char *p, *s = cps->variants;
16875     if(!s) return NULL;
16876     do { // parse string from variants feature
16877       VariantClass v;
16878         p = strchr(s, ',');
16879         if(p) *p = NULLCHAR;
16880       v = StringToVariant(s);
16881       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16882         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16883             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16884                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16885                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16886                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16887             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16888         }
16889         if(p) *p++ = ',';
16890         if(n < 0) return buf;
16891     } while(s = p);
16892     return NULL;
16893 }
16894
16895 int
16896 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16897 {
16898   char buf[MSG_SIZ];
16899   int len = strlen(name);
16900   int val;
16901
16902   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16903     (*p) += len + 1;
16904     sscanf(*p, "%d", &val);
16905     *loc = (val != 0);
16906     while (**p && **p != ' ')
16907       (*p)++;
16908     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16909     SendToProgram(buf, cps);
16910     return TRUE;
16911   }
16912   return FALSE;
16913 }
16914
16915 int
16916 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16917 {
16918   char buf[MSG_SIZ];
16919   int len = strlen(name);
16920   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16921     (*p) += len + 1;
16922     sscanf(*p, "%d", loc);
16923     while (**p && **p != ' ') (*p)++;
16924     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16925     SendToProgram(buf, cps);
16926     return TRUE;
16927   }
16928   return FALSE;
16929 }
16930
16931 int
16932 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16933 {
16934   char buf[MSG_SIZ];
16935   int len = strlen(name);
16936   if (strncmp((*p), name, len) == 0
16937       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16938     (*p) += len + 2;
16939     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16940     sscanf(*p, "%[^\"]", *loc);
16941     while (**p && **p != '\"') (*p)++;
16942     if (**p == '\"') (*p)++;
16943     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16944     SendToProgram(buf, cps);
16945     return TRUE;
16946   }
16947   return FALSE;
16948 }
16949
16950 int
16951 ParseOption (Option *opt, ChessProgramState *cps)
16952 // [HGM] options: process the string that defines an engine option, and determine
16953 // name, type, default value, and allowed value range
16954 {
16955         char *p, *q, buf[MSG_SIZ];
16956         int n, min = (-1)<<31, max = 1<<31, def;
16957
16958         if(p = strstr(opt->name, " -spin ")) {
16959             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16960             if(max < min) max = min; // enforce consistency
16961             if(def < min) def = min;
16962             if(def > max) def = max;
16963             opt->value = def;
16964             opt->min = min;
16965             opt->max = max;
16966             opt->type = Spin;
16967         } else if((p = strstr(opt->name, " -slider "))) {
16968             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16969             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16970             if(max < min) max = min; // enforce consistency
16971             if(def < min) def = min;
16972             if(def > max) def = max;
16973             opt->value = def;
16974             opt->min = min;
16975             opt->max = max;
16976             opt->type = Spin; // Slider;
16977         } else if((p = strstr(opt->name, " -string "))) {
16978             opt->textValue = p+9;
16979             opt->type = TextBox;
16980         } else if((p = strstr(opt->name, " -file "))) {
16981             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16982             opt->textValue = p+7;
16983             opt->type = FileName; // FileName;
16984         } else if((p = strstr(opt->name, " -path "))) {
16985             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16986             opt->textValue = p+7;
16987             opt->type = PathName; // PathName;
16988         } else if(p = strstr(opt->name, " -check ")) {
16989             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16990             opt->value = (def != 0);
16991             opt->type = CheckBox;
16992         } else if(p = strstr(opt->name, " -combo ")) {
16993             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16994             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16995             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16996             opt->value = n = 0;
16997             while(q = StrStr(q, " /// ")) {
16998                 n++; *q = 0;    // count choices, and null-terminate each of them
16999                 q += 5;
17000                 if(*q == '*') { // remember default, which is marked with * prefix
17001                     q++;
17002                     opt->value = n;
17003                 }
17004                 cps->comboList[cps->comboCnt++] = q;
17005             }
17006             cps->comboList[cps->comboCnt++] = NULL;
17007             opt->max = n + 1;
17008             opt->type = ComboBox;
17009         } else if(p = strstr(opt->name, " -button")) {
17010             opt->type = Button;
17011         } else if(p = strstr(opt->name, " -save")) {
17012             opt->type = SaveButton;
17013         } else return FALSE;
17014         *p = 0; // terminate option name
17015         // now look if the command-line options define a setting for this engine option.
17016         if(cps->optionSettings && cps->optionSettings[0])
17017             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17018         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17019           snprintf(buf, MSG_SIZ, "option %s", p);
17020                 if(p = strstr(buf, ",")) *p = 0;
17021                 if(q = strchr(buf, '=')) switch(opt->type) {
17022                     case ComboBox:
17023                         for(n=0; n<opt->max; n++)
17024                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17025                         break;
17026                     case TextBox:
17027                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17028                         break;
17029                     case Spin:
17030                     case CheckBox:
17031                         opt->value = atoi(q+1);
17032                     default:
17033                         break;
17034                 }
17035                 strcat(buf, "\n");
17036                 SendToProgram(buf, cps);
17037         }
17038         return TRUE;
17039 }
17040
17041 void
17042 FeatureDone (ChessProgramState *cps, int val)
17043 {
17044   DelayedEventCallback cb = GetDelayedEvent();
17045   if ((cb == InitBackEnd3 && cps == &first) ||
17046       (cb == SettingsMenuIfReady && cps == &second) ||
17047       (cb == LoadEngine) ||
17048       (cb == TwoMachinesEventIfReady)) {
17049     CancelDelayedEvent();
17050     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17051   }
17052   cps->initDone = val;
17053   if(val) cps->reload = FALSE;
17054 }
17055
17056 /* Parse feature command from engine */
17057 void
17058 ParseFeatures (char *args, ChessProgramState *cps)
17059 {
17060   char *p = args;
17061   char *q = NULL;
17062   int val;
17063   char buf[MSG_SIZ];
17064
17065   for (;;) {
17066     while (*p == ' ') p++;
17067     if (*p == NULLCHAR) return;
17068
17069     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17070     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17071     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17072     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17073     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17074     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17075     if (BoolFeature(&p, "reuse", &val, cps)) {
17076       /* Engine can disable reuse, but can't enable it if user said no */
17077       if (!val) cps->reuse = FALSE;
17078       continue;
17079     }
17080     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17081     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17082       if (gameMode == TwoMachinesPlay) {
17083         DisplayTwoMachinesTitle();
17084       } else {
17085         DisplayTitle("");
17086       }
17087       continue;
17088     }
17089     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17090     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17091     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17092     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17093     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17094     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17095     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17096     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17097     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17098     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17099     if (IntFeature(&p, "done", &val, cps)) {
17100       FeatureDone(cps, val);
17101       continue;
17102     }
17103     /* Added by Tord: */
17104     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17105     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17106     /* End of additions by Tord */
17107
17108     /* [HGM] added features: */
17109     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17110     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17111     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17112     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17113     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17114     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17115     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17116     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17117         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17118         FREE(cps->option[cps->nrOptions].name);
17119         cps->option[cps->nrOptions].name = q; q = NULL;
17120         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17121           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17122             SendToProgram(buf, cps);
17123             continue;
17124         }
17125         if(cps->nrOptions >= MAX_OPTIONS) {
17126             cps->nrOptions--;
17127             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17128             DisplayError(buf, 0);
17129         }
17130         continue;
17131     }
17132     /* End of additions by HGM */
17133
17134     /* unknown feature: complain and skip */
17135     q = p;
17136     while (*q && *q != '=') q++;
17137     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17138     SendToProgram(buf, cps);
17139     p = q;
17140     if (*p == '=') {
17141       p++;
17142       if (*p == '\"') {
17143         p++;
17144         while (*p && *p != '\"') p++;
17145         if (*p == '\"') p++;
17146       } else {
17147         while (*p && *p != ' ') p++;
17148       }
17149     }
17150   }
17151
17152 }
17153
17154 void
17155 PeriodicUpdatesEvent (int newState)
17156 {
17157     if (newState == appData.periodicUpdates)
17158       return;
17159
17160     appData.periodicUpdates=newState;
17161
17162     /* Display type changes, so update it now */
17163 //    DisplayAnalysis();
17164
17165     /* Get the ball rolling again... */
17166     if (newState) {
17167         AnalysisPeriodicEvent(1);
17168         StartAnalysisClock();
17169     }
17170 }
17171
17172 void
17173 PonderNextMoveEvent (int newState)
17174 {
17175     if (newState == appData.ponderNextMove) return;
17176     if (gameMode == EditPosition) EditPositionDone(TRUE);
17177     if (newState) {
17178         SendToProgram("hard\n", &first);
17179         if (gameMode == TwoMachinesPlay) {
17180             SendToProgram("hard\n", &second);
17181         }
17182     } else {
17183         SendToProgram("easy\n", &first);
17184         thinkOutput[0] = NULLCHAR;
17185         if (gameMode == TwoMachinesPlay) {
17186             SendToProgram("easy\n", &second);
17187         }
17188     }
17189     appData.ponderNextMove = newState;
17190 }
17191
17192 void
17193 NewSettingEvent (int option, int *feature, char *command, int value)
17194 {
17195     char buf[MSG_SIZ];
17196
17197     if (gameMode == EditPosition) EditPositionDone(TRUE);
17198     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17199     if(feature == NULL || *feature) SendToProgram(buf, &first);
17200     if (gameMode == TwoMachinesPlay) {
17201         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17202     }
17203 }
17204
17205 void
17206 ShowThinkingEvent ()
17207 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17208 {
17209     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17210     int newState = appData.showThinking
17211         // [HGM] thinking: other features now need thinking output as well
17212         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17213
17214     if (oldState == newState) return;
17215     oldState = newState;
17216     if (gameMode == EditPosition) EditPositionDone(TRUE);
17217     if (oldState) {
17218         SendToProgram("post\n", &first);
17219         if (gameMode == TwoMachinesPlay) {
17220             SendToProgram("post\n", &second);
17221         }
17222     } else {
17223         SendToProgram("nopost\n", &first);
17224         thinkOutput[0] = NULLCHAR;
17225         if (gameMode == TwoMachinesPlay) {
17226             SendToProgram("nopost\n", &second);
17227         }
17228     }
17229 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17230 }
17231
17232 void
17233 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17234 {
17235   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17236   if (pr == NoProc) return;
17237   AskQuestion(title, question, replyPrefix, pr);
17238 }
17239
17240 void
17241 TypeInEvent (char firstChar)
17242 {
17243     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17244         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17245         gameMode == AnalyzeMode || gameMode == EditGame ||
17246         gameMode == EditPosition || gameMode == IcsExamining ||
17247         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17248         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17249                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17250                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17251         gameMode == Training) PopUpMoveDialog(firstChar);
17252 }
17253
17254 void
17255 TypeInDoneEvent (char *move)
17256 {
17257         Board board;
17258         int n, fromX, fromY, toX, toY;
17259         char promoChar;
17260         ChessMove moveType;
17261
17262         // [HGM] FENedit
17263         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17264                 EditPositionPasteFEN(move);
17265                 return;
17266         }
17267         // [HGM] movenum: allow move number to be typed in any mode
17268         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17269           ToNrEvent(2*n-1);
17270           return;
17271         }
17272         // undocumented kludge: allow command-line option to be typed in!
17273         // (potentially fatal, and does not implement the effect of the option.)
17274         // should only be used for options that are values on which future decisions will be made,
17275         // and definitely not on options that would be used during initialization.
17276         if(strstr(move, "!!! -") == move) {
17277             ParseArgsFromString(move+4);
17278             return;
17279         }
17280
17281       if (gameMode != EditGame && currentMove != forwardMostMove &&
17282         gameMode != Training) {
17283         DisplayMoveError(_("Displayed move is not current"));
17284       } else {
17285         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17286           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17287         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17288         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17289           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17290           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17291         } else {
17292           DisplayMoveError(_("Could not parse move"));
17293         }
17294       }
17295 }
17296
17297 void
17298 DisplayMove (int moveNumber)
17299 {
17300     char message[MSG_SIZ];
17301     char res[MSG_SIZ];
17302     char cpThinkOutput[MSG_SIZ];
17303
17304     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17305
17306     if (moveNumber == forwardMostMove - 1 ||
17307         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17308
17309         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17310
17311         if (strchr(cpThinkOutput, '\n')) {
17312             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17313         }
17314     } else {
17315         *cpThinkOutput = NULLCHAR;
17316     }
17317
17318     /* [AS] Hide thinking from human user */
17319     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17320         *cpThinkOutput = NULLCHAR;
17321         if( thinkOutput[0] != NULLCHAR ) {
17322             int i;
17323
17324             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17325                 cpThinkOutput[i] = '.';
17326             }
17327             cpThinkOutput[i] = NULLCHAR;
17328             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17329         }
17330     }
17331
17332     if (moveNumber == forwardMostMove - 1 &&
17333         gameInfo.resultDetails != NULL) {
17334         if (gameInfo.resultDetails[0] == NULLCHAR) {
17335           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17336         } else {
17337           snprintf(res, MSG_SIZ, " {%s} %s",
17338                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17339         }
17340     } else {
17341         res[0] = NULLCHAR;
17342     }
17343
17344     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17345         DisplayMessage(res, cpThinkOutput);
17346     } else {
17347       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17348                 WhiteOnMove(moveNumber) ? " " : ".. ",
17349                 parseList[moveNumber], res);
17350         DisplayMessage(message, cpThinkOutput);
17351     }
17352 }
17353
17354 void
17355 DisplayComment (int moveNumber, char *text)
17356 {
17357     char title[MSG_SIZ];
17358
17359     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17360       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17361     } else {
17362       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17363               WhiteOnMove(moveNumber) ? " " : ".. ",
17364               parseList[moveNumber]);
17365     }
17366     if (text != NULL && (appData.autoDisplayComment || commentUp))
17367         CommentPopUp(title, text);
17368 }
17369
17370 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17371  * might be busy thinking or pondering.  It can be omitted if your
17372  * gnuchess is configured to stop thinking immediately on any user
17373  * input.  However, that gnuchess feature depends on the FIONREAD
17374  * ioctl, which does not work properly on some flavors of Unix.
17375  */
17376 void
17377 Attention (ChessProgramState *cps)
17378 {
17379 #if ATTENTION
17380     if (!cps->useSigint) return;
17381     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17382     switch (gameMode) {
17383       case MachinePlaysWhite:
17384       case MachinePlaysBlack:
17385       case TwoMachinesPlay:
17386       case IcsPlayingWhite:
17387       case IcsPlayingBlack:
17388       case AnalyzeMode:
17389       case AnalyzeFile:
17390         /* Skip if we know it isn't thinking */
17391         if (!cps->maybeThinking) return;
17392         if (appData.debugMode)
17393           fprintf(debugFP, "Interrupting %s\n", cps->which);
17394         InterruptChildProcess(cps->pr);
17395         cps->maybeThinking = FALSE;
17396         break;
17397       default:
17398         break;
17399     }
17400 #endif /*ATTENTION*/
17401 }
17402
17403 int
17404 CheckFlags ()
17405 {
17406     if (whiteTimeRemaining <= 0) {
17407         if (!whiteFlag) {
17408             whiteFlag = TRUE;
17409             if (appData.icsActive) {
17410                 if (appData.autoCallFlag &&
17411                     gameMode == IcsPlayingBlack && !blackFlag) {
17412                   SendToICS(ics_prefix);
17413                   SendToICS("flag\n");
17414                 }
17415             } else {
17416                 if (blackFlag) {
17417                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17418                 } else {
17419                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17420                     if (appData.autoCallFlag) {
17421                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17422                         return TRUE;
17423                     }
17424                 }
17425             }
17426         }
17427     }
17428     if (blackTimeRemaining <= 0) {
17429         if (!blackFlag) {
17430             blackFlag = TRUE;
17431             if (appData.icsActive) {
17432                 if (appData.autoCallFlag &&
17433                     gameMode == IcsPlayingWhite && !whiteFlag) {
17434                   SendToICS(ics_prefix);
17435                   SendToICS("flag\n");
17436                 }
17437             } else {
17438                 if (whiteFlag) {
17439                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17440                 } else {
17441                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17442                     if (appData.autoCallFlag) {
17443                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17444                         return TRUE;
17445                     }
17446                 }
17447             }
17448         }
17449     }
17450     return FALSE;
17451 }
17452
17453 void
17454 CheckTimeControl ()
17455 {
17456     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17457         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17458
17459     /*
17460      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17461      */
17462     if ( !WhiteOnMove(forwardMostMove) ) {
17463         /* White made time control */
17464         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17465         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17466         /* [HGM] time odds: correct new time quota for time odds! */
17467                                             / WhitePlayer()->timeOdds;
17468         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17469     } else {
17470         lastBlack -= blackTimeRemaining;
17471         /* Black made time control */
17472         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17473                                             / WhitePlayer()->other->timeOdds;
17474         lastWhite = whiteTimeRemaining;
17475     }
17476 }
17477
17478 void
17479 DisplayBothClocks ()
17480 {
17481     int wom = gameMode == EditPosition ?
17482       !blackPlaysFirst : WhiteOnMove(currentMove);
17483     DisplayWhiteClock(whiteTimeRemaining, wom);
17484     DisplayBlackClock(blackTimeRemaining, !wom);
17485 }
17486
17487
17488 /* Timekeeping seems to be a portability nightmare.  I think everyone
17489    has ftime(), but I'm really not sure, so I'm including some ifdefs
17490    to use other calls if you don't.  Clocks will be less accurate if
17491    you have neither ftime nor gettimeofday.
17492 */
17493
17494 /* VS 2008 requires the #include outside of the function */
17495 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17496 #include <sys/timeb.h>
17497 #endif
17498
17499 /* Get the current time as a TimeMark */
17500 void
17501 GetTimeMark (TimeMark *tm)
17502 {
17503 #if HAVE_GETTIMEOFDAY
17504
17505     struct timeval timeVal;
17506     struct timezone timeZone;
17507
17508     gettimeofday(&timeVal, &timeZone);
17509     tm->sec = (long) timeVal.tv_sec;
17510     tm->ms = (int) (timeVal.tv_usec / 1000L);
17511
17512 #else /*!HAVE_GETTIMEOFDAY*/
17513 #if HAVE_FTIME
17514
17515 // include <sys/timeb.h> / moved to just above start of function
17516     struct timeb timeB;
17517
17518     ftime(&timeB);
17519     tm->sec = (long) timeB.time;
17520     tm->ms = (int) timeB.millitm;
17521
17522 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17523     tm->sec = (long) time(NULL);
17524     tm->ms = 0;
17525 #endif
17526 #endif
17527 }
17528
17529 /* Return the difference in milliseconds between two
17530    time marks.  We assume the difference will fit in a long!
17531 */
17532 long
17533 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17534 {
17535     return 1000L*(tm2->sec - tm1->sec) +
17536            (long) (tm2->ms - tm1->ms);
17537 }
17538
17539
17540 /*
17541  * Code to manage the game clocks.
17542  *
17543  * In tournament play, black starts the clock and then white makes a move.
17544  * We give the human user a slight advantage if he is playing white---the
17545  * clocks don't run until he makes his first move, so it takes zero time.
17546  * Also, we don't account for network lag, so we could get out of sync
17547  * with GNU Chess's clock -- but then, referees are always right.
17548  */
17549
17550 static TimeMark tickStartTM;
17551 static long intendedTickLength;
17552
17553 long
17554 NextTickLength (long timeRemaining)
17555 {
17556     long nominalTickLength, nextTickLength;
17557
17558     if (timeRemaining > 0L && timeRemaining <= 10000L)
17559       nominalTickLength = 100L;
17560     else
17561       nominalTickLength = 1000L;
17562     nextTickLength = timeRemaining % nominalTickLength;
17563     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17564
17565     return nextTickLength;
17566 }
17567
17568 /* Adjust clock one minute up or down */
17569 void
17570 AdjustClock (Boolean which, int dir)
17571 {
17572     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17573     if(which) blackTimeRemaining += 60000*dir;
17574     else      whiteTimeRemaining += 60000*dir;
17575     DisplayBothClocks();
17576     adjustedClock = TRUE;
17577 }
17578
17579 /* Stop clocks and reset to a fresh time control */
17580 void
17581 ResetClocks ()
17582 {
17583     (void) StopClockTimer();
17584     if (appData.icsActive) {
17585         whiteTimeRemaining = blackTimeRemaining = 0;
17586     } else if (searchTime) {
17587         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17588         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17589     } else { /* [HGM] correct new time quote for time odds */
17590         whiteTC = blackTC = fullTimeControlString;
17591         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17592         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17593     }
17594     if (whiteFlag || blackFlag) {
17595         DisplayTitle("");
17596         whiteFlag = blackFlag = FALSE;
17597     }
17598     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17599     DisplayBothClocks();
17600     adjustedClock = FALSE;
17601 }
17602
17603 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17604
17605 /* Decrement running clock by amount of time that has passed */
17606 void
17607 DecrementClocks ()
17608 {
17609     long timeRemaining;
17610     long lastTickLength, fudge;
17611     TimeMark now;
17612
17613     if (!appData.clockMode) return;
17614     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17615
17616     GetTimeMark(&now);
17617
17618     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17619
17620     /* Fudge if we woke up a little too soon */
17621     fudge = intendedTickLength - lastTickLength;
17622     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17623
17624     if (WhiteOnMove(forwardMostMove)) {
17625         if(whiteNPS >= 0) lastTickLength = 0;
17626         timeRemaining = whiteTimeRemaining -= lastTickLength;
17627         if(timeRemaining < 0 && !appData.icsActive) {
17628             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17629             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17630                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17631                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17632             }
17633         }
17634         DisplayWhiteClock(whiteTimeRemaining - fudge,
17635                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17636     } else {
17637         if(blackNPS >= 0) lastTickLength = 0;
17638         timeRemaining = blackTimeRemaining -= lastTickLength;
17639         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17640             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17641             if(suddenDeath) {
17642                 blackStartMove = forwardMostMove;
17643                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17644             }
17645         }
17646         DisplayBlackClock(blackTimeRemaining - fudge,
17647                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17648     }
17649     if (CheckFlags()) return;
17650
17651     if(twoBoards) { // count down secondary board's clocks as well
17652         activePartnerTime -= lastTickLength;
17653         partnerUp = 1;
17654         if(activePartner == 'W')
17655             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17656         else
17657             DisplayBlackClock(activePartnerTime, TRUE);
17658         partnerUp = 0;
17659     }
17660
17661     tickStartTM = now;
17662     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17663     StartClockTimer(intendedTickLength);
17664
17665     /* if the time remaining has fallen below the alarm threshold, sound the
17666      * alarm. if the alarm has sounded and (due to a takeback or time control
17667      * with increment) the time remaining has increased to a level above the
17668      * threshold, reset the alarm so it can sound again.
17669      */
17670
17671     if (appData.icsActive && appData.icsAlarm) {
17672
17673         /* make sure we are dealing with the user's clock */
17674         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17675                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17676            )) return;
17677
17678         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17679             alarmSounded = FALSE;
17680         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17681             PlayAlarmSound();
17682             alarmSounded = TRUE;
17683         }
17684     }
17685 }
17686
17687
17688 /* A player has just moved, so stop the previously running
17689    clock and (if in clock mode) start the other one.
17690    We redisplay both clocks in case we're in ICS mode, because
17691    ICS gives us an update to both clocks after every move.
17692    Note that this routine is called *after* forwardMostMove
17693    is updated, so the last fractional tick must be subtracted
17694    from the color that is *not* on move now.
17695 */
17696 void
17697 SwitchClocks (int newMoveNr)
17698 {
17699     long lastTickLength;
17700     TimeMark now;
17701     int flagged = FALSE;
17702
17703     GetTimeMark(&now);
17704
17705     if (StopClockTimer() && appData.clockMode) {
17706         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17707         if (!WhiteOnMove(forwardMostMove)) {
17708             if(blackNPS >= 0) lastTickLength = 0;
17709             blackTimeRemaining -= lastTickLength;
17710            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17711 //         if(pvInfoList[forwardMostMove].time == -1)
17712                  pvInfoList[forwardMostMove].time =               // use GUI time
17713                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17714         } else {
17715            if(whiteNPS >= 0) lastTickLength = 0;
17716            whiteTimeRemaining -= lastTickLength;
17717            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17718 //         if(pvInfoList[forwardMostMove].time == -1)
17719                  pvInfoList[forwardMostMove].time =
17720                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17721         }
17722         flagged = CheckFlags();
17723     }
17724     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17725     CheckTimeControl();
17726
17727     if (flagged || !appData.clockMode) return;
17728
17729     switch (gameMode) {
17730       case MachinePlaysBlack:
17731       case MachinePlaysWhite:
17732       case BeginningOfGame:
17733         if (pausing) return;
17734         break;
17735
17736       case EditGame:
17737       case PlayFromGameFile:
17738       case IcsExamining:
17739         return;
17740
17741       default:
17742         break;
17743     }
17744
17745     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17746         if(WhiteOnMove(forwardMostMove))
17747              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17748         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17749     }
17750
17751     tickStartTM = now;
17752     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17753       whiteTimeRemaining : blackTimeRemaining);
17754     StartClockTimer(intendedTickLength);
17755 }
17756
17757
17758 /* Stop both clocks */
17759 void
17760 StopClocks ()
17761 {
17762     long lastTickLength;
17763     TimeMark now;
17764
17765     if (!StopClockTimer()) return;
17766     if (!appData.clockMode) return;
17767
17768     GetTimeMark(&now);
17769
17770     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17771     if (WhiteOnMove(forwardMostMove)) {
17772         if(whiteNPS >= 0) lastTickLength = 0;
17773         whiteTimeRemaining -= lastTickLength;
17774         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17775     } else {
17776         if(blackNPS >= 0) lastTickLength = 0;
17777         blackTimeRemaining -= lastTickLength;
17778         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17779     }
17780     CheckFlags();
17781 }
17782
17783 /* Start clock of player on move.  Time may have been reset, so
17784    if clock is already running, stop and restart it. */
17785 void
17786 StartClocks ()
17787 {
17788     (void) StopClockTimer(); /* in case it was running already */
17789     DisplayBothClocks();
17790     if (CheckFlags()) return;
17791
17792     if (!appData.clockMode) return;
17793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17794
17795     GetTimeMark(&tickStartTM);
17796     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17797       whiteTimeRemaining : blackTimeRemaining);
17798
17799    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17800     whiteNPS = blackNPS = -1;
17801     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17802        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17803         whiteNPS = first.nps;
17804     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17805        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17806         blackNPS = first.nps;
17807     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17808         whiteNPS = second.nps;
17809     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17810         blackNPS = second.nps;
17811     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17812
17813     StartClockTimer(intendedTickLength);
17814 }
17815
17816 char *
17817 TimeString (long ms)
17818 {
17819     long second, minute, hour, day;
17820     char *sign = "";
17821     static char buf[32];
17822
17823     if (ms > 0 && ms <= 9900) {
17824       /* convert milliseconds to tenths, rounding up */
17825       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17826
17827       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17828       return buf;
17829     }
17830
17831     /* convert milliseconds to seconds, rounding up */
17832     /* use floating point to avoid strangeness of integer division
17833        with negative dividends on many machines */
17834     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17835
17836     if (second < 0) {
17837         sign = "-";
17838         second = -second;
17839     }
17840
17841     day = second / (60 * 60 * 24);
17842     second = second % (60 * 60 * 24);
17843     hour = second / (60 * 60);
17844     second = second % (60 * 60);
17845     minute = second / 60;
17846     second = second % 60;
17847
17848     if (day > 0)
17849       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17850               sign, day, hour, minute, second);
17851     else if (hour > 0)
17852       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17853     else
17854       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17855
17856     return buf;
17857 }
17858
17859
17860 /*
17861  * This is necessary because some C libraries aren't ANSI C compliant yet.
17862  */
17863 char *
17864 StrStr (char *string, char *match)
17865 {
17866     int i, length;
17867
17868     length = strlen(match);
17869
17870     for (i = strlen(string) - length; i >= 0; i--, string++)
17871       if (!strncmp(match, string, length))
17872         return string;
17873
17874     return NULL;
17875 }
17876
17877 char *
17878 StrCaseStr (char *string, char *match)
17879 {
17880     int i, j, length;
17881
17882     length = strlen(match);
17883
17884     for (i = strlen(string) - length; i >= 0; i--, string++) {
17885         for (j = 0; j < length; j++) {
17886             if (ToLower(match[j]) != ToLower(string[j]))
17887               break;
17888         }
17889         if (j == length) return string;
17890     }
17891
17892     return NULL;
17893 }
17894
17895 #ifndef _amigados
17896 int
17897 StrCaseCmp (char *s1, char *s2)
17898 {
17899     char c1, c2;
17900
17901     for (;;) {
17902         c1 = ToLower(*s1++);
17903         c2 = ToLower(*s2++);
17904         if (c1 > c2) return 1;
17905         if (c1 < c2) return -1;
17906         if (c1 == NULLCHAR) return 0;
17907     }
17908 }
17909
17910
17911 int
17912 ToLower (int c)
17913 {
17914     return isupper(c) ? tolower(c) : c;
17915 }
17916
17917
17918 int
17919 ToUpper (int c)
17920 {
17921     return islower(c) ? toupper(c) : c;
17922 }
17923 #endif /* !_amigados    */
17924
17925 char *
17926 StrSave (char *s)
17927 {
17928   char *ret;
17929
17930   if ((ret = (char *) malloc(strlen(s) + 1)))
17931     {
17932       safeStrCpy(ret, s, strlen(s)+1);
17933     }
17934   return ret;
17935 }
17936
17937 char *
17938 StrSavePtr (char *s, char **savePtr)
17939 {
17940     if (*savePtr) {
17941         free(*savePtr);
17942     }
17943     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17944       safeStrCpy(*savePtr, s, strlen(s)+1);
17945     }
17946     return(*savePtr);
17947 }
17948
17949 char *
17950 PGNDate ()
17951 {
17952     time_t clock;
17953     struct tm *tm;
17954     char buf[MSG_SIZ];
17955
17956     clock = time((time_t *)NULL);
17957     tm = localtime(&clock);
17958     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17959             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17960     return StrSave(buf);
17961 }
17962
17963
17964 char *
17965 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17966 {
17967     int i, j, fromX, fromY, toX, toY;
17968     int whiteToPlay, haveRights = nrCastlingRights;
17969     char buf[MSG_SIZ];
17970     char *p, *q;
17971     int emptycount;
17972     ChessSquare piece;
17973
17974     whiteToPlay = (gameMode == EditPosition) ?
17975       !blackPlaysFirst : (move % 2 == 0);
17976     p = buf;
17977
17978     /* Piece placement data */
17979     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17980         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17981         emptycount = 0;
17982         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17983             if (boards[move][i][j] == EmptySquare) {
17984                 emptycount++;
17985             } else { ChessSquare piece = boards[move][i][j];
17986                 if (emptycount > 0) {
17987                     if(emptycount<10) /* [HGM] can be >= 10 */
17988                         *p++ = '0' + emptycount;
17989                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17990                     emptycount = 0;
17991                 }
17992                 if(PieceToChar(piece) == '+') {
17993                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17994                     *p++ = '+';
17995                     piece = (ChessSquare)(CHUDEMOTED(piece));
17996                 }
17997                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17998                 if(*p = PieceSuffix(piece)) p++;
17999                 if(p[-1] == '~') {
18000                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18001                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18002                     *p++ = '~';
18003                 }
18004             }
18005         }
18006         if (emptycount > 0) {
18007             if(emptycount<10) /* [HGM] can be >= 10 */
18008                 *p++ = '0' + emptycount;
18009             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18010             emptycount = 0;
18011         }
18012         *p++ = '/';
18013     }
18014     *(p - 1) = ' ';
18015
18016     /* [HGM] print Crazyhouse or Shogi holdings */
18017     if( gameInfo.holdingsWidth ) {
18018         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18019         q = p;
18020         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18021             piece = boards[move][i][BOARD_WIDTH-1];
18022             if( piece != EmptySquare )
18023               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18024                   *p++ = PieceToChar(piece);
18025         }
18026         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18027             piece = boards[move][BOARD_HEIGHT-i-1][0];
18028             if( piece != EmptySquare )
18029               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18030                   *p++ = PieceToChar(piece);
18031         }
18032
18033         if( q == p ) *p++ = '-';
18034         *p++ = ']';
18035         *p++ = ' ';
18036     }
18037
18038     /* Active color */
18039     *p++ = whiteToPlay ? 'w' : 'b';
18040     *p++ = ' ';
18041
18042   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18043     haveRights = 0; q = p;
18044     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18045       piece = boards[move][0][i];
18046       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18047         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18048       }
18049     }
18050     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18051       piece = boards[move][BOARD_HEIGHT-1][i];
18052       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18053         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18054       }
18055     }
18056     if(p == q) *p++ = '-';
18057     *p++ = ' ';
18058   }
18059
18060   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18061     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18062   } else {
18063   if(haveRights) {
18064      int handW=0, handB=0;
18065      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18066         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18067         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18068      }
18069      q = p;
18070      if(appData.fischerCastling) {
18071         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18072            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18073                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18074         } else {
18075        /* [HGM] write directly from rights */
18076            if(boards[move][CASTLING][2] != NoRights &&
18077               boards[move][CASTLING][0] != NoRights   )
18078                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18079            if(boards[move][CASTLING][2] != NoRights &&
18080               boards[move][CASTLING][1] != NoRights   )
18081                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18082         }
18083         if(handB) {
18084            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18085                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18086         } else {
18087            if(boards[move][CASTLING][5] != NoRights &&
18088               boards[move][CASTLING][3] != NoRights   )
18089                 *p++ = boards[move][CASTLING][3] + AAA;
18090            if(boards[move][CASTLING][5] != NoRights &&
18091               boards[move][CASTLING][4] != NoRights   )
18092                 *p++ = boards[move][CASTLING][4] + AAA;
18093         }
18094      } else {
18095
18096         /* [HGM] write true castling rights */
18097         if( nrCastlingRights == 6 ) {
18098             int q, k=0;
18099             if(boards[move][CASTLING][0] != NoRights &&
18100                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18101             q = (boards[move][CASTLING][1] != NoRights &&
18102                  boards[move][CASTLING][2] != NoRights  );
18103             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18104                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18105                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18106                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18107             }
18108             if(q) *p++ = 'Q';
18109             k = 0;
18110             if(boards[move][CASTLING][3] != NoRights &&
18111                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18112             q = (boards[move][CASTLING][4] != NoRights &&
18113                  boards[move][CASTLING][5] != NoRights  );
18114             if(handB) {
18115                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18116                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18117                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18118             }
18119             if(q) *p++ = 'q';
18120         }
18121      }
18122      if (q == p) *p++ = '-'; /* No castling rights */
18123      *p++ = ' ';
18124   }
18125
18126   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18127      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18128      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18129     /* En passant target square */
18130     if (move > backwardMostMove) {
18131         fromX = moveList[move - 1][0] - AAA;
18132         fromY = moveList[move - 1][1] - ONE;
18133         toX = moveList[move - 1][2] - AAA;
18134         toY = moveList[move - 1][3] - ONE;
18135         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18136             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18137             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18138             fromX == toX) {
18139             /* 2-square pawn move just happened */
18140             *p++ = toX + AAA;
18141             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18142         } else {
18143             *p++ = '-';
18144         }
18145     } else if(move == backwardMostMove) {
18146         // [HGM] perhaps we should always do it like this, and forget the above?
18147         if((signed char)boards[move][EP_STATUS] >= 0) {
18148             *p++ = boards[move][EP_STATUS] + AAA;
18149             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18150         } else {
18151             *p++ = '-';
18152         }
18153     } else {
18154         *p++ = '-';
18155     }
18156     *p++ = ' ';
18157   }
18158   }
18159
18160     if(moveCounts)
18161     {   int i = 0, j=move;
18162
18163         /* [HGM] find reversible plies */
18164         if (appData.debugMode) { int k;
18165             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18166             for(k=backwardMostMove; k<=forwardMostMove; k++)
18167                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18168
18169         }
18170
18171         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18172         if( j == backwardMostMove ) i += initialRulePlies;
18173         sprintf(p, "%d ", i);
18174         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18175
18176         /* Fullmove number */
18177         sprintf(p, "%d", (move / 2) + 1);
18178     } else *--p = NULLCHAR;
18179
18180     return StrSave(buf);
18181 }
18182
18183 Boolean
18184 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18185 {
18186     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18187     char *p, c;
18188     int emptycount, virgin[BOARD_FILES];
18189     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18190
18191     p = fen;
18192
18193     /* Piece placement data */
18194     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18195         j = 0;
18196         for (;;) {
18197             if (*p == '/' || *p == ' ' || *p == '[' ) {
18198                 if(j > w) w = j;
18199                 emptycount = gameInfo.boardWidth - j;
18200                 while (emptycount--)
18201                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18202                 if (*p == '/') p++;
18203                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18204                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18205                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18206                     }
18207                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18208                 }
18209                 break;
18210 #if(BOARD_FILES >= 10)*0
18211             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18212                 p++; emptycount=10;
18213                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18214                 while (emptycount--)
18215                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18216 #endif
18217             } else if (*p == '*') {
18218                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18219             } else if (isdigit(*p)) {
18220                 emptycount = *p++ - '0';
18221                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18222                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18223                 while (emptycount--)
18224                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18225             } else if (*p == '<') {
18226                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18227                 else if (i != 0 || !shuffle) return FALSE;
18228                 p++;
18229             } else if (shuffle && *p == '>') {
18230                 p++; // for now ignore closing shuffle range, and assume rank-end
18231             } else if (*p == '?') {
18232                 if (j >= gameInfo.boardWidth) return FALSE;
18233                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18234                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18235             } else if (*p == '+' || isalpha(*p)) {
18236                 char *q, *s = SUFFIXES;
18237                 if (j >= gameInfo.boardWidth) return FALSE;
18238                 if(*p=='+') {
18239                     char c = *++p;
18240                     if(q = strchr(s, p[1])) p++;
18241                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18242                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18243                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18244                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18245                 } else {
18246                     char c = *p++;
18247                     if(q = strchr(s, *p)) p++;
18248                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18249                 }
18250
18251                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18252                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18253                     piece = (ChessSquare) (PROMOTED(piece));
18254                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18255                     p++;
18256                 }
18257                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18258                 if(piece == king) wKingRank = i;
18259                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18260             } else {
18261                 return FALSE;
18262             }
18263         }
18264     }
18265     while (*p == '/' || *p == ' ') p++;
18266
18267     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18268
18269     /* [HGM] by default clear Crazyhouse holdings, if present */
18270     if(gameInfo.holdingsWidth) {
18271        for(i=0; i<BOARD_HEIGHT; i++) {
18272            board[i][0]             = EmptySquare; /* black holdings */
18273            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18274            board[i][1]             = (ChessSquare) 0; /* black counts */
18275            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18276        }
18277     }
18278
18279     /* [HGM] look for Crazyhouse holdings here */
18280     while(*p==' ') p++;
18281     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18282         int swap=0, wcnt=0, bcnt=0;
18283         if(*p == '[') p++;
18284         if(*p == '<') swap++, p++;
18285         if(*p == '-' ) p++; /* empty holdings */ else {
18286             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18287             /* if we would allow FEN reading to set board size, we would   */
18288             /* have to add holdings and shift the board read so far here   */
18289             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18290                 p++;
18291                 if((int) piece >= (int) BlackPawn ) {
18292                     i = (int)piece - (int)BlackPawn;
18293                     i = PieceToNumber((ChessSquare)i);
18294                     if( i >= gameInfo.holdingsSize ) return FALSE;
18295                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18296                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18297                     bcnt++;
18298                 } else {
18299                     i = (int)piece - (int)WhitePawn;
18300                     i = PieceToNumber((ChessSquare)i);
18301                     if( i >= gameInfo.holdingsSize ) return FALSE;
18302                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18303                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18304                     wcnt++;
18305                 }
18306             }
18307             if(subst) { // substitute back-rank question marks by holdings pieces
18308                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18309                     int k, m, n = bcnt + 1;
18310                     if(board[0][j] == ClearBoard) {
18311                         if(!wcnt) return FALSE;
18312                         n = rand() % wcnt;
18313                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18314                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18315                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18316                             break;
18317                         }
18318                     }
18319                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18320                         if(!bcnt) return FALSE;
18321                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18322                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18323                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18324                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18325                             break;
18326                         }
18327                     }
18328                 }
18329                 subst = 0;
18330             }
18331         }
18332         if(*p == ']') p++;
18333     }
18334
18335     if(subst) return FALSE; // substitution requested, but no holdings
18336
18337     while(*p == ' ') p++;
18338
18339     /* Active color */
18340     c = *p++;
18341     if(appData.colorNickNames) {
18342       if( c == appData.colorNickNames[0] ) c = 'w'; else
18343       if( c == appData.colorNickNames[1] ) c = 'b';
18344     }
18345     switch (c) {
18346       case 'w':
18347         *blackPlaysFirst = FALSE;
18348         break;
18349       case 'b':
18350         *blackPlaysFirst = TRUE;
18351         break;
18352       default:
18353         return FALSE;
18354     }
18355
18356     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18357     /* return the extra info in global variiables             */
18358
18359     while(*p==' ') p++;
18360
18361     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18362         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18363         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18364     }
18365
18366     /* set defaults in case FEN is incomplete */
18367     board[EP_STATUS] = EP_UNKNOWN;
18368     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18369     for(i=0; i<nrCastlingRights; i++ ) {
18370         board[CASTLING][i] =
18371             appData.fischerCastling ? NoRights : initialRights[i];
18372     }   /* assume possible unless obviously impossible */
18373     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18374     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18375     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18376                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18377     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18378     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18379     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18380                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18381     FENrulePlies = 0;
18382
18383     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18384       char *q = p;
18385       int w=0, b=0;
18386       while(isalpha(*p)) {
18387         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18388         if(islower(*p)) b |= 1 << (*p++ - 'a');
18389       }
18390       if(*p == '-') p++;
18391       if(p != q) {
18392         board[TOUCHED_W] = ~w;
18393         board[TOUCHED_B] = ~b;
18394         while(*p == ' ') p++;
18395       }
18396     } else
18397
18398     if(nrCastlingRights) {
18399       int fischer = 0;
18400       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18401       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18402           /* castling indicator present, so default becomes no castlings */
18403           for(i=0; i<nrCastlingRights; i++ ) {
18404                  board[CASTLING][i] = NoRights;
18405           }
18406       }
18407       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18408              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18409              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18410              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18411         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18412
18413         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18414             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18415             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18416         }
18417         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18418             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18419         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18420                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18421         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18422                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18423         switch(c) {
18424           case'K':
18425               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18426               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18427               board[CASTLING][2] = whiteKingFile;
18428               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18429               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18430               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18431               break;
18432           case'Q':
18433               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18434               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18435               board[CASTLING][2] = whiteKingFile;
18436               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18437               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18438               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18439               break;
18440           case'k':
18441               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18442               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18443               board[CASTLING][5] = blackKingFile;
18444               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18445               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18446               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18447               break;
18448           case'q':
18449               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18450               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18451               board[CASTLING][5] = blackKingFile;
18452               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18453               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18454               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18455           case '-':
18456               break;
18457           default: /* FRC castlings */
18458               if(c >= 'a') { /* black rights */
18459                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18460                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18461                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18462                   if(i == BOARD_RGHT) break;
18463                   board[CASTLING][5] = i;
18464                   c -= AAA;
18465                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18466                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18467                   if(c > i)
18468                       board[CASTLING][3] = c;
18469                   else
18470                       board[CASTLING][4] = c;
18471               } else { /* white rights */
18472                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18473                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18474                     if(board[0][i] == WhiteKing) break;
18475                   if(i == BOARD_RGHT) break;
18476                   board[CASTLING][2] = i;
18477                   c -= AAA - 'a' + 'A';
18478                   if(board[0][c] >= WhiteKing) break;
18479                   if(c > i)
18480                       board[CASTLING][0] = c;
18481                   else
18482                       board[CASTLING][1] = c;
18483               }
18484         }
18485       }
18486       for(i=0; i<nrCastlingRights; i++)
18487         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18488       if(gameInfo.variant == VariantSChess)
18489         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18490       if(fischer && shuffle) appData.fischerCastling = TRUE;
18491     if (appData.debugMode) {
18492         fprintf(debugFP, "FEN castling rights:");
18493         for(i=0; i<nrCastlingRights; i++)
18494         fprintf(debugFP, " %d", board[CASTLING][i]);
18495         fprintf(debugFP, "\n");
18496     }
18497
18498       while(*p==' ') p++;
18499     }
18500
18501     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18502
18503     /* read e.p. field in games that know e.p. capture */
18504     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18505        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18506        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18507       if(*p=='-') {
18508         p++; board[EP_STATUS] = EP_NONE;
18509       } else {
18510          char c = *p++ - AAA;
18511
18512          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18513          if(*p >= '0' && *p <='9') p++;
18514          board[EP_STATUS] = c;
18515       }
18516     }
18517
18518
18519     if(sscanf(p, "%d", &i) == 1) {
18520         FENrulePlies = i; /* 50-move ply counter */
18521         /* (The move number is still ignored)    */
18522     }
18523
18524     return TRUE;
18525 }
18526
18527 void
18528 EditPositionPasteFEN (char *fen)
18529 {
18530   if (fen != NULL) {
18531     Board initial_position;
18532
18533     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18534       DisplayError(_("Bad FEN position in clipboard"), 0);
18535       return ;
18536     } else {
18537       int savedBlackPlaysFirst = blackPlaysFirst;
18538       EditPositionEvent();
18539       blackPlaysFirst = savedBlackPlaysFirst;
18540       CopyBoard(boards[0], initial_position);
18541       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18542       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18543       DisplayBothClocks();
18544       DrawPosition(FALSE, boards[currentMove]);
18545     }
18546   }
18547 }
18548
18549 static char cseq[12] = "\\   ";
18550
18551 Boolean
18552 set_cont_sequence (char *new_seq)
18553 {
18554     int len;
18555     Boolean ret;
18556
18557     // handle bad attempts to set the sequence
18558         if (!new_seq)
18559                 return 0; // acceptable error - no debug
18560
18561     len = strlen(new_seq);
18562     ret = (len > 0) && (len < sizeof(cseq));
18563     if (ret)
18564       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18565     else if (appData.debugMode)
18566       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18567     return ret;
18568 }
18569
18570 /*
18571     reformat a source message so words don't cross the width boundary.  internal
18572     newlines are not removed.  returns the wrapped size (no null character unless
18573     included in source message).  If dest is NULL, only calculate the size required
18574     for the dest buffer.  lp argument indicats line position upon entry, and it's
18575     passed back upon exit.
18576 */
18577 int
18578 wrap (char *dest, char *src, int count, int width, int *lp)
18579 {
18580     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18581
18582     cseq_len = strlen(cseq);
18583     old_line = line = *lp;
18584     ansi = len = clen = 0;
18585
18586     for (i=0; i < count; i++)
18587     {
18588         if (src[i] == '\033')
18589             ansi = 1;
18590
18591         // if we hit the width, back up
18592         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18593         {
18594             // store i & len in case the word is too long
18595             old_i = i, old_len = len;
18596
18597             // find the end of the last word
18598             while (i && src[i] != ' ' && src[i] != '\n')
18599             {
18600                 i--;
18601                 len--;
18602             }
18603
18604             // word too long?  restore i & len before splitting it
18605             if ((old_i-i+clen) >= width)
18606             {
18607                 i = old_i;
18608                 len = old_len;
18609             }
18610
18611             // extra space?
18612             if (i && src[i-1] == ' ')
18613                 len--;
18614
18615             if (src[i] != ' ' && src[i] != '\n')
18616             {
18617                 i--;
18618                 if (len)
18619                     len--;
18620             }
18621
18622             // now append the newline and continuation sequence
18623             if (dest)
18624                 dest[len] = '\n';
18625             len++;
18626             if (dest)
18627                 strncpy(dest+len, cseq, cseq_len);
18628             len += cseq_len;
18629             line = cseq_len;
18630             clen = cseq_len;
18631             continue;
18632         }
18633
18634         if (dest)
18635             dest[len] = src[i];
18636         len++;
18637         if (!ansi)
18638             line++;
18639         if (src[i] == '\n')
18640             line = 0;
18641         if (src[i] == 'm')
18642             ansi = 0;
18643     }
18644     if (dest && appData.debugMode)
18645     {
18646         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18647             count, width, line, len, *lp);
18648         show_bytes(debugFP, src, count);
18649         fprintf(debugFP, "\ndest: ");
18650         show_bytes(debugFP, dest, len);
18651         fprintf(debugFP, "\n");
18652     }
18653     *lp = dest ? line : old_line;
18654
18655     return len;
18656 }
18657
18658 // [HGM] vari: routines for shelving variations
18659 Boolean modeRestore = FALSE;
18660
18661 void
18662 PushInner (int firstMove, int lastMove)
18663 {
18664         int i, j, nrMoves = lastMove - firstMove;
18665
18666         // push current tail of game on stack
18667         savedResult[storedGames] = gameInfo.result;
18668         savedDetails[storedGames] = gameInfo.resultDetails;
18669         gameInfo.resultDetails = NULL;
18670         savedFirst[storedGames] = firstMove;
18671         savedLast [storedGames] = lastMove;
18672         savedFramePtr[storedGames] = framePtr;
18673         framePtr -= nrMoves; // reserve space for the boards
18674         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18675             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18676             for(j=0; j<MOVE_LEN; j++)
18677                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18678             for(j=0; j<2*MOVE_LEN; j++)
18679                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18680             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18681             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18682             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18683             pvInfoList[firstMove+i-1].depth = 0;
18684             commentList[framePtr+i] = commentList[firstMove+i];
18685             commentList[firstMove+i] = NULL;
18686         }
18687
18688         storedGames++;
18689         forwardMostMove = firstMove; // truncate game so we can start variation
18690 }
18691
18692 void
18693 PushTail (int firstMove, int lastMove)
18694 {
18695         if(appData.icsActive) { // only in local mode
18696                 forwardMostMove = currentMove; // mimic old ICS behavior
18697                 return;
18698         }
18699         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18700
18701         PushInner(firstMove, lastMove);
18702         if(storedGames == 1) GreyRevert(FALSE);
18703         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18704 }
18705
18706 void
18707 PopInner (Boolean annotate)
18708 {
18709         int i, j, nrMoves;
18710         char buf[8000], moveBuf[20];
18711
18712         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18713         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18714         nrMoves = savedLast[storedGames] - currentMove;
18715         if(annotate) {
18716                 int cnt = 10;
18717                 if(!WhiteOnMove(currentMove))
18718                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18719                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18720                 for(i=currentMove; i<forwardMostMove; i++) {
18721                         if(WhiteOnMove(i))
18722                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18723                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18724                         strcat(buf, moveBuf);
18725                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18726                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18727                 }
18728                 strcat(buf, ")");
18729         }
18730         for(i=1; i<=nrMoves; i++) { // copy last variation back
18731             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18732             for(j=0; j<MOVE_LEN; j++)
18733                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18734             for(j=0; j<2*MOVE_LEN; j++)
18735                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18736             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18737             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18738             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18739             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18740             commentList[currentMove+i] = commentList[framePtr+i];
18741             commentList[framePtr+i] = NULL;
18742         }
18743         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18744         framePtr = savedFramePtr[storedGames];
18745         gameInfo.result = savedResult[storedGames];
18746         if(gameInfo.resultDetails != NULL) {
18747             free(gameInfo.resultDetails);
18748       }
18749         gameInfo.resultDetails = savedDetails[storedGames];
18750         forwardMostMove = currentMove + nrMoves;
18751 }
18752
18753 Boolean
18754 PopTail (Boolean annotate)
18755 {
18756         if(appData.icsActive) return FALSE; // only in local mode
18757         if(!storedGames) return FALSE; // sanity
18758         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18759
18760         PopInner(annotate);
18761         if(currentMove < forwardMostMove) ForwardEvent(); else
18762         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18763
18764         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18765         return TRUE;
18766 }
18767
18768 void
18769 CleanupTail ()
18770 {       // remove all shelved variations
18771         int i;
18772         for(i=0; i<storedGames; i++) {
18773             if(savedDetails[i])
18774                 free(savedDetails[i]);
18775             savedDetails[i] = NULL;
18776         }
18777         for(i=framePtr; i<MAX_MOVES; i++) {
18778                 if(commentList[i]) free(commentList[i]);
18779                 commentList[i] = NULL;
18780         }
18781         framePtr = MAX_MOVES-1;
18782         storedGames = 0;
18783 }
18784
18785 void
18786 LoadVariation (int index, char *text)
18787 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18788         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18789         int level = 0, move;
18790
18791         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18792         // first find outermost bracketing variation
18793         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18794             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18795                 if(*p == '{') wait = '}'; else
18796                 if(*p == '[') wait = ']'; else
18797                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18798                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18799             }
18800             if(*p == wait) wait = NULLCHAR; // closing ]} found
18801             p++;
18802         }
18803         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18804         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18805         end[1] = NULLCHAR; // clip off comment beyond variation
18806         ToNrEvent(currentMove-1);
18807         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18808         // kludge: use ParsePV() to append variation to game
18809         move = currentMove;
18810         ParsePV(start, TRUE, TRUE);
18811         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18812         ClearPremoveHighlights();
18813         CommentPopDown();
18814         ToNrEvent(currentMove+1);
18815 }
18816
18817 void
18818 LoadTheme ()
18819 {
18820     char *p, *q, buf[MSG_SIZ];
18821     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18822         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18823         ParseArgsFromString(buf);
18824         ActivateTheme(TRUE); // also redo colors
18825         return;
18826     }
18827     p = nickName;
18828     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18829     {
18830         int len;
18831         q = appData.themeNames;
18832         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18833       if(appData.useBitmaps) {
18834         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18835                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18836                 appData.liteBackTextureMode,
18837                 appData.darkBackTextureMode );
18838       } else {
18839         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18840                 Col2Text(2),   // lightSquareColor
18841                 Col2Text(3) ); // darkSquareColor
18842       }
18843       if(appData.useBorder) {
18844         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18845                 appData.border);
18846       } else {
18847         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18848       }
18849       if(appData.useFont) {
18850         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18851                 appData.renderPiecesWithFont,
18852                 appData.fontToPieceTable,
18853                 Col2Text(9),    // appData.fontBackColorWhite
18854                 Col2Text(10) ); // appData.fontForeColorBlack
18855       } else {
18856         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18857                 appData.pieceDirectory);
18858         if(!appData.pieceDirectory[0])
18859           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18860                 Col2Text(0),   // whitePieceColor
18861                 Col2Text(1) ); // blackPieceColor
18862       }
18863       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18864                 Col2Text(4),   // highlightSquareColor
18865                 Col2Text(5) ); // premoveHighlightColor
18866         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18867         if(insert != q) insert[-1] = NULLCHAR;
18868         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18869         if(q)   free(q);
18870     }
18871     ActivateTheme(FALSE);
18872 }