Fix installing chosen protocols
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks, handSize, handOffsets;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode, tryNr;
917
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
922
923 void
924 FloatToFront(char **list, char *engineLine)
925 {
926     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
927     int i=0;
928     if(appData.recentEngines <= 0) return;
929     TidyProgramName(engineLine, "localhost", tidy+1);
930     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931     strncpy(buf+1, *list, MSG_SIZ-50);
932     if(p = strstr(buf, tidy)) { // tidy name appears in list
933         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934         while(*p++ = *++q); // squeeze out
935     }
936     strcat(tidy, buf+1); // put list behind tidy name
937     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939     ASSIGN(*list, tidy+1);
940 }
941
942 void
943 SaveEngineList ()
944 {
945         FILE *f;
946         if(*engineListFile && (f = fopen(engineListFile, "w"))) {
947           fprintf(f, "-firstChessProgramNames {%s}\n", firstChessProgramNames);
948           fclose(f);
949         }
950 }
951
952 void
953 AddToEngineList (int i)
954 {
955         int len;
956         char quote, buf[MSG_SIZ];
957         char *q = firstChessProgramNames, *p = newEngineCommand;
958         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
959         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
960         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
961                         quote, p, quote, appData.directory[i],
962                         useNick ? " -fn \"" : "",
963                         useNick ? nickName : "",
964                         useNick ? "\"" : "",
965                         v1 ? " -firstProtocolVersion 1" : "",
966                         hasBook ? "" : " -fNoOwnBookUCI",
967                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
968                         storeVariant ? " -variant " : "",
969                         storeVariant ? VariantName(gameInfo.variant) : "");
970         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
971         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
972         if(insert != q) insert[-1] = NULLCHAR;
973         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
974         if(q)   free(q);
975         SaveEngineList();
976         FloatToFront(&appData.recentEngineList, buf);
977         ASSIGN(currentEngine[i], buf);
978 }
979
980 void
981 LoadEngine ()
982 {
983     int i;
984     if(WaitForEngine(savCps, LoadEngine)) return;
985     if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
986     if(tryNr) v1 |= (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
987     CommonEngineInit(); // recalculate time odds
988     if(gameInfo.variant != StringToVariant(appData.variant)) {
989         // we changed variant when loading the engine; this forces us to reset
990         Reset(TRUE, savCps != &first);
991         oldMode = BeginningOfGame; // to prevent restoring old mode
992     }
993     InitChessProgram(savCps, FALSE);
994     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
995     DisplayMessage("", "");
996     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
997     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
998     ThawUI();
999     SetGNUMode();
1000     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
1001 }
1002
1003 void
1004 ReplaceEngine (ChessProgramState *cps, int n)
1005 {
1006     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
1007     keepInfo = 1;
1008     if(oldMode != BeginningOfGame) EditGameEvent();
1009     keepInfo = 0;
1010     UnloadEngine(cps);
1011     appData.noChessProgram = FALSE;
1012     appData.clockMode = TRUE;
1013     InitEngine(cps, n);
1014     UpdateLogos(TRUE);
1015     if(n && !tryNr) return; // only startup first engine immediately; second can wait (unless autodetect)
1016     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1017     LoadEngine();
1018 }
1019
1020 static char resetOptions[] =
1021         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1022         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1023         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1024         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1025
1026 void
1027 Load (ChessProgramState *cps, int i)
1028 {
1029     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1030     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1031         ASSIGN(currentEngine[i], engineLine);
1032         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1033         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1034         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1035         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1036         appData.firstProtocolVersion = PROTOVER;
1037         ParseArgsFromString(buf);
1038         SwapEngines(i);
1039         ReplaceEngine(cps, i);
1040         FloatToFront(&appData.recentEngineList, engineLine);
1041         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1042         return;
1043     }
1044     p = engineName;
1045     while(q = strchr(p, SLASH)) p = q+1;
1046     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1047     if(engineDir[0] != NULLCHAR) {
1048         ASSIGN(appData.directory[i], engineDir); p = engineName;
1049     } else if(p != engineName) { // derive directory from engine path, when not given
1050         p[-1] = 0;
1051         ASSIGN(appData.directory[i], engineName);
1052         p[-1] = SLASH;
1053         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1054     } else { ASSIGN(appData.directory[i], "."); }
1055     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1056     if(params[0]) {
1057         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1058         snprintf(command, MSG_SIZ, "%s %s", p, params);
1059         p = command;
1060     }
1061     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1062     ASSIGN(appData.chessProgram[i], p);
1063     tryNr = 3; // requests adding to list without auto-detect
1064     if(isUCI == 3) tryNr = 1, isUCI = 0; // auto-detect
1065     appData.isUCI[i] = isUCI;
1066     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1067     appData.hasOwnBookUCI[i] = hasBook;
1068     if(!nickName[0]) useNick = FALSE;
1069     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1070     safeStrCpy(newEngineCommand, p, MSG_SIZ);
1071     ReplaceEngine(cps, i);
1072 }
1073
1074 void
1075 InitTimeControls ()
1076 {
1077     int matched, min, sec;
1078     /*
1079      * Parse timeControl resource
1080      */
1081     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1082                           appData.movesPerSession)) {
1083         char buf[MSG_SIZ];
1084         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1085         DisplayFatalError(buf, 0, 2);
1086     }
1087
1088     /*
1089      * Parse searchTime resource
1090      */
1091     if (*appData.searchTime != NULLCHAR) {
1092         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1093         if (matched == 1) {
1094             searchTime = min * 60;
1095         } else if (matched == 2) {
1096             searchTime = min * 60 + sec;
1097         } else {
1098             char buf[MSG_SIZ];
1099             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1100             DisplayFatalError(buf, 0, 2);
1101         }
1102     }
1103 }
1104
1105 void
1106 InitBackEnd1 ()
1107 {
1108
1109     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1110     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1111
1112     GetTimeMark(&programStartTime);
1113     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1114     appData.seedBase = random() + (random()<<15);
1115     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1116
1117     ClearProgramStats();
1118     programStats.ok_to_send = 1;
1119     programStats.seen_stat = 0;
1120
1121     /*
1122      * Initialize game list
1123      */
1124     ListNew(&gameList);
1125
1126
1127     /*
1128      * Internet chess server status
1129      */
1130     if (appData.icsActive) {
1131         appData.matchMode = FALSE;
1132         appData.matchGames = 0;
1133 #if ZIPPY
1134         appData.noChessProgram = !appData.zippyPlay;
1135 #else
1136         appData.zippyPlay = FALSE;
1137         appData.zippyTalk = FALSE;
1138         appData.noChessProgram = TRUE;
1139 #endif
1140         if (*appData.icsHelper != NULLCHAR) {
1141             appData.useTelnet = TRUE;
1142             appData.telnetProgram = appData.icsHelper;
1143         }
1144     } else {
1145         appData.zippyTalk = appData.zippyPlay = FALSE;
1146     }
1147
1148     /* [AS] Initialize pv info list [HGM] and game state */
1149     {
1150         int i, j;
1151
1152         for( i=0; i<=framePtr; i++ ) {
1153             pvInfoList[i].depth = -1;
1154             boards[i][EP_STATUS] = EP_NONE;
1155             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1156         }
1157     }
1158
1159     InitTimeControls();
1160
1161     /* [AS] Adjudication threshold */
1162     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1163
1164     InitEngine(&first, 0);
1165     InitEngine(&second, 1);
1166     CommonEngineInit();
1167
1168     pairing.which = "pairing"; // pairing engine
1169     pairing.pr = NoProc;
1170     pairing.isr = NULL;
1171     pairing.program = appData.pairingEngine;
1172     pairing.host = "localhost";
1173     pairing.dir = ".";
1174
1175     if (appData.icsActive) {
1176         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1177     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1178         appData.clockMode = FALSE;
1179         first.sendTime = second.sendTime = 0;
1180     }
1181
1182 #if ZIPPY
1183     /* Override some settings from environment variables, for backward
1184        compatibility.  Unfortunately it's not feasible to have the env
1185        vars just set defaults, at least in xboard.  Ugh.
1186     */
1187     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1188       ZippyInit();
1189     }
1190 #endif
1191
1192     if (!appData.icsActive) {
1193       char buf[MSG_SIZ];
1194       int len;
1195
1196       /* Check for variants that are supported only in ICS mode,
1197          or not at all.  Some that are accepted here nevertheless
1198          have bugs; see comments below.
1199       */
1200       VariantClass variant = StringToVariant(appData.variant);
1201       switch (variant) {
1202       case VariantBughouse:     /* need four players and two boards */
1203       case VariantKriegspiel:   /* need to hide pieces and move details */
1204         /* case VariantFischeRandom: (Fabien: moved below) */
1205         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1206         if( (len >= MSG_SIZ) && appData.debugMode )
1207           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1208
1209         DisplayFatalError(buf, 0, 2);
1210         return;
1211
1212       case VariantUnknown:
1213       case VariantLoadable:
1214       case Variant29:
1215       case Variant30:
1216       case Variant31:
1217       case Variant32:
1218       case Variant33:
1219       case Variant34:
1220       case Variant35:
1221       case Variant36:
1222       default:
1223         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1224         if( (len >= MSG_SIZ) && appData.debugMode )
1225           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1226
1227         DisplayFatalError(buf, 0, 2);
1228         return;
1229
1230       case VariantNormal:     /* definitely works! */
1231         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1232           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1233           return;
1234         }
1235       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1236       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1237       case VariantGothic:     /* [HGM] should work */
1238       case VariantCapablanca: /* [HGM] should work */
1239       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1240       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1241       case VariantChu:        /* [HGM] experimental */
1242       case VariantKnightmate: /* [HGM] should work */
1243       case VariantCylinder:   /* [HGM] untested */
1244       case VariantFalcon:     /* [HGM] untested */
1245       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1246                                  offboard interposition not understood */
1247       case VariantWildCastle: /* pieces not automatically shuffled */
1248       case VariantNoCastle:   /* pieces not automatically shuffled */
1249       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1250       case VariantLosers:     /* should work except for win condition,
1251                                  and doesn't know captures are mandatory */
1252       case VariantSuicide:    /* should work except for win condition,
1253                                  and doesn't know captures are mandatory */
1254       case VariantGiveaway:   /* should work except for win condition,
1255                                  and doesn't know captures are mandatory */
1256       case VariantTwoKings:   /* should work */
1257       case VariantAtomic:     /* should work except for win condition */
1258       case Variant3Check:     /* should work except for win condition */
1259       case VariantShatranj:   /* should work except for all win conditions */
1260       case VariantMakruk:     /* should work except for draw countdown */
1261       case VariantASEAN :     /* should work except for draw countdown */
1262       case VariantBerolina:   /* might work if TestLegality is off */
1263       case VariantCapaRandom: /* should work */
1264       case VariantJanus:      /* should work */
1265       case VariantSuper:      /* experimental */
1266       case VariantGreat:      /* experimental, requires legality testing to be off */
1267       case VariantSChess:     /* S-Chess, should work */
1268       case VariantGrand:      /* should work */
1269       case VariantSpartan:    /* should work */
1270       case VariantLion:       /* should work */
1271       case VariantChuChess:   /* should work */
1272         break;
1273       }
1274     }
1275
1276 }
1277
1278 int
1279 NextIntegerFromString (char ** str, long * value)
1280 {
1281     int result = -1;
1282     char * s = *str;
1283
1284     while( *s == ' ' || *s == '\t' ) {
1285         s++;
1286     }
1287
1288     *value = 0;
1289
1290     if( *s >= '0' && *s <= '9' ) {
1291         while( *s >= '0' && *s <= '9' ) {
1292             *value = *value * 10 + (*s - '0');
1293             s++;
1294         }
1295
1296         result = 0;
1297     }
1298
1299     *str = s;
1300
1301     return result;
1302 }
1303
1304 int
1305 NextTimeControlFromString (char ** str, long * value)
1306 {
1307     long temp;
1308     int result = NextIntegerFromString( str, &temp );
1309
1310     if( result == 0 ) {
1311         *value = temp * 60; /* Minutes */
1312         if( **str == ':' ) {
1313             (*str)++;
1314             result = NextIntegerFromString( str, &temp );
1315             *value += temp; /* Seconds */
1316         }
1317     }
1318
1319     return result;
1320 }
1321
1322 int
1323 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1324 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1325     int result = -1, type = 0; long temp, temp2;
1326
1327     if(**str != ':') return -1; // old params remain in force!
1328     (*str)++;
1329     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1330     if( NextIntegerFromString( str, &temp ) ) return -1;
1331     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1332
1333     if(**str != '/') {
1334         /* time only: incremental or sudden-death time control */
1335         if(**str == '+') { /* increment follows; read it */
1336             (*str)++;
1337             if(**str == '!') type = *(*str)++; // Bronstein TC
1338             if(result = NextIntegerFromString( str, &temp2)) return -1;
1339             *inc = temp2 * 1000;
1340             if(**str == '.') { // read fraction of increment
1341                 char *start = ++(*str);
1342                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1343                 temp2 *= 1000;
1344                 while(start++ < *str) temp2 /= 10;
1345                 *inc += temp2;
1346             }
1347         } else *inc = 0;
1348         *moves = 0; *tc = temp * 1000; *incType = type;
1349         return 0;
1350     }
1351
1352     (*str)++; /* classical time control */
1353     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1354
1355     if(result == 0) {
1356         *moves = temp;
1357         *tc    = temp2 * 1000;
1358         *inc   = 0;
1359         *incType = type;
1360     }
1361     return result;
1362 }
1363
1364 int
1365 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1366 {   /* [HGM] get time to add from the multi-session time-control string */
1367     int incType, moves=1; /* kludge to force reading of first session */
1368     long time, increment;
1369     char *s = tcString;
1370
1371     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1372     do {
1373         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1374         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1375         if(movenr == -1) return time;    /* last move before new session     */
1376         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1377         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1378         if(!moves) return increment;     /* current session is incremental   */
1379         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1380     } while(movenr >= -1);               /* try again for next session       */
1381
1382     return 0; // no new time quota on this move
1383 }
1384
1385 int
1386 ParseTimeControl (char *tc, float ti, int mps)
1387 {
1388   long tc1;
1389   long tc2;
1390   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1391   int min, sec=0;
1392
1393   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1394   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1395       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1396   if(ti > 0) {
1397
1398     if(mps)
1399       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1400     else
1401       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1402   } else {
1403     if(mps)
1404       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1405     else
1406       snprintf(buf, MSG_SIZ, ":%s", mytc);
1407   }
1408   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1409
1410   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1411     return FALSE;
1412   }
1413
1414   if( *tc == '/' ) {
1415     /* Parse second time control */
1416     tc++;
1417
1418     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1419       return FALSE;
1420     }
1421
1422     if( tc2 == 0 ) {
1423       return FALSE;
1424     }
1425
1426     timeControl_2 = tc2 * 1000;
1427   }
1428   else {
1429     timeControl_2 = 0;
1430   }
1431
1432   if( tc1 == 0 ) {
1433     return FALSE;
1434   }
1435
1436   timeControl = tc1 * 1000;
1437
1438   if (ti >= 0) {
1439     timeIncrement = ti * 1000;  /* convert to ms */
1440     movesPerSession = 0;
1441   } else {
1442     timeIncrement = 0;
1443     movesPerSession = mps;
1444   }
1445   return TRUE;
1446 }
1447
1448 void
1449 InitBackEnd2 ()
1450 {
1451     if (appData.debugMode) {
1452 #    ifdef __GIT_VERSION
1453       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1454 #    else
1455       fprintf(debugFP, "Version: %s\n", programVersion);
1456 #    endif
1457     }
1458     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1459
1460     set_cont_sequence(appData.wrapContSeq);
1461     if (appData.matchGames > 0) {
1462         appData.matchMode = TRUE;
1463     } else if (appData.matchMode) {
1464         appData.matchGames = 1;
1465     }
1466     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1467         appData.matchGames = appData.sameColorGames;
1468     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1469         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1470         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1471     }
1472     Reset(TRUE, FALSE);
1473     if (appData.noChessProgram || first.protocolVersion == 1) {
1474       InitBackEnd3();
1475     } else {
1476       /* kludge: allow timeout for initial "feature" commands */
1477       FreezeUI();
1478       DisplayMessage("", _("Starting chess program"));
1479       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1480     }
1481 }
1482
1483 int
1484 CalculateIndex (int index, int gameNr)
1485 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1486     int res;
1487     if(index > 0) return index; // fixed nmber
1488     if(index == 0) return 1;
1489     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1490     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1491     return res;
1492 }
1493
1494 int
1495 LoadGameOrPosition (int gameNr)
1496 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1497     if (*appData.loadGameFile != NULLCHAR) {
1498         if (!LoadGameFromFile(appData.loadGameFile,
1499                 CalculateIndex(appData.loadGameIndex, gameNr),
1500                               appData.loadGameFile, FALSE)) {
1501             DisplayFatalError(_("Bad game file"), 0, 1);
1502             return 0;
1503         }
1504     } else if (*appData.loadPositionFile != NULLCHAR) {
1505         if (!LoadPositionFromFile(appData.loadPositionFile,
1506                 CalculateIndex(appData.loadPositionIndex, gameNr),
1507                                   appData.loadPositionFile)) {
1508             DisplayFatalError(_("Bad position file"), 0, 1);
1509             return 0;
1510         }
1511     }
1512     return 1;
1513 }
1514
1515 void
1516 ReserveGame (int gameNr, char resChar)
1517 {
1518     FILE *tf = fopen(appData.tourneyFile, "r+");
1519     char *p, *q, c, buf[MSG_SIZ];
1520     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1521     safeStrCpy(buf, lastMsg, MSG_SIZ);
1522     DisplayMessage(_("Pick new game"), "");
1523     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1524     ParseArgsFromFile(tf);
1525     p = q = appData.results;
1526     if(appData.debugMode) {
1527       char *r = appData.participants;
1528       fprintf(debugFP, "results = '%s'\n", p);
1529       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1530       fprintf(debugFP, "\n");
1531     }
1532     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1533     nextGame = q - p;
1534     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1535     safeStrCpy(q, p, strlen(p) + 2);
1536     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1537     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1538     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1539         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1540         q[nextGame] = '*';
1541     }
1542     fseek(tf, -(strlen(p)+4), SEEK_END);
1543     c = fgetc(tf);
1544     if(c != '"') // depending on DOS or Unix line endings we can be one off
1545          fseek(tf, -(strlen(p)+2), SEEK_END);
1546     else fseek(tf, -(strlen(p)+3), SEEK_END);
1547     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1548     DisplayMessage(buf, "");
1549     free(p); appData.results = q;
1550     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1551        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1552       int round = appData.defaultMatchGames * appData.tourneyType;
1553       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1554          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1555         UnloadEngine(&first);  // next game belongs to other pairing;
1556         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1557     }
1558     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1559 }
1560
1561 void
1562 MatchEvent (int mode)
1563 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1564         int dummy;
1565         if(matchMode) { // already in match mode: switch it off
1566             abortMatch = TRUE;
1567             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1568             return;
1569         }
1570 //      if(gameMode != BeginningOfGame) {
1571 //          DisplayError(_("You can only start a match from the initial position."), 0);
1572 //          return;
1573 //      }
1574         abortMatch = FALSE;
1575         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1576         /* Set up machine vs. machine match */
1577         nextGame = 0;
1578         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1579         if(appData.tourneyFile[0]) {
1580             ReserveGame(-1, 0);
1581             if(nextGame > appData.matchGames) {
1582                 char buf[MSG_SIZ];
1583                 if(strchr(appData.results, '*') == NULL) {
1584                     FILE *f;
1585                     appData.tourneyCycles++;
1586                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1587                         fclose(f);
1588                         NextTourneyGame(-1, &dummy);
1589                         ReserveGame(-1, 0);
1590                         if(nextGame <= appData.matchGames) {
1591                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1592                             matchMode = mode;
1593                             ScheduleDelayedEvent(NextMatchGame, 10000);
1594                             return;
1595                         }
1596                     }
1597                 }
1598                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1599                 DisplayError(buf, 0);
1600                 appData.tourneyFile[0] = 0;
1601                 return;
1602             }
1603         } else
1604         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1605             DisplayFatalError(_("Can't have a match with no chess programs"),
1606                               0, 2);
1607             return;
1608         }
1609         matchMode = mode;
1610         matchGame = roundNr = 1;
1611         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1612         NextMatchGame();
1613 }
1614
1615 void
1616 InitBackEnd3 P((void))
1617 {
1618     GameMode initialMode;
1619     char buf[MSG_SIZ];
1620     int err, len;
1621
1622     ParseFeatures(appData.features[0], &first);
1623     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1624        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1625         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1626        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1627        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1628         char c, *q = first.variants, *p = strchr(q, ',');
1629         if(p) *p = NULLCHAR;
1630         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1631             int w, h, s;
1632             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1633                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1634             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1635             Reset(TRUE, FALSE);         // and re-initialize
1636         }
1637         if(p) *p = ',';
1638     }
1639
1640     InitChessProgram(&first, startedFromSetupPosition);
1641
1642     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1643         free(programVersion);
1644         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1645         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1646         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1647     }
1648
1649     if (appData.icsActive) {
1650 #ifdef WIN32
1651         /* [DM] Make a console window if needed [HGM] merged ifs */
1652         ConsoleCreate();
1653 #endif
1654         err = establish();
1655         if (err != 0)
1656           {
1657             if (*appData.icsCommPort != NULLCHAR)
1658               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1659                              appData.icsCommPort);
1660             else
1661               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1662                         appData.icsHost, appData.icsPort);
1663
1664             if( (len >= MSG_SIZ) && appData.debugMode )
1665               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1666
1667             DisplayFatalError(buf, err, 1);
1668             return;
1669         }
1670         SetICSMode();
1671         telnetISR =
1672           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1673         fromUserISR =
1674           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1675         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1676             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1677     } else if (appData.noChessProgram) {
1678         SetNCPMode();
1679     } else {
1680         SetGNUMode();
1681     }
1682
1683     if (*appData.cmailGameName != NULLCHAR) {
1684         SetCmailMode();
1685         OpenLoopback(&cmailPR);
1686         cmailISR =
1687           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1688     }
1689
1690     ThawUI();
1691     DisplayMessage("", "");
1692     if (StrCaseCmp(appData.initialMode, "") == 0) {
1693       initialMode = BeginningOfGame;
1694       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1695         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1696         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1697         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1698         ModeHighlight();
1699       }
1700     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1701       initialMode = TwoMachinesPlay;
1702     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1703       initialMode = AnalyzeFile;
1704     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1705       initialMode = AnalyzeMode;
1706     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1707       initialMode = MachinePlaysWhite;
1708     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1709       initialMode = MachinePlaysBlack;
1710     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1711       initialMode = EditGame;
1712     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1713       initialMode = EditPosition;
1714     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1715       initialMode = Training;
1716     } else {
1717       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1718       if( (len >= MSG_SIZ) && appData.debugMode )
1719         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1720
1721       DisplayFatalError(buf, 0, 2);
1722       return;
1723     }
1724
1725     if (appData.matchMode) {
1726         if(appData.tourneyFile[0]) { // start tourney from command line
1727             FILE *f;
1728             if(f = fopen(appData.tourneyFile, "r")) {
1729                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1730                 fclose(f);
1731                 appData.clockMode = TRUE;
1732                 SetGNUMode();
1733             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1734         }
1735         MatchEvent(TRUE);
1736     } else if (*appData.cmailGameName != NULLCHAR) {
1737         /* Set up cmail mode */
1738         ReloadCmailMsgEvent(TRUE);
1739     } else {
1740         /* Set up other modes */
1741         if (initialMode == AnalyzeFile) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1744             return;
1745           }
1746         }
1747         if (*appData.loadGameFile != NULLCHAR) {
1748             (void) LoadGameFromFile(appData.loadGameFile,
1749                                     appData.loadGameIndex,
1750                                     appData.loadGameFile, TRUE);
1751         } else if (*appData.loadPositionFile != NULLCHAR) {
1752             (void) LoadPositionFromFile(appData.loadPositionFile,
1753                                         appData.loadPositionIndex,
1754                                         appData.loadPositionFile);
1755             /* [HGM] try to make self-starting even after FEN load */
1756             /* to allow automatic setup of fairy variants with wtm */
1757             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1758                 gameMode = BeginningOfGame;
1759                 setboardSpoiledMachineBlack = 1;
1760             }
1761             /* [HGM] loadPos: make that every new game uses the setup */
1762             /* from file as long as we do not switch variant          */
1763             if(!blackPlaysFirst) {
1764                 startedFromPositionFile = TRUE;
1765                 CopyBoard(filePosition, boards[0]);
1766                 CopyBoard(initialPosition, boards[0]);
1767             }
1768         } else if(*appData.fen != NULLCHAR) {
1769             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1770                 startedFromPositionFile = TRUE;
1771                 Reset(TRUE, TRUE);
1772             }
1773         }
1774         if (initialMode == AnalyzeMode) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1781             return;
1782           }
1783           AnalyzeModeEvent();
1784         } else if (initialMode == AnalyzeFile) {
1785           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1786           ShowThinkingEvent();
1787           AnalyzeFileEvent();
1788           AnalysisPeriodicEvent(1);
1789         } else if (initialMode == MachinePlaysWhite) {
1790           if (appData.noChessProgram) {
1791             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1792                               0, 2);
1793             return;
1794           }
1795           if (appData.icsActive) {
1796             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1797                               0, 2);
1798             return;
1799           }
1800           MachineWhiteEvent();
1801         } else if (initialMode == MachinePlaysBlack) {
1802           if (appData.noChessProgram) {
1803             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1804                               0, 2);
1805             return;
1806           }
1807           if (appData.icsActive) {
1808             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1809                               0, 2);
1810             return;
1811           }
1812           MachineBlackEvent();
1813         } else if (initialMode == TwoMachinesPlay) {
1814           if (appData.noChessProgram) {
1815             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1816                               0, 2);
1817             return;
1818           }
1819           if (appData.icsActive) {
1820             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1821                               0, 2);
1822             return;
1823           }
1824           TwoMachinesEvent();
1825         } else if (initialMode == EditGame) {
1826           EditGameEvent();
1827         } else if (initialMode == EditPosition) {
1828           EditPositionEvent();
1829         } else if (initialMode == Training) {
1830           if (*appData.loadGameFile == NULLCHAR) {
1831             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1832             return;
1833           }
1834           TrainingEvent();
1835         }
1836     }
1837 }
1838
1839 void
1840 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1841 {
1842     DisplayBook(current+1);
1843
1844     MoveHistorySet( movelist, first, last, current, pvInfoList );
1845
1846     EvalGraphSet( first, last, current, pvInfoList );
1847
1848     MakeEngineOutputTitle();
1849 }
1850
1851 /*
1852  * Establish will establish a contact to a remote host.port.
1853  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1854  *  used to talk to the host.
1855  * Returns 0 if okay, error code if not.
1856  */
1857 int
1858 establish ()
1859 {
1860     char buf[MSG_SIZ];
1861
1862     if (*appData.icsCommPort != NULLCHAR) {
1863         /* Talk to the host through a serial comm port */
1864         return OpenCommPort(appData.icsCommPort, &icsPR);
1865
1866     } else if (*appData.gateway != NULLCHAR) {
1867         if (*appData.remoteShell == NULLCHAR) {
1868             /* Use the rcmd protocol to run telnet program on a gateway host */
1869             snprintf(buf, sizeof(buf), "%s %s %s",
1870                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1871             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1872
1873         } else {
1874             /* Use the rsh program to run telnet program on a gateway host */
1875             if (*appData.remoteUser == NULLCHAR) {
1876                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1877                         appData.gateway, appData.telnetProgram,
1878                         appData.icsHost, appData.icsPort);
1879             } else {
1880                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1881                         appData.remoteShell, appData.gateway,
1882                         appData.remoteUser, appData.telnetProgram,
1883                         appData.icsHost, appData.icsPort);
1884             }
1885             return StartChildProcess(buf, "", &icsPR);
1886
1887         }
1888     } else if (appData.useTelnet) {
1889         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1890
1891     } else {
1892         /* TCP socket interface differs somewhat between
1893            Unix and NT; handle details in the front end.
1894            */
1895         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1896     }
1897 }
1898
1899 void
1900 EscapeExpand (char *p, char *q)
1901 {       // [HGM] initstring: routine to shape up string arguments
1902         while(*p++ = *q++) if(p[-1] == '\\')
1903             switch(*q++) {
1904                 case 'n': p[-1] = '\n'; break;
1905                 case 'r': p[-1] = '\r'; break;
1906                 case 't': p[-1] = '\t'; break;
1907                 case '\\': p[-1] = '\\'; break;
1908                 case 0: *p = 0; return;
1909                 default: p[-1] = q[-1]; break;
1910             }
1911 }
1912
1913 void
1914 show_bytes (FILE *fp, char *buf, int count)
1915 {
1916     while (count--) {
1917         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1918             fprintf(fp, "\\%03o", *buf & 0xff);
1919         } else {
1920             putc(*buf, fp);
1921         }
1922         buf++;
1923     }
1924     fflush(fp);
1925 }
1926
1927 /* Returns an errno value */
1928 int
1929 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1930 {
1931     char buf[8192], *p, *q, *buflim;
1932     int left, newcount, outcount;
1933
1934     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1935         *appData.gateway != NULLCHAR) {
1936         if (appData.debugMode) {
1937             fprintf(debugFP, ">ICS: ");
1938             show_bytes(debugFP, message, count);
1939             fprintf(debugFP, "\n");
1940         }
1941         return OutputToProcess(pr, message, count, outError);
1942     }
1943
1944     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1945     p = message;
1946     q = buf;
1947     left = count;
1948     newcount = 0;
1949     while (left) {
1950         if (q >= buflim) {
1951             if (appData.debugMode) {
1952                 fprintf(debugFP, ">ICS: ");
1953                 show_bytes(debugFP, buf, newcount);
1954                 fprintf(debugFP, "\n");
1955             }
1956             outcount = OutputToProcess(pr, buf, newcount, outError);
1957             if (outcount < newcount) return -1; /* to be sure */
1958             q = buf;
1959             newcount = 0;
1960         }
1961         if (*p == '\n') {
1962             *q++ = '\r';
1963             newcount++;
1964         } else if (((unsigned char) *p) == TN_IAC) {
1965             *q++ = (char) TN_IAC;
1966             newcount ++;
1967         }
1968         *q++ = *p++;
1969         newcount++;
1970         left--;
1971     }
1972     if (appData.debugMode) {
1973         fprintf(debugFP, ">ICS: ");
1974         show_bytes(debugFP, buf, newcount);
1975         fprintf(debugFP, "\n");
1976     }
1977     outcount = OutputToProcess(pr, buf, newcount, outError);
1978     if (outcount < newcount) return -1; /* to be sure */
1979     return count;
1980 }
1981
1982 void
1983 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1984 {
1985     int outError, outCount;
1986     static int gotEof = 0;
1987     static FILE *ini;
1988
1989     /* Pass data read from player on to ICS */
1990     if (count > 0) {
1991         gotEof = 0;
1992         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1993         if (outCount < count) {
1994             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1995         }
1996         if(have_sent_ICS_logon == 2) {
1997           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1998             fprintf(ini, "%s", message);
1999             have_sent_ICS_logon = 3;
2000           } else
2001             have_sent_ICS_logon = 1;
2002         } else if(have_sent_ICS_logon == 3) {
2003             fprintf(ini, "%s", message);
2004             fclose(ini);
2005           have_sent_ICS_logon = 1;
2006         }
2007     } else if (count < 0) {
2008         RemoveInputSource(isr);
2009         DisplayFatalError(_("Error reading from keyboard"), error, 1);
2010     } else if (gotEof++ > 0) {
2011         RemoveInputSource(isr);
2012         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2013     }
2014 }
2015
2016 void
2017 KeepAlive ()
2018 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2019     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2020     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2021     SendToICS("date\n");
2022     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2023 }
2024
2025 /* added routine for printf style output to ics */
2026 void
2027 ics_printf (char *format, ...)
2028 {
2029     char buffer[MSG_SIZ];
2030     va_list args;
2031
2032     va_start(args, format);
2033     vsnprintf(buffer, sizeof(buffer), format, args);
2034     buffer[sizeof(buffer)-1] = '\0';
2035     SendToICS(buffer);
2036     va_end(args);
2037 }
2038
2039 void
2040 SendToICS (char *s)
2041 {
2042     int count, outCount, outError;
2043
2044     if (icsPR == NoProc) return;
2045
2046     count = strlen(s);
2047     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2048     if (outCount < count) {
2049         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2050     }
2051 }
2052
2053 /* This is used for sending logon scripts to the ICS. Sending
2054    without a delay causes problems when using timestamp on ICC
2055    (at least on my machine). */
2056 void
2057 SendToICSDelayed (char *s, long msdelay)
2058 {
2059     int count, outCount, outError;
2060
2061     if (icsPR == NoProc) return;
2062
2063     count = strlen(s);
2064     if (appData.debugMode) {
2065         fprintf(debugFP, ">ICS: ");
2066         show_bytes(debugFP, s, count);
2067         fprintf(debugFP, "\n");
2068     }
2069     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2070                                       msdelay);
2071     if (outCount < count) {
2072         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2073     }
2074 }
2075
2076
2077 /* Remove all highlighting escape sequences in s
2078    Also deletes any suffix starting with '('
2079    */
2080 char *
2081 StripHighlightAndTitle (char *s)
2082 {
2083     static char retbuf[MSG_SIZ];
2084     char *p = retbuf;
2085
2086     while (*s != NULLCHAR) {
2087         while (*s == '\033') {
2088             while (*s != NULLCHAR && !isalpha(*s)) s++;
2089             if (*s != NULLCHAR) s++;
2090         }
2091         while (*s != NULLCHAR && *s != '\033') {
2092             if (*s == '(' || *s == '[') {
2093                 *p = NULLCHAR;
2094                 return retbuf;
2095             }
2096             *p++ = *s++;
2097         }
2098     }
2099     *p = NULLCHAR;
2100     return retbuf;
2101 }
2102
2103 /* Remove all highlighting escape sequences in s */
2104 char *
2105 StripHighlight (char *s)
2106 {
2107     static char retbuf[MSG_SIZ];
2108     char *p = retbuf;
2109
2110     while (*s != NULLCHAR) {
2111         while (*s == '\033') {
2112             while (*s != NULLCHAR && !isalpha(*s)) s++;
2113             if (*s != NULLCHAR) s++;
2114         }
2115         while (*s != NULLCHAR && *s != '\033') {
2116             *p++ = *s++;
2117         }
2118     }
2119     *p = NULLCHAR;
2120     return retbuf;
2121 }
2122
2123 char engineVariant[MSG_SIZ];
2124 char *variantNames[] = VARIANT_NAMES;
2125 char *
2126 VariantName (VariantClass v)
2127 {
2128     if(v == VariantUnknown || *engineVariant) return engineVariant;
2129     return variantNames[v];
2130 }
2131
2132
2133 /* Identify a variant from the strings the chess servers use or the
2134    PGN Variant tag names we use. */
2135 VariantClass
2136 StringToVariant (char *e)
2137 {
2138     char *p;
2139     int wnum = -1;
2140     VariantClass v = VariantNormal;
2141     int i, found = FALSE;
2142     char buf[MSG_SIZ], c;
2143     int len;
2144
2145     if (!e) return v;
2146
2147     /* [HGM] skip over optional board-size prefixes */
2148     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2149         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2150         while( *e++ != '_');
2151     }
2152
2153     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2154         v = VariantNormal;
2155         found = TRUE;
2156     } else
2157     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2158       if (p = StrCaseStr(e, variantNames[i])) {
2159         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2160         v = (VariantClass) i;
2161         found = TRUE;
2162         break;
2163       }
2164     }
2165
2166     if (!found) {
2167       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2168           || StrCaseStr(e, "wild/fr")
2169           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2170         v = VariantFischeRandom;
2171       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2172                  (i = 1, p = StrCaseStr(e, "w"))) {
2173         p += i;
2174         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2175         if (isdigit(*p)) {
2176           wnum = atoi(p);
2177         } else {
2178           wnum = -1;
2179         }
2180         switch (wnum) {
2181         case 0: /* FICS only, actually */
2182         case 1:
2183           /* Castling legal even if K starts on d-file */
2184           v = VariantWildCastle;
2185           break;
2186         case 2:
2187         case 3:
2188         case 4:
2189           /* Castling illegal even if K & R happen to start in
2190              normal positions. */
2191           v = VariantNoCastle;
2192           break;
2193         case 5:
2194         case 7:
2195         case 8:
2196         case 10:
2197         case 11:
2198         case 12:
2199         case 13:
2200         case 14:
2201         case 15:
2202         case 18:
2203         case 19:
2204           /* Castling legal iff K & R start in normal positions */
2205           v = VariantNormal;
2206           break;
2207         case 6:
2208         case 20:
2209         case 21:
2210           /* Special wilds for position setup; unclear what to do here */
2211           v = VariantLoadable;
2212           break;
2213         case 9:
2214           /* Bizarre ICC game */
2215           v = VariantTwoKings;
2216           break;
2217         case 16:
2218           v = VariantKriegspiel;
2219           break;
2220         case 17:
2221           v = VariantLosers;
2222           break;
2223         case 22:
2224           v = VariantFischeRandom;
2225           break;
2226         case 23:
2227           v = VariantCrazyhouse;
2228           break;
2229         case 24:
2230           v = VariantBughouse;
2231           break;
2232         case 25:
2233           v = Variant3Check;
2234           break;
2235         case 26:
2236           /* Not quite the same as FICS suicide! */
2237           v = VariantGiveaway;
2238           break;
2239         case 27:
2240           v = VariantAtomic;
2241           break;
2242         case 28:
2243           v = VariantShatranj;
2244           break;
2245
2246         /* Temporary names for future ICC types.  The name *will* change in
2247            the next xboard/WinBoard release after ICC defines it. */
2248         case 29:
2249           v = Variant29;
2250           break;
2251         case 30:
2252           v = Variant30;
2253           break;
2254         case 31:
2255           v = Variant31;
2256           break;
2257         case 32:
2258           v = Variant32;
2259           break;
2260         case 33:
2261           v = Variant33;
2262           break;
2263         case 34:
2264           v = Variant34;
2265           break;
2266         case 35:
2267           v = Variant35;
2268           break;
2269         case 36:
2270           v = Variant36;
2271           break;
2272         case 37:
2273           v = VariantShogi;
2274           break;
2275         case 38:
2276           v = VariantXiangqi;
2277           break;
2278         case 39:
2279           v = VariantCourier;
2280           break;
2281         case 40:
2282           v = VariantGothic;
2283           break;
2284         case 41:
2285           v = VariantCapablanca;
2286           break;
2287         case 42:
2288           v = VariantKnightmate;
2289           break;
2290         case 43:
2291           v = VariantFairy;
2292           break;
2293         case 44:
2294           v = VariantCylinder;
2295           break;
2296         case 45:
2297           v = VariantFalcon;
2298           break;
2299         case 46:
2300           v = VariantCapaRandom;
2301           break;
2302         case 47:
2303           v = VariantBerolina;
2304           break;
2305         case 48:
2306           v = VariantJanus;
2307           break;
2308         case 49:
2309           v = VariantSuper;
2310           break;
2311         case 50:
2312           v = VariantGreat;
2313           break;
2314         case -1:
2315           /* Found "wild" or "w" in the string but no number;
2316              must assume it's normal chess. */
2317           v = VariantNormal;
2318           break;
2319         default:
2320           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2321           if( (len >= MSG_SIZ) && appData.debugMode )
2322             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2323
2324           DisplayError(buf, 0);
2325           v = VariantUnknown;
2326           break;
2327         }
2328       }
2329     }
2330     if (appData.debugMode) {
2331       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2332               e, wnum, VariantName(v));
2333     }
2334     return v;
2335 }
2336
2337 static int leftover_start = 0, leftover_len = 0;
2338 char star_match[STAR_MATCH_N][MSG_SIZ];
2339
2340 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2341    advance *index beyond it, and set leftover_start to the new value of
2342    *index; else return FALSE.  If pattern contains the character '*', it
2343    matches any sequence of characters not containing '\r', '\n', or the
2344    character following the '*' (if any), and the matched sequence(s) are
2345    copied into star_match.
2346    */
2347 int
2348 looking_at ( char *buf, int *index, char *pattern)
2349 {
2350     char *bufp = &buf[*index], *patternp = pattern;
2351     int star_count = 0;
2352     char *matchp = star_match[0];
2353
2354     for (;;) {
2355         if (*patternp == NULLCHAR) {
2356             *index = leftover_start = bufp - buf;
2357             *matchp = NULLCHAR;
2358             return TRUE;
2359         }
2360         if (*bufp == NULLCHAR) return FALSE;
2361         if (*patternp == '*') {
2362             if (*bufp == *(patternp + 1)) {
2363                 *matchp = NULLCHAR;
2364                 matchp = star_match[++star_count];
2365                 patternp += 2;
2366                 bufp++;
2367                 continue;
2368             } else if (*bufp == '\n' || *bufp == '\r') {
2369                 patternp++;
2370                 if (*patternp == NULLCHAR)
2371                   continue;
2372                 else
2373                   return FALSE;
2374             } else {
2375                 *matchp++ = *bufp++;
2376                 continue;
2377             }
2378         }
2379         if (*patternp != *bufp) return FALSE;
2380         patternp++;
2381         bufp++;
2382     }
2383 }
2384
2385 void
2386 SendToPlayer (char *data, int length)
2387 {
2388     int error, outCount;
2389     outCount = OutputToProcess(NoProc, data, length, &error);
2390     if (outCount < length) {
2391         DisplayFatalError(_("Error writing to display"), error, 1);
2392     }
2393 }
2394
2395 void
2396 PackHolding (char packed[], char *holding)
2397 {
2398     char *p = holding;
2399     char *q = packed;
2400     int runlength = 0;
2401     int curr = 9999;
2402     do {
2403         if (*p == curr) {
2404             runlength++;
2405         } else {
2406             switch (runlength) {
2407               case 0:
2408                 break;
2409               case 1:
2410                 *q++ = curr;
2411                 break;
2412               case 2:
2413                 *q++ = curr;
2414                 *q++ = curr;
2415                 break;
2416               default:
2417                 sprintf(q, "%d", runlength);
2418                 while (*q) q++;
2419                 *q++ = curr;
2420                 break;
2421             }
2422             runlength = 1;
2423             curr = *p;
2424         }
2425     } while (*p++);
2426     *q = NULLCHAR;
2427 }
2428
2429 /* Telnet protocol requests from the front end */
2430 void
2431 TelnetRequest (unsigned char ddww, unsigned char option)
2432 {
2433     unsigned char msg[3];
2434     int outCount, outError;
2435
2436     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2437
2438     if (appData.debugMode) {
2439         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2440         switch (ddww) {
2441           case TN_DO:
2442             ddwwStr = "DO";
2443             break;
2444           case TN_DONT:
2445             ddwwStr = "DONT";
2446             break;
2447           case TN_WILL:
2448             ddwwStr = "WILL";
2449             break;
2450           case TN_WONT:
2451             ddwwStr = "WONT";
2452             break;
2453           default:
2454             ddwwStr = buf1;
2455             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2456             break;
2457         }
2458         switch (option) {
2459           case TN_ECHO:
2460             optionStr = "ECHO";
2461             break;
2462           default:
2463             optionStr = buf2;
2464             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2465             break;
2466         }
2467         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2468     }
2469     msg[0] = TN_IAC;
2470     msg[1] = ddww;
2471     msg[2] = option;
2472     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2473     if (outCount < 3) {
2474         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2475     }
2476 }
2477
2478 void
2479 DoEcho ()
2480 {
2481     if (!appData.icsActive) return;
2482     TelnetRequest(TN_DO, TN_ECHO);
2483 }
2484
2485 void
2486 DontEcho ()
2487 {
2488     if (!appData.icsActive) return;
2489     TelnetRequest(TN_DONT, TN_ECHO);
2490 }
2491
2492 void
2493 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2494 {
2495     /* put the holdings sent to us by the server on the board holdings area */
2496     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2497     char p;
2498     ChessSquare piece;
2499
2500     if(gameInfo.holdingsWidth < 2)  return;
2501     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2502         return; // prevent overwriting by pre-board holdings
2503
2504     if( (int)lowestPiece >= BlackPawn ) {
2505         holdingsColumn = 0;
2506         countsColumn = 1;
2507         holdingsStartRow = handSize-1;
2508         direction = -1;
2509     } else {
2510         holdingsColumn = BOARD_WIDTH-1;
2511         countsColumn = BOARD_WIDTH-2;
2512         holdingsStartRow = 0;
2513         direction = 1;
2514     }
2515
2516     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2517         board[i][holdingsColumn] = EmptySquare;
2518         board[i][countsColumn]   = (ChessSquare) 0;
2519     }
2520     while( (p=*holdings++) != NULLCHAR ) {
2521         piece = CharToPiece( ToUpper(p) );
2522         if(piece == EmptySquare) continue;
2523         /*j = (int) piece - (int) WhitePawn;*/
2524         j = PieceToNumber(piece);
2525         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2526         if(j < 0) continue;               /* should not happen */
2527         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2528         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2529         board[holdingsStartRow+j*direction][countsColumn]++;
2530     }
2531 }
2532
2533
2534 void
2535 VariantSwitch (Board board, VariantClass newVariant)
2536 {
2537    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2538    static Board oldBoard;
2539
2540    startedFromPositionFile = FALSE;
2541    if(gameInfo.variant == newVariant) return;
2542
2543    /* [HGM] This routine is called each time an assignment is made to
2544     * gameInfo.variant during a game, to make sure the board sizes
2545     * are set to match the new variant. If that means adding or deleting
2546     * holdings, we shift the playing board accordingly
2547     * This kludge is needed because in ICS observe mode, we get boards
2548     * of an ongoing game without knowing the variant, and learn about the
2549     * latter only later. This can be because of the move list we requested,
2550     * in which case the game history is refilled from the beginning anyway,
2551     * but also when receiving holdings of a crazyhouse game. In the latter
2552     * case we want to add those holdings to the already received position.
2553     */
2554
2555
2556    if (appData.debugMode) {
2557      fprintf(debugFP, "Switch board from %s to %s\n",
2558              VariantName(gameInfo.variant), VariantName(newVariant));
2559      setbuf(debugFP, NULL);
2560    }
2561    shuffleOpenings = 0;       /* [HGM] shuffle */
2562    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2563    switch(newVariant)
2564      {
2565      case VariantShogi:
2566        newWidth = 9;  newHeight = 9;
2567        gameInfo.holdingsSize = 7;
2568      case VariantBughouse:
2569      case VariantCrazyhouse:
2570        newHoldingsWidth = 2; break;
2571      case VariantGreat:
2572        newWidth = 10;
2573      case VariantSuper:
2574        newHoldingsWidth = 2;
2575        gameInfo.holdingsSize = 8;
2576        break;
2577      case VariantGothic:
2578      case VariantCapablanca:
2579      case VariantCapaRandom:
2580        newWidth = 10;
2581      default:
2582        newHoldingsWidth = gameInfo.holdingsSize = 0;
2583      };
2584
2585    if(newWidth  != gameInfo.boardWidth  ||
2586       newHeight != gameInfo.boardHeight ||
2587       newHoldingsWidth != gameInfo.holdingsWidth ) {
2588
2589      /* shift position to new playing area, if needed */
2590      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2591        for(i=0; i<BOARD_HEIGHT; i++)
2592          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2593            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2594              board[i][j];
2595        for(i=0; i<newHeight; i++) {
2596          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2597          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2598        }
2599      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2600        for(i=0; i<BOARD_HEIGHT; i++)
2601          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2602            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2603              board[i][j];
2604      }
2605      board[HOLDINGS_SET] = 0;
2606      gameInfo.boardWidth  = newWidth;
2607      gameInfo.boardHeight = newHeight;
2608      gameInfo.holdingsWidth = newHoldingsWidth;
2609      gameInfo.variant = newVariant;
2610      InitDrawingSizes(-2, 0);
2611    } else gameInfo.variant = newVariant;
2612    CopyBoard(oldBoard, board);   // remember correctly formatted board
2613      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2614    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2615 }
2616
2617 static int loggedOn = FALSE;
2618
2619 /*-- Game start info cache: --*/
2620 int gs_gamenum;
2621 char gs_kind[MSG_SIZ];
2622 static char player1Name[128] = "";
2623 static char player2Name[128] = "";
2624 static char cont_seq[] = "\n\\   ";
2625 static int player1Rating = -1;
2626 static int player2Rating = -1;
2627 /*----------------------------*/
2628
2629 ColorClass curColor = ColorNormal;
2630 int suppressKibitz = 0;
2631
2632 // [HGM] seekgraph
2633 Boolean soughtPending = FALSE;
2634 Boolean seekGraphUp;
2635 #define MAX_SEEK_ADS 200
2636 #define SQUARE 0x80
2637 char *seekAdList[MAX_SEEK_ADS];
2638 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2639 float tcList[MAX_SEEK_ADS];
2640 char colorList[MAX_SEEK_ADS];
2641 int nrOfSeekAds = 0;
2642 int minRating = 1010, maxRating = 2800;
2643 int hMargin = 10, vMargin = 20, h, w;
2644 extern int squareSize, lineGap;
2645
2646 void
2647 PlotSeekAd (int i)
2648 {
2649         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2650         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2651         if(r < minRating+100 && r >=0 ) r = minRating+100;
2652         if(r > maxRating) r = maxRating;
2653         if(tc < 1.f) tc = 1.f;
2654         if(tc > 95.f) tc = 95.f;
2655         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2656         y = ((double)r - minRating)/(maxRating - minRating)
2657             * (h-vMargin-squareSize/8-1) + vMargin;
2658         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2659         if(strstr(seekAdList[i], " u ")) color = 1;
2660         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2661            !strstr(seekAdList[i], "bullet") &&
2662            !strstr(seekAdList[i], "blitz") &&
2663            !strstr(seekAdList[i], "standard") ) color = 2;
2664         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2665         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2666 }
2667
2668 void
2669 PlotSingleSeekAd (int i)
2670 {
2671         PlotSeekAd(i);
2672 }
2673
2674 void
2675 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2676 {
2677         char buf[MSG_SIZ], *ext = "";
2678         VariantClass v = StringToVariant(type);
2679         if(strstr(type, "wild")) {
2680             ext = type + 4; // append wild number
2681             if(v == VariantFischeRandom) type = "chess960"; else
2682             if(v == VariantLoadable) type = "setup"; else
2683             type = VariantName(v);
2684         }
2685         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2686         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2687             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2688             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2689             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2690             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2691             seekNrList[nrOfSeekAds] = nr;
2692             zList[nrOfSeekAds] = 0;
2693             seekAdList[nrOfSeekAds++] = StrSave(buf);
2694             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2695         }
2696 }
2697
2698 void
2699 EraseSeekDot (int i)
2700 {
2701     int x = xList[i], y = yList[i], d=squareSize/4, k;
2702     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2703     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2704     // now replot every dot that overlapped
2705     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2706         int xx = xList[k], yy = yList[k];
2707         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2708             DrawSeekDot(xx, yy, colorList[k]);
2709     }
2710 }
2711
2712 void
2713 RemoveSeekAd (int nr)
2714 {
2715         int i;
2716         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2717             EraseSeekDot(i);
2718             if(seekAdList[i]) free(seekAdList[i]);
2719             seekAdList[i] = seekAdList[--nrOfSeekAds];
2720             seekNrList[i] = seekNrList[nrOfSeekAds];
2721             ratingList[i] = ratingList[nrOfSeekAds];
2722             colorList[i]  = colorList[nrOfSeekAds];
2723             tcList[i] = tcList[nrOfSeekAds];
2724             xList[i]  = xList[nrOfSeekAds];
2725             yList[i]  = yList[nrOfSeekAds];
2726             zList[i]  = zList[nrOfSeekAds];
2727             seekAdList[nrOfSeekAds] = NULL;
2728             break;
2729         }
2730 }
2731
2732 Boolean
2733 MatchSoughtLine (char *line)
2734 {
2735     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2736     int nr, base, inc, u=0; char dummy;
2737
2738     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2739        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2740        (u=1) &&
2741        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2742         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2743         // match: compact and save the line
2744         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2745         return TRUE;
2746     }
2747     return FALSE;
2748 }
2749
2750 int
2751 DrawSeekGraph ()
2752 {
2753     int i;
2754     if(!seekGraphUp) return FALSE;
2755     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2756     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2757
2758     DrawSeekBackground(0, 0, w, h);
2759     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2760     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2761     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2762         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2763         yy = h-1-yy;
2764         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2765         if(i%500 == 0) {
2766             char buf[MSG_SIZ];
2767             snprintf(buf, MSG_SIZ, "%d", i);
2768             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2769         }
2770     }
2771     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2772     for(i=1; i<100; i+=(i<10?1:5)) {
2773         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2774         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2775         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2776             char buf[MSG_SIZ];
2777             snprintf(buf, MSG_SIZ, "%d", i);
2778             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2779         }
2780     }
2781     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2782     return TRUE;
2783 }
2784
2785 int
2786 SeekGraphClick (ClickType click, int x, int y, int moving)
2787 {
2788     static int lastDown = 0, displayed = 0, lastSecond;
2789     if(y < 0) return FALSE;
2790     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2791         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2792         if(!seekGraphUp) return FALSE;
2793         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2794         DrawPosition(TRUE, NULL);
2795         return TRUE;
2796     }
2797     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2798         if(click == Release || moving) return FALSE;
2799         nrOfSeekAds = 0;
2800         soughtPending = TRUE;
2801         SendToICS(ics_prefix);
2802         SendToICS("sought\n"); // should this be "sought all"?
2803     } else { // issue challenge based on clicked ad
2804         int dist = 10000; int i, closest = 0, second = 0;
2805         for(i=0; i<nrOfSeekAds; i++) {
2806             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2807             if(d < dist) { dist = d; closest = i; }
2808             second += (d - zList[i] < 120); // count in-range ads
2809             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2810         }
2811         if(dist < 120) {
2812             char buf[MSG_SIZ];
2813             second = (second > 1);
2814             if(displayed != closest || second != lastSecond) {
2815                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2816                 lastSecond = second; displayed = closest;
2817             }
2818             if(click == Press) {
2819                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2820                 lastDown = closest;
2821                 return TRUE;
2822             } // on press 'hit', only show info
2823             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2824             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2825             SendToICS(ics_prefix);
2826             SendToICS(buf);
2827             return TRUE; // let incoming board of started game pop down the graph
2828         } else if(click == Release) { // release 'miss' is ignored
2829             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2830             if(moving == 2) { // right up-click
2831                 nrOfSeekAds = 0; // refresh graph
2832                 soughtPending = TRUE;
2833                 SendToICS(ics_prefix);
2834                 SendToICS("sought\n"); // should this be "sought all"?
2835             }
2836             return TRUE;
2837         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2838         // press miss or release hit 'pop down' seek graph
2839         seekGraphUp = FALSE;
2840         DrawPosition(TRUE, NULL);
2841     }
2842     return TRUE;
2843 }
2844
2845 void
2846 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2847 {
2848 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2849 #define STARTED_NONE 0
2850 #define STARTED_MOVES 1
2851 #define STARTED_BOARD 2
2852 #define STARTED_OBSERVE 3
2853 #define STARTED_HOLDINGS 4
2854 #define STARTED_CHATTER 5
2855 #define STARTED_COMMENT 6
2856 #define STARTED_MOVES_NOHIDE 7
2857
2858     static int started = STARTED_NONE;
2859     static char parse[20000];
2860     static int parse_pos = 0;
2861     static char buf[BUF_SIZE + 1];
2862     static int firstTime = TRUE, intfSet = FALSE;
2863     static ColorClass prevColor = ColorNormal;
2864     static int savingComment = FALSE;
2865     static int cmatch = 0; // continuation sequence match
2866     char *bp;
2867     char str[MSG_SIZ];
2868     int i, oldi;
2869     int buf_len;
2870     int next_out;
2871     int tkind;
2872     int backup;    /* [DM] For zippy color lines */
2873     char *p;
2874     char talker[MSG_SIZ]; // [HGM] chat
2875     int channel, collective=0;
2876
2877     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2878
2879     if (appData.debugMode) {
2880       if (!error) {
2881         fprintf(debugFP, "<ICS: ");
2882         show_bytes(debugFP, data, count);
2883         fprintf(debugFP, "\n");
2884       }
2885     }
2886
2887     if (appData.debugMode) { int f = forwardMostMove;
2888         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2889                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2890                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2891     }
2892     if (count > 0) {
2893         /* If last read ended with a partial line that we couldn't parse,
2894            prepend it to the new read and try again. */
2895         if (leftover_len > 0) {
2896             for (i=0; i<leftover_len; i++)
2897               buf[i] = buf[leftover_start + i];
2898         }
2899
2900     /* copy new characters into the buffer */
2901     bp = buf + leftover_len;
2902     buf_len=leftover_len;
2903     for (i=0; i<count; i++)
2904     {
2905         // ignore these
2906         if (data[i] == '\r')
2907             continue;
2908
2909         // join lines split by ICS?
2910         if (!appData.noJoin)
2911         {
2912             /*
2913                 Joining just consists of finding matches against the
2914                 continuation sequence, and discarding that sequence
2915                 if found instead of copying it.  So, until a match
2916                 fails, there's nothing to do since it might be the
2917                 complete sequence, and thus, something we don't want
2918                 copied.
2919             */
2920             if (data[i] == cont_seq[cmatch])
2921             {
2922                 cmatch++;
2923                 if (cmatch == strlen(cont_seq))
2924                 {
2925                     cmatch = 0; // complete match.  just reset the counter
2926
2927                     /*
2928                         it's possible for the ICS to not include the space
2929                         at the end of the last word, making our [correct]
2930                         join operation fuse two separate words.  the server
2931                         does this when the space occurs at the width setting.
2932                     */
2933                     if (!buf_len || buf[buf_len-1] != ' ')
2934                     {
2935                         *bp++ = ' ';
2936                         buf_len++;
2937                     }
2938                 }
2939                 continue;
2940             }
2941             else if (cmatch)
2942             {
2943                 /*
2944                     match failed, so we have to copy what matched before
2945                     falling through and copying this character.  In reality,
2946                     this will only ever be just the newline character, but
2947                     it doesn't hurt to be precise.
2948                 */
2949                 strncpy(bp, cont_seq, cmatch);
2950                 bp += cmatch;
2951                 buf_len += cmatch;
2952                 cmatch = 0;
2953             }
2954         }
2955
2956         // copy this char
2957         *bp++ = data[i];
2958         buf_len++;
2959     }
2960
2961         buf[buf_len] = NULLCHAR;
2962 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2963         next_out = 0;
2964         leftover_start = 0;
2965
2966         i = 0;
2967         while (i < buf_len) {
2968             /* Deal with part of the TELNET option negotiation
2969                protocol.  We refuse to do anything beyond the
2970                defaults, except that we allow the WILL ECHO option,
2971                which ICS uses to turn off password echoing when we are
2972                directly connected to it.  We reject this option
2973                if localLineEditing mode is on (always on in xboard)
2974                and we are talking to port 23, which might be a real
2975                telnet server that will try to keep WILL ECHO on permanently.
2976              */
2977             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2978                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2979                 unsigned char option;
2980                 oldi = i;
2981                 switch ((unsigned char) buf[++i]) {
2982                   case TN_WILL:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WILL ");
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                         if (appData.localLineEditing &&
2993                             atoi(appData.icsPort) == TN_PORT) {
2994                             TelnetRequest(TN_DONT, TN_ECHO);
2995                         } else {
2996                             EchoOff();
2997                             TelnetRequest(TN_DO, TN_ECHO);
2998                             remoteEchoOption = TRUE;
2999                         }
3000                         break;
3001                       default:
3002                         if (appData.debugMode)
3003                           fprintf(debugFP, "%d ", option);
3004                         /* Whatever this is, we don't want it. */
3005                         TelnetRequest(TN_DONT, option);
3006                         break;
3007                     }
3008                     break;
3009                   case TN_WONT:
3010                     if (appData.debugMode)
3011                       fprintf(debugFP, "\n<WONT ");
3012                     switch (option = (unsigned char) buf[++i]) {
3013                       case TN_ECHO:
3014                         if (appData.debugMode)
3015                           fprintf(debugFP, "ECHO ");
3016                         /* Reply only if this is a change, according
3017                            to the protocol rules. */
3018                         if (!remoteEchoOption) break;
3019                         EchoOn();
3020                         TelnetRequest(TN_DONT, TN_ECHO);
3021                         remoteEchoOption = FALSE;
3022                         break;
3023                       default:
3024                         if (appData.debugMode)
3025                           fprintf(debugFP, "%d ", (unsigned char) option);
3026                         /* Whatever this is, it must already be turned
3027                            off, because we never agree to turn on
3028                            anything non-default, so according to the
3029                            protocol rules, we don't reply. */
3030                         break;
3031                     }
3032                     break;
3033                   case TN_DO:
3034                     if (appData.debugMode)
3035                       fprintf(debugFP, "\n<DO ");
3036                     switch (option = (unsigned char) buf[++i]) {
3037                       default:
3038                         /* Whatever this is, we refuse to do it. */
3039                         if (appData.debugMode)
3040                           fprintf(debugFP, "%d ", option);
3041                         TelnetRequest(TN_WONT, option);
3042                         break;
3043                     }
3044                     break;
3045                   case TN_DONT:
3046                     if (appData.debugMode)
3047                       fprintf(debugFP, "\n<DONT ");
3048                     switch (option = (unsigned char) buf[++i]) {
3049                       default:
3050                         if (appData.debugMode)
3051                           fprintf(debugFP, "%d ", option);
3052                         /* Whatever this is, we are already not doing
3053                            it, because we never agree to do anything
3054                            non-default, so according to the protocol
3055                            rules, we don't reply. */
3056                         break;
3057                     }
3058                     break;
3059                   case TN_IAC:
3060                     if (appData.debugMode)
3061                       fprintf(debugFP, "\n<IAC ");
3062                     /* Doubled IAC; pass it through */
3063                     i--;
3064                     break;
3065                   default:
3066                     if (appData.debugMode)
3067                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3068                     /* Drop all other telnet commands on the floor */
3069                     break;
3070                 }
3071                 if (oldi > next_out)
3072                   SendToPlayer(&buf[next_out], oldi - next_out);
3073                 if (++i > next_out)
3074                   next_out = i;
3075                 continue;
3076             }
3077
3078             /* OK, this at least will *usually* work */
3079             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3080                 loggedOn = TRUE;
3081             }
3082
3083             if (loggedOn && !intfSet) {
3084                 if (ics_type == ICS_ICC) {
3085                   snprintf(str, MSG_SIZ,
3086                           "/set-quietly interface %s\n/set-quietly style 12\n",
3087                           programVersion);
3088                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3089                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3090                 } else if (ics_type == ICS_CHESSNET) {
3091                   snprintf(str, MSG_SIZ, "/style 12\n");
3092                 } else {
3093                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3094                   strcat(str, programVersion);
3095                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3096                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3097                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3098 #ifdef WIN32
3099                   strcat(str, "$iset nohighlight 1\n");
3100 #endif
3101                   strcat(str, "$iset lock 1\n$style 12\n");
3102                 }
3103                 SendToICS(str);
3104                 NotifyFrontendLogin();
3105                 intfSet = TRUE;
3106             }
3107
3108             if (started == STARTED_COMMENT) {
3109                 /* Accumulate characters in comment */
3110                 parse[parse_pos++] = buf[i];
3111                 if (buf[i] == '\n') {
3112                     parse[parse_pos] = NULLCHAR;
3113                     if(chattingPartner>=0) {
3114                         char mess[MSG_SIZ];
3115                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3116                         OutputChatMessage(chattingPartner, mess);
3117                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3118                             int p;
3119                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3120                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3121                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3122                                 OutputChatMessage(p, mess);
3123                                 break;
3124                             }
3125                         }
3126                         chattingPartner = -1;
3127                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3128                         collective = 0;
3129                     } else
3130                     if(!suppressKibitz) // [HGM] kibitz
3131                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3132                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3133                         int nrDigit = 0, nrAlph = 0, j;
3134                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3135                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3136                         parse[parse_pos] = NULLCHAR;
3137                         // try to be smart: if it does not look like search info, it should go to
3138                         // ICS interaction window after all, not to engine-output window.
3139                         for(j=0; j<parse_pos; j++) { // count letters and digits
3140                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3141                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3142                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3143                         }
3144                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3145                             int depth=0; float score;
3146                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3147                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3148                                 pvInfoList[forwardMostMove-1].depth = depth;
3149                                 pvInfoList[forwardMostMove-1].score = 100*score;
3150                             }
3151                             OutputKibitz(suppressKibitz, parse);
3152                         } else {
3153                             char tmp[MSG_SIZ];
3154                             if(gameMode == IcsObserving) // restore original ICS messages
3155                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3156                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3157                             else
3158                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3159                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3160                             SendToPlayer(tmp, strlen(tmp));
3161                         }
3162                         next_out = i+1; // [HGM] suppress printing in ICS window
3163                     }
3164                     started = STARTED_NONE;
3165                 } else {
3166                     /* Don't match patterns against characters in comment */
3167                     i++;
3168                     continue;
3169                 }
3170             }
3171             if (started == STARTED_CHATTER) {
3172                 if (buf[i] != '\n') {
3173                     /* Don't match patterns against characters in chatter */
3174                     i++;
3175                     continue;
3176                 }
3177                 started = STARTED_NONE;
3178                 if(suppressKibitz) next_out = i+1;
3179             }
3180
3181             /* Kludge to deal with rcmd protocol */
3182             if (firstTime && looking_at(buf, &i, "\001*")) {
3183                 DisplayFatalError(&buf[1], 0, 1);
3184                 continue;
3185             } else {
3186                 firstTime = FALSE;
3187             }
3188
3189             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3190                 ics_type = ICS_ICC;
3191                 ics_prefix = "/";
3192                 if (appData.debugMode)
3193                   fprintf(debugFP, "ics_type %d\n", ics_type);
3194                 continue;
3195             }
3196             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3197                 ics_type = ICS_FICS;
3198                 ics_prefix = "$";
3199                 if (appData.debugMode)
3200                   fprintf(debugFP, "ics_type %d\n", ics_type);
3201                 continue;
3202             }
3203             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3204                 ics_type = ICS_CHESSNET;
3205                 ics_prefix = "/";
3206                 if (appData.debugMode)
3207                   fprintf(debugFP, "ics_type %d\n", ics_type);
3208                 continue;
3209             }
3210
3211             if (!loggedOn &&
3212                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3213                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3214                  looking_at(buf, &i, "will be \"*\""))) {
3215               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3216               continue;
3217             }
3218
3219             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3220               char buf[MSG_SIZ];
3221               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3222               DisplayIcsInteractionTitle(buf);
3223               have_set_title = TRUE;
3224             }
3225
3226             /* skip finger notes */
3227             if (started == STARTED_NONE &&
3228                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3229                  (buf[i] == '1' && buf[i+1] == '0')) &&
3230                 buf[i+2] == ':' && buf[i+3] == ' ') {
3231               started = STARTED_CHATTER;
3232               i += 3;
3233               continue;
3234             }
3235
3236             oldi = i;
3237             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3238             if(appData.seekGraph) {
3239                 if(soughtPending && MatchSoughtLine(buf+i)) {
3240                     i = strstr(buf+i, "rated") - buf;
3241                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                     next_out = leftover_start = i;
3243                     started = STARTED_CHATTER;
3244                     suppressKibitz = TRUE;
3245                     continue;
3246                 }
3247                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3248                         && looking_at(buf, &i, "* ads displayed")) {
3249                     soughtPending = FALSE;
3250                     seekGraphUp = TRUE;
3251                     DrawSeekGraph();
3252                     continue;
3253                 }
3254                 if(appData.autoRefresh) {
3255                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3256                         int s = (ics_type == ICS_ICC); // ICC format differs
3257                         if(seekGraphUp)
3258                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3259                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3260                         looking_at(buf, &i, "*% "); // eat prompt
3261                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3262                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3263                         next_out = i; // suppress
3264                         continue;
3265                     }
3266                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3267                         char *p = star_match[0];
3268                         while(*p) {
3269                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3270                             while(*p && *p++ != ' '); // next
3271                         }
3272                         looking_at(buf, &i, "*% "); // eat prompt
3273                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3274                         next_out = i;
3275                         continue;
3276                     }
3277                 }
3278             }
3279
3280             /* skip formula vars */
3281             if (started == STARTED_NONE &&
3282                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3283               started = STARTED_CHATTER;
3284               i += 3;
3285               continue;
3286             }
3287
3288             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3289             if (appData.autoKibitz && started == STARTED_NONE &&
3290                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3291                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3292                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3293                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3294                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3295                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3296                         suppressKibitz = TRUE;
3297                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                         next_out = i;
3299                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3300                                 && (gameMode == IcsPlayingWhite)) ||
3301                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3302                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3303                             started = STARTED_CHATTER; // own kibitz we simply discard
3304                         else {
3305                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3306                             parse_pos = 0; parse[0] = NULLCHAR;
3307                             savingComment = TRUE;
3308                             suppressKibitz = gameMode != IcsObserving ? 2 :
3309                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3310                         }
3311                         continue;
3312                 } else
3313                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3314                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3315                          && atoi(star_match[0])) {
3316                     // suppress the acknowledgements of our own autoKibitz
3317                     char *p;
3318                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3319                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3320                     SendToPlayer(star_match[0], strlen(star_match[0]));
3321                     if(looking_at(buf, &i, "*% ")) // eat prompt
3322                         suppressKibitz = FALSE;
3323                     next_out = i;
3324                     continue;
3325                 }
3326             } // [HGM] kibitz: end of patch
3327
3328             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3329
3330             // [HGM] chat: intercept tells by users for which we have an open chat window
3331             channel = -1;
3332             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3333                                            looking_at(buf, &i, "* whispers:") ||
3334                                            looking_at(buf, &i, "* kibitzes:") ||
3335                                            looking_at(buf, &i, "* shouts:") ||
3336                                            looking_at(buf, &i, "* c-shouts:") ||
3337                                            looking_at(buf, &i, "--> * ") ||
3338                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3339                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3340                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3341                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3342                 int p;
3343                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3344                 chattingPartner = -1; collective = 0;
3345
3346                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3347                 for(p=0; p<MAX_CHAT; p++) {
3348                     collective = 1;
3349                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3350                     talker[0] = '['; strcat(talker, "] ");
3351                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3352                     chattingPartner = p; break;
3353                     }
3354                 } else
3355                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3356                 for(p=0; p<MAX_CHAT; p++) {
3357                     collective = 1;
3358                     if(!strcmp("kibitzes", chatPartner[p])) {
3359                         talker[0] = '['; strcat(talker, "] ");
3360                         chattingPartner = p; break;
3361                     }
3362                 } else
3363                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3364                 for(p=0; p<MAX_CHAT; p++) {
3365                     collective = 1;
3366                     if(!strcmp("whispers", chatPartner[p])) {
3367                         talker[0] = '['; strcat(talker, "] ");
3368                         chattingPartner = p; break;
3369                     }
3370                 } else
3371                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3372                   if(buf[i-8] == '-' && buf[i-3] == 't')
3373                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3374                     collective = 1;
3375                     if(!strcmp("c-shouts", chatPartner[p])) {
3376                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3377                         chattingPartner = p; break;
3378                     }
3379                   }
3380                   if(chattingPartner < 0)
3381                   for(p=0; p<MAX_CHAT; p++) {
3382                     collective = 1;
3383                     if(!strcmp("shouts", chatPartner[p])) {
3384                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3385                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3386                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3387                         chattingPartner = p; break;
3388                     }
3389                   }
3390                 }
3391                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3392                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3393                     talker[0] = 0;
3394                     Colorize(ColorTell, FALSE);
3395                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3396                     collective |= 2;
3397                     chattingPartner = p; break;
3398                 }
3399                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3400                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3401                     started = STARTED_COMMENT;
3402                     parse_pos = 0; parse[0] = NULLCHAR;
3403                     savingComment = 3 + chattingPartner; // counts as TRUE
3404                     if(collective == 3) i = oldi; else {
3405                         suppressKibitz = TRUE;
3406                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3407                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3408                         continue;
3409                     }
3410                 }
3411             } // [HGM] chat: end of patch
3412
3413           backup = i;
3414             if (appData.zippyTalk || appData.zippyPlay) {
3415                 /* [DM] Backup address for color zippy lines */
3416 #if ZIPPY
3417                if (loggedOn == TRUE)
3418                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3419                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3420                        ;
3421 #endif
3422             } // [DM] 'else { ' deleted
3423                 if (
3424                     /* Regular tells and says */
3425                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3426                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3427                     looking_at(buf, &i, "* says: ") ||
3428                     /* Don't color "message" or "messages" output */
3429                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3430                     looking_at(buf, &i, "*. * at *:*: ") ||
3431                     looking_at(buf, &i, "--* (*:*): ") ||
3432                     /* Message notifications (same color as tells) */
3433                     looking_at(buf, &i, "* has left a message ") ||
3434                     looking_at(buf, &i, "* just sent you a message:\n") ||
3435                     /* Whispers and kibitzes */
3436                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3437                     looking_at(buf, &i, "* kibitzes: ") ||
3438                     /* Channel tells */
3439                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3440
3441                   if (tkind == 1 && strchr(star_match[0], ':')) {
3442                       /* Avoid "tells you:" spoofs in channels */
3443                      tkind = 3;
3444                   }
3445                   if (star_match[0][0] == NULLCHAR ||
3446                       strchr(star_match[0], ' ') ||
3447                       (tkind == 3 && strchr(star_match[1], ' '))) {
3448                     /* Reject bogus matches */
3449                     i = oldi;
3450                   } else {
3451                     if (appData.colorize) {
3452                       if (oldi > next_out) {
3453                         SendToPlayer(&buf[next_out], oldi - next_out);
3454                         next_out = oldi;
3455                       }
3456                       switch (tkind) {
3457                       case 1:
3458                         Colorize(ColorTell, FALSE);
3459                         curColor = ColorTell;
3460                         break;
3461                       case 2:
3462                         Colorize(ColorKibitz, FALSE);
3463                         curColor = ColorKibitz;
3464                         break;
3465                       case 3:
3466                         p = strrchr(star_match[1], '(');
3467                         if (p == NULL) {
3468                           p = star_match[1];
3469                         } else {
3470                           p++;
3471                         }
3472                         if (atoi(p) == 1) {
3473                           Colorize(ColorChannel1, FALSE);
3474                           curColor = ColorChannel1;
3475                         } else {
3476                           Colorize(ColorChannel, FALSE);
3477                           curColor = ColorChannel;
3478                         }
3479                         break;
3480                       case 5:
3481                         curColor = ColorNormal;
3482                         break;
3483                       }
3484                     }
3485                     if (started == STARTED_NONE && appData.autoComment &&
3486                         (gameMode == IcsObserving ||
3487                          gameMode == IcsPlayingWhite ||
3488                          gameMode == IcsPlayingBlack)) {
3489                       parse_pos = i - oldi;
3490                       memcpy(parse, &buf[oldi], parse_pos);
3491                       parse[parse_pos] = NULLCHAR;
3492                       started = STARTED_COMMENT;
3493                       savingComment = TRUE;
3494                     } else if(collective != 3) {
3495                       started = STARTED_CHATTER;
3496                       savingComment = FALSE;
3497                     }
3498                     loggedOn = TRUE;
3499                     continue;
3500                   }
3501                 }
3502
3503                 if (looking_at(buf, &i, "* s-shouts: ") ||
3504                     looking_at(buf, &i, "* c-shouts: ")) {
3505                     if (appData.colorize) {
3506                         if (oldi > next_out) {
3507                             SendToPlayer(&buf[next_out], oldi - next_out);
3508                             next_out = oldi;
3509                         }
3510                         Colorize(ColorSShout, FALSE);
3511                         curColor = ColorSShout;
3512                     }
3513                     loggedOn = TRUE;
3514                     started = STARTED_CHATTER;
3515                     continue;
3516                 }
3517
3518                 if (looking_at(buf, &i, "--->")) {
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* shouts: ") ||
3524                     looking_at(buf, &i, "--> ")) {
3525                     if (appData.colorize) {
3526                         if (oldi > next_out) {
3527                             SendToPlayer(&buf[next_out], oldi - next_out);
3528                             next_out = oldi;
3529                         }
3530                         Colorize(ColorShout, FALSE);
3531                         curColor = ColorShout;
3532                     }
3533                     loggedOn = TRUE;
3534                     started = STARTED_CHATTER;
3535                     continue;
3536                 }
3537
3538                 if (looking_at( buf, &i, "Challenge:")) {
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorChallenge, FALSE);
3545                         curColor = ColorChallenge;
3546                     }
3547                     loggedOn = TRUE;
3548                     continue;
3549                 }
3550
3551                 if (looking_at(buf, &i, "* offers you") ||
3552                     looking_at(buf, &i, "* offers to be") ||
3553                     looking_at(buf, &i, "* would like to") ||
3554                     looking_at(buf, &i, "* requests to") ||
3555                     looking_at(buf, &i, "Your opponent offers") ||
3556                     looking_at(buf, &i, "Your opponent requests")) {
3557
3558                     if (appData.colorize) {
3559                         if (oldi > next_out) {
3560                             SendToPlayer(&buf[next_out], oldi - next_out);
3561                             next_out = oldi;
3562                         }
3563                         Colorize(ColorRequest, FALSE);
3564                         curColor = ColorRequest;
3565                     }
3566                     continue;
3567                 }
3568
3569                 if (looking_at(buf, &i, "* (*) seeking")) {
3570                     if (appData.colorize) {
3571                         if (oldi > next_out) {
3572                             SendToPlayer(&buf[next_out], oldi - next_out);
3573                             next_out = oldi;
3574                         }
3575                         Colorize(ColorSeek, FALSE);
3576                         curColor = ColorSeek;
3577                     }
3578                     continue;
3579             }
3580
3581           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3582
3583             if (looking_at(buf, &i, "\\   ")) {
3584                 if (prevColor != ColorNormal) {
3585                     if (oldi > next_out) {
3586                         SendToPlayer(&buf[next_out], oldi - next_out);
3587                         next_out = oldi;
3588                     }
3589                     Colorize(prevColor, TRUE);
3590                     curColor = prevColor;
3591                 }
3592                 if (savingComment) {
3593                     parse_pos = i - oldi;
3594                     memcpy(parse, &buf[oldi], parse_pos);
3595                     parse[parse_pos] = NULLCHAR;
3596                     started = STARTED_COMMENT;
3597                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3598                         chattingPartner = savingComment - 3; // kludge to remember the box
3599                 } else {
3600                     started = STARTED_CHATTER;
3601                 }
3602                 continue;
3603             }
3604
3605             if (looking_at(buf, &i, "Black Strength :") ||
3606                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3607                 looking_at(buf, &i, "<10>") ||
3608                 looking_at(buf, &i, "#@#")) {
3609                 /* Wrong board style */
3610                 loggedOn = TRUE;
3611                 SendToICS(ics_prefix);
3612                 SendToICS("set style 12\n");
3613                 SendToICS(ics_prefix);
3614                 SendToICS("refresh\n");
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "login:")) {
3619               if (!have_sent_ICS_logon) {
3620                 if(ICSInitScript())
3621                   have_sent_ICS_logon = 1;
3622                 else // no init script was found
3623                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3624               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3625                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3626               }
3627                 continue;
3628             }
3629
3630             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3631                 (looking_at(buf, &i, "\n<12> ") ||
3632                  looking_at(buf, &i, "<12> "))) {
3633                 loggedOn = TRUE;
3634                 if (oldi > next_out) {
3635                     SendToPlayer(&buf[next_out], oldi - next_out);
3636                 }
3637                 next_out = i;
3638                 started = STARTED_BOARD;
3639                 parse_pos = 0;
3640                 continue;
3641             }
3642
3643             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3644                 looking_at(buf, &i, "<b1> ")) {
3645                 if (oldi > next_out) {
3646                     SendToPlayer(&buf[next_out], oldi - next_out);
3647                 }
3648                 next_out = i;
3649                 started = STARTED_HOLDINGS;
3650                 parse_pos = 0;
3651                 continue;
3652             }
3653
3654             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3655                 loggedOn = TRUE;
3656                 /* Header for a move list -- first line */
3657
3658                 switch (ics_getting_history) {
3659                   case H_FALSE:
3660                     switch (gameMode) {
3661                       case IcsIdle:
3662                       case BeginningOfGame:
3663                         /* User typed "moves" or "oldmoves" while we
3664                            were idle.  Pretend we asked for these
3665                            moves and soak them up so user can step
3666                            through them and/or save them.
3667                            */
3668                         Reset(FALSE, TRUE);
3669                         gameMode = IcsObserving;
3670                         ModeHighlight();
3671                         ics_gamenum = -1;
3672                         ics_getting_history = H_GOT_UNREQ_HEADER;
3673                         break;
3674                       case EditGame: /*?*/
3675                       case EditPosition: /*?*/
3676                         /* Should above feature work in these modes too? */
3677                         /* For now it doesn't */
3678                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3679                         break;
3680                       default:
3681                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3682                         break;
3683                     }
3684                     break;
3685                   case H_REQUESTED:
3686                     /* Is this the right one? */
3687                     if (gameInfo.white && gameInfo.black &&
3688                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3689                         strcmp(gameInfo.black, star_match[2]) == 0) {
3690                         /* All is well */
3691                         ics_getting_history = H_GOT_REQ_HEADER;
3692                     }
3693                     break;
3694                   case H_GOT_REQ_HEADER:
3695                   case H_GOT_UNREQ_HEADER:
3696                   case H_GOT_UNWANTED_HEADER:
3697                   case H_GETTING_MOVES:
3698                     /* Should not happen */
3699                     DisplayError(_("Error gathering move list: two headers"), 0);
3700                     ics_getting_history = H_FALSE;
3701                     break;
3702                 }
3703
3704                 /* Save player ratings into gameInfo if needed */
3705                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3706                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3707                     (gameInfo.whiteRating == -1 ||
3708                      gameInfo.blackRating == -1)) {
3709
3710                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3711                     gameInfo.blackRating = string_to_rating(star_match[3]);
3712                     if (appData.debugMode)
3713                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3714                               gameInfo.whiteRating, gameInfo.blackRating);
3715                 }
3716                 continue;
3717             }
3718
3719             if (looking_at(buf, &i,
3720               "* * match, initial time: * minute*, increment: * second")) {
3721                 /* Header for a move list -- second line */
3722                 /* Initial board will follow if this is a wild game */
3723                 if (gameInfo.event != NULL) free(gameInfo.event);
3724                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3725                 gameInfo.event = StrSave(str);
3726                 /* [HGM] we switched variant. Translate boards if needed. */
3727                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3728                 continue;
3729             }
3730
3731             if (looking_at(buf, &i, "Move  ")) {
3732                 /* Beginning of a move list */
3733                 switch (ics_getting_history) {
3734                   case H_FALSE:
3735                     /* Normally should not happen */
3736                     /* Maybe user hit reset while we were parsing */
3737                     break;
3738                   case H_REQUESTED:
3739                     /* Happens if we are ignoring a move list that is not
3740                      * the one we just requested.  Common if the user
3741                      * tries to observe two games without turning off
3742                      * getMoveList */
3743                     break;
3744                   case H_GETTING_MOVES:
3745                     /* Should not happen */
3746                     DisplayError(_("Error gathering move list: nested"), 0);
3747                     ics_getting_history = H_FALSE;
3748                     break;
3749                   case H_GOT_REQ_HEADER:
3750                     ics_getting_history = H_GETTING_MOVES;
3751                     started = STARTED_MOVES;
3752                     parse_pos = 0;
3753                     if (oldi > next_out) {
3754                         SendToPlayer(&buf[next_out], oldi - next_out);
3755                     }
3756                     break;
3757                   case H_GOT_UNREQ_HEADER:
3758                     ics_getting_history = H_GETTING_MOVES;
3759                     started = STARTED_MOVES_NOHIDE;
3760                     parse_pos = 0;
3761                     break;
3762                   case H_GOT_UNWANTED_HEADER:
3763                     ics_getting_history = H_FALSE;
3764                     break;
3765                 }
3766                 continue;
3767             }
3768
3769             if (looking_at(buf, &i, "% ") ||
3770                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3771                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3772                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3773                     soughtPending = FALSE;
3774                     seekGraphUp = TRUE;
3775                     DrawSeekGraph();
3776                 }
3777                 if(suppressKibitz) next_out = i;
3778                 savingComment = FALSE;
3779                 suppressKibitz = 0;
3780                 switch (started) {
3781                   case STARTED_MOVES:
3782                   case STARTED_MOVES_NOHIDE:
3783                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3784                     parse[parse_pos + i - oldi] = NULLCHAR;
3785                     ParseGameHistory(parse);
3786 #if ZIPPY
3787                     if (appData.zippyPlay && first.initDone) {
3788                         FeedMovesToProgram(&first, forwardMostMove);
3789                         if (gameMode == IcsPlayingWhite) {
3790                             if (WhiteOnMove(forwardMostMove)) {
3791                                 if (first.sendTime) {
3792                                   if (first.useColors) {
3793                                     SendToProgram("black\n", &first);
3794                                   }
3795                                   SendTimeRemaining(&first, TRUE);
3796                                 }
3797                                 if (first.useColors) {
3798                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3799                                 }
3800                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3801                                 first.maybeThinking = TRUE;
3802                             } else {
3803                                 if (first.usePlayother) {
3804                                   if (first.sendTime) {
3805                                     SendTimeRemaining(&first, TRUE);
3806                                   }
3807                                   SendToProgram("playother\n", &first);
3808                                   firstMove = FALSE;
3809                                 } else {
3810                                   firstMove = TRUE;
3811                                 }
3812                             }
3813                         } else if (gameMode == IcsPlayingBlack) {
3814                             if (!WhiteOnMove(forwardMostMove)) {
3815                                 if (first.sendTime) {
3816                                   if (first.useColors) {
3817                                     SendToProgram("white\n", &first);
3818                                   }
3819                                   SendTimeRemaining(&first, FALSE);
3820                                 }
3821                                 if (first.useColors) {
3822                                   SendToProgram("black\n", &first);
3823                                 }
3824                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3825                                 first.maybeThinking = TRUE;
3826                             } else {
3827                                 if (first.usePlayother) {
3828                                   if (first.sendTime) {
3829                                     SendTimeRemaining(&first, FALSE);
3830                                   }
3831                                   SendToProgram("playother\n", &first);
3832                                   firstMove = FALSE;
3833                                 } else {
3834                                   firstMove = TRUE;
3835                                 }
3836                             }
3837                         }
3838                     }
3839 #endif
3840                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3841                         /* Moves came from oldmoves or moves command
3842                            while we weren't doing anything else.
3843                            */
3844                         currentMove = forwardMostMove;
3845                         ClearHighlights();/*!!could figure this out*/
3846                         flipView = appData.flipView;
3847                         DrawPosition(TRUE, boards[currentMove]);
3848                         DisplayBothClocks();
3849                         snprintf(str, MSG_SIZ, "%s %s %s",
3850                                 gameInfo.white, _("vs."),  gameInfo.black);
3851                         DisplayTitle(str);
3852                         gameMode = IcsIdle;
3853                     } else {
3854                         /* Moves were history of an active game */
3855                         if (gameInfo.resultDetails != NULL) {
3856                             free(gameInfo.resultDetails);
3857                             gameInfo.resultDetails = NULL;
3858                         }
3859                     }
3860                     HistorySet(parseList, backwardMostMove,
3861                                forwardMostMove, currentMove-1);
3862                     DisplayMove(currentMove - 1);
3863                     if (started == STARTED_MOVES) next_out = i;
3864                     started = STARTED_NONE;
3865                     ics_getting_history = H_FALSE;
3866                     break;
3867
3868                   case STARTED_OBSERVE:
3869                     started = STARTED_NONE;
3870                     SendToICS(ics_prefix);
3871                     SendToICS("refresh\n");
3872                     break;
3873
3874                   default:
3875                     break;
3876                 }
3877                 if(bookHit) { // [HGM] book: simulate book reply
3878                     static char bookMove[MSG_SIZ]; // a bit generous?
3879
3880                     programStats.nodes = programStats.depth = programStats.time =
3881                     programStats.score = programStats.got_only_move = 0;
3882                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3883
3884                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3885                     strcat(bookMove, bookHit);
3886                     HandleMachineMove(bookMove, &first);
3887                 }
3888                 continue;
3889             }
3890
3891             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3892                  started == STARTED_HOLDINGS ||
3893                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3894                 /* Accumulate characters in move list or board */
3895                 parse[parse_pos++] = buf[i];
3896             }
3897
3898             /* Start of game messages.  Mostly we detect start of game
3899                when the first board image arrives.  On some versions
3900                of the ICS, though, we need to do a "refresh" after starting
3901                to observe in order to get the current board right away. */
3902             if (looking_at(buf, &i, "Adding game * to observation list")) {
3903                 started = STARTED_OBSERVE;
3904                 continue;
3905             }
3906
3907             /* Handle auto-observe */
3908             if (appData.autoObserve &&
3909                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3910                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3911                 char *player;
3912                 /* Choose the player that was highlighted, if any. */
3913                 if (star_match[0][0] == '\033' ||
3914                     star_match[1][0] != '\033') {
3915                     player = star_match[0];
3916                 } else {
3917                     player = star_match[2];
3918                 }
3919                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3920                         ics_prefix, StripHighlightAndTitle(player));
3921                 SendToICS(str);
3922
3923                 /* Save ratings from notify string */
3924                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3925                 player1Rating = string_to_rating(star_match[1]);
3926                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3927                 player2Rating = string_to_rating(star_match[3]);
3928
3929                 if (appData.debugMode)
3930                   fprintf(debugFP,
3931                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3932                           player1Name, player1Rating,
3933                           player2Name, player2Rating);
3934
3935                 continue;
3936             }
3937
3938             /* Deal with automatic examine mode after a game,
3939                and with IcsObserving -> IcsExamining transition */
3940             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3941                 looking_at(buf, &i, "has made you an examiner of game *")) {
3942
3943                 int gamenum = atoi(star_match[0]);
3944                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3945                     gamenum == ics_gamenum) {
3946                     /* We were already playing or observing this game;
3947                        no need to refetch history */
3948                     gameMode = IcsExamining;
3949                     if (pausing) {
3950                         pauseExamForwardMostMove = forwardMostMove;
3951                     } else if (currentMove < forwardMostMove) {
3952                         ForwardInner(forwardMostMove);
3953                     }
3954                 } else {
3955                     /* I don't think this case really can happen */
3956                     SendToICS(ics_prefix);
3957                     SendToICS("refresh\n");
3958                 }
3959                 continue;
3960             }
3961
3962             /* Error messages */
3963 //          if (ics_user_moved) {
3964             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3965                 if (looking_at(buf, &i, "Illegal move") ||
3966                     looking_at(buf, &i, "Not a legal move") ||
3967                     looking_at(buf, &i, "Your king is in check") ||
3968                     looking_at(buf, &i, "It isn't your turn") ||
3969                     looking_at(buf, &i, "It is not your move")) {
3970                     /* Illegal move */
3971                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3972                         currentMove = forwardMostMove-1;
3973                         DisplayMove(currentMove - 1); /* before DMError */
3974                         DrawPosition(FALSE, boards[currentMove]);
3975                         SwitchClocks(forwardMostMove-1); // [HGM] race
3976                         DisplayBothClocks();
3977                     }
3978                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3979                     ics_user_moved = 0;
3980                     continue;
3981                 }
3982             }
3983
3984             if (looking_at(buf, &i, "still have time") ||
3985                 looking_at(buf, &i, "not out of time") ||
3986                 looking_at(buf, &i, "either player is out of time") ||
3987                 looking_at(buf, &i, "has timeseal; checking")) {
3988                 /* We must have called his flag a little too soon */
3989                 whiteFlag = blackFlag = FALSE;
3990                 continue;
3991             }
3992
3993             if (looking_at(buf, &i, "added * seconds to") ||
3994                 looking_at(buf, &i, "seconds were added to")) {
3995                 /* Update the clocks */
3996                 SendToICS(ics_prefix);
3997                 SendToICS("refresh\n");
3998                 continue;
3999             }
4000
4001             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
4002                 ics_clock_paused = TRUE;
4003                 StopClocks();
4004                 continue;
4005             }
4006
4007             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
4008                 ics_clock_paused = FALSE;
4009                 StartClocks();
4010                 continue;
4011             }
4012
4013             /* Grab player ratings from the Creating: message.
4014                Note we have to check for the special case when
4015                the ICS inserts things like [white] or [black]. */
4016             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4017                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4018                 /* star_matches:
4019                    0    player 1 name (not necessarily white)
4020                    1    player 1 rating
4021                    2    empty, white, or black (IGNORED)
4022                    3    player 2 name (not necessarily black)
4023                    4    player 2 rating
4024
4025                    The names/ratings are sorted out when the game
4026                    actually starts (below).
4027                 */
4028                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4029                 player1Rating = string_to_rating(star_match[1]);
4030                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4031                 player2Rating = string_to_rating(star_match[4]);
4032
4033                 if (appData.debugMode)
4034                   fprintf(debugFP,
4035                           "Ratings from 'Creating:' %s %d, %s %d\n",
4036                           player1Name, player1Rating,
4037                           player2Name, player2Rating);
4038
4039                 continue;
4040             }
4041
4042             /* Improved generic start/end-of-game messages */
4043             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4044                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4045                 /* If tkind == 0: */
4046                 /* star_match[0] is the game number */
4047                 /*           [1] is the white player's name */
4048                 /*           [2] is the black player's name */
4049                 /* For end-of-game: */
4050                 /*           [3] is the reason for the game end */
4051                 /*           [4] is a PGN end game-token, preceded by " " */
4052                 /* For start-of-game: */
4053                 /*           [3] begins with "Creating" or "Continuing" */
4054                 /*           [4] is " *" or empty (don't care). */
4055                 int gamenum = atoi(star_match[0]);
4056                 char *whitename, *blackname, *why, *endtoken;
4057                 ChessMove endtype = EndOfFile;
4058
4059                 if (tkind == 0) {
4060                   whitename = star_match[1];
4061                   blackname = star_match[2];
4062                   why = star_match[3];
4063                   endtoken = star_match[4];
4064                 } else {
4065                   whitename = star_match[1];
4066                   blackname = star_match[3];
4067                   why = star_match[5];
4068                   endtoken = star_match[6];
4069                 }
4070
4071                 /* Game start messages */
4072                 if (strncmp(why, "Creating ", 9) == 0 ||
4073                     strncmp(why, "Continuing ", 11) == 0) {
4074                     gs_gamenum = gamenum;
4075                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4076                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4077                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4078 #if ZIPPY
4079                     if (appData.zippyPlay) {
4080                         ZippyGameStart(whitename, blackname);
4081                     }
4082 #endif /*ZIPPY*/
4083                     partnerBoardValid = FALSE; // [HGM] bughouse
4084                     continue;
4085                 }
4086
4087                 /* Game end messages */
4088                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4089                     ics_gamenum != gamenum) {
4090                     continue;
4091                 }
4092                 while (endtoken[0] == ' ') endtoken++;
4093                 switch (endtoken[0]) {
4094                   case '*':
4095                   default:
4096                     endtype = GameUnfinished;
4097                     break;
4098                   case '0':
4099                     endtype = BlackWins;
4100                     break;
4101                   case '1':
4102                     if (endtoken[1] == '/')
4103                       endtype = GameIsDrawn;
4104                     else
4105                       endtype = WhiteWins;
4106                     break;
4107                 }
4108                 GameEnds(endtype, why, GE_ICS);
4109 #if ZIPPY
4110                 if (appData.zippyPlay && first.initDone) {
4111                     ZippyGameEnd(endtype, why);
4112                     if (first.pr == NoProc) {
4113                       /* Start the next process early so that we'll
4114                          be ready for the next challenge */
4115                       StartChessProgram(&first);
4116                     }
4117                     /* Send "new" early, in case this command takes
4118                        a long time to finish, so that we'll be ready
4119                        for the next challenge. */
4120                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4121                     Reset(TRUE, TRUE);
4122                 }
4123 #endif /*ZIPPY*/
4124                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4125                 continue;
4126             }
4127
4128             if (looking_at(buf, &i, "Removing game * from observation") ||
4129                 looking_at(buf, &i, "no longer observing game *") ||
4130                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4131                 if (gameMode == IcsObserving &&
4132                     atoi(star_match[0]) == ics_gamenum)
4133                   {
4134                       /* icsEngineAnalyze */
4135                       if (appData.icsEngineAnalyze) {
4136                             ExitAnalyzeMode();
4137                             ModeHighlight();
4138                       }
4139                       StopClocks();
4140                       gameMode = IcsIdle;
4141                       ics_gamenum = -1;
4142                       ics_user_moved = FALSE;
4143                   }
4144                 continue;
4145             }
4146
4147             if (looking_at(buf, &i, "no longer examining game *")) {
4148                 if (gameMode == IcsExamining &&
4149                     atoi(star_match[0]) == ics_gamenum)
4150                   {
4151                       gameMode = IcsIdle;
4152                       ics_gamenum = -1;
4153                       ics_user_moved = FALSE;
4154                   }
4155                 continue;
4156             }
4157
4158             /* Advance leftover_start past any newlines we find,
4159                so only partial lines can get reparsed */
4160             if (looking_at(buf, &i, "\n")) {
4161                 prevColor = curColor;
4162                 if (curColor != ColorNormal) {
4163                     if (oldi > next_out) {
4164                         SendToPlayer(&buf[next_out], oldi - next_out);
4165                         next_out = oldi;
4166                     }
4167                     Colorize(ColorNormal, FALSE);
4168                     curColor = ColorNormal;
4169                 }
4170                 if (started == STARTED_BOARD) {
4171                     started = STARTED_NONE;
4172                     parse[parse_pos] = NULLCHAR;
4173                     ParseBoard12(parse);
4174                     ics_user_moved = 0;
4175
4176                     /* Send premove here */
4177                     if (appData.premove) {
4178                       char str[MSG_SIZ];
4179                       if (currentMove == 0 &&
4180                           gameMode == IcsPlayingWhite &&
4181                           appData.premoveWhite) {
4182                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4183                         if (appData.debugMode)
4184                           fprintf(debugFP, "Sending premove:\n");
4185                         SendToICS(str);
4186                       } else if (currentMove == 1 &&
4187                                  gameMode == IcsPlayingBlack &&
4188                                  appData.premoveBlack) {
4189                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4190                         if (appData.debugMode)
4191                           fprintf(debugFP, "Sending premove:\n");
4192                         SendToICS(str);
4193                       } else if (gotPremove) {
4194                         int oldFMM = forwardMostMove;
4195                         gotPremove = 0;
4196                         ClearPremoveHighlights();
4197                         if (appData.debugMode)
4198                           fprintf(debugFP, "Sending premove:\n");
4199                           UserMoveEvent(premoveFromX, premoveFromY,
4200                                         premoveToX, premoveToY,
4201                                         premovePromoChar);
4202                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4203                           if(moveList[oldFMM-1][1] != '@')
4204                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4205                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4206                           else // (drop)
4207                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4208                         }
4209                       }
4210                     }
4211
4212                     /* Usually suppress following prompt */
4213                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4214                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4215                         if (looking_at(buf, &i, "*% ")) {
4216                             savingComment = FALSE;
4217                             suppressKibitz = 0;
4218                         }
4219                     }
4220                     next_out = i;
4221                 } else if (started == STARTED_HOLDINGS) {
4222                     int gamenum;
4223                     char new_piece[MSG_SIZ];
4224                     started = STARTED_NONE;
4225                     parse[parse_pos] = NULLCHAR;
4226                     if (appData.debugMode)
4227                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4228                                                         parse, currentMove);
4229                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4230                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4231                         new_piece[0] = NULLCHAR;
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         if (gameInfo.variant == VariantNormal) {
4238                           /* [HGM] We seem to switch variant during a game!
4239                            * Presumably no holdings were displayed, so we have
4240                            * to move the position two files to the right to
4241                            * create room for them!
4242                            */
4243                           VariantClass newVariant;
4244                           switch(gameInfo.boardWidth) { // base guess on board width
4245                                 case 9:  newVariant = VariantShogi; break;
4246                                 case 10: newVariant = VariantGreat; break;
4247                                 default: newVariant = VariantCrazyhouse;
4248                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4249                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4250                                          newVariant = VariantSChess;
4251                           }
4252                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4253                           /* Get a move list just to see the header, which
4254                              will tell us whether this is really bug or zh */
4255                           if (ics_getting_history == H_FALSE) {
4256                             ics_getting_history = H_REQUESTED;
4257                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4258                             SendToICS(str);
4259                           }
4260                         }
4261                         /* [HGM] copy holdings to board holdings area */
4262                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4263                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4264                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4265 #if ZIPPY
4266                         if (appData.zippyPlay && first.initDone) {
4267                             ZippyHoldings(white_holding, black_holding,
4268                                           new_piece);
4269                         }
4270 #endif /*ZIPPY*/
4271                         if (tinyLayout || smallLayout) {
4272                             char wh[16], bh[16];
4273                             PackHolding(wh, white_holding);
4274                             PackHolding(bh, black_holding);
4275                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4276                                     gameInfo.white, gameInfo.black);
4277                         } else {
4278                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4279                                     gameInfo.white, white_holding, _("vs."),
4280                                     gameInfo.black, black_holding);
4281                         }
4282                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4283                         DrawPosition(FALSE, boards[currentMove]);
4284                         DisplayTitle(str);
4285                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4286                         sscanf(parse, "game %d white [%s black [%s <- %s",
4287                                &gamenum, white_holding, black_holding,
4288                                new_piece);
4289                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4290                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4291                         /* [HGM] copy holdings to partner-board holdings area */
4292                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4293                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4294                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4295                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4296                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4297                       }
4298                     }
4299                     /* Suppress following prompt */
4300                     if (looking_at(buf, &i, "*% ")) {
4301                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4302                         savingComment = FALSE;
4303                         suppressKibitz = 0;
4304                     }
4305                     next_out = i;
4306                 }
4307                 continue;
4308             }
4309
4310             i++;                /* skip unparsed character and loop back */
4311         }
4312
4313         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4314 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4315 //          SendToPlayer(&buf[next_out], i - next_out);
4316             started != STARTED_HOLDINGS && leftover_start > next_out) {
4317             SendToPlayer(&buf[next_out], leftover_start - next_out);
4318             next_out = i;
4319         }
4320
4321         leftover_len = buf_len - leftover_start;
4322         /* if buffer ends with something we couldn't parse,
4323            reparse it after appending the next read */
4324
4325     } else if (count == 0) {
4326         RemoveInputSource(isr);
4327         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4328     } else {
4329         DisplayFatalError(_("Error reading from ICS"), error, 1);
4330     }
4331 }
4332
4333
4334 /* Board style 12 looks like this:
4335
4336    <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
4337
4338  * The "<12> " is stripped before it gets to this routine.  The two
4339  * trailing 0's (flip state and clock ticking) are later addition, and
4340  * some chess servers may not have them, or may have only the first.
4341  * Additional trailing fields may be added in the future.
4342  */
4343
4344 #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"
4345
4346 #define RELATION_OBSERVING_PLAYED    0
4347 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4348 #define RELATION_PLAYING_MYMOVE      1
4349 #define RELATION_PLAYING_NOTMYMOVE  -1
4350 #define RELATION_EXAMINING           2
4351 #define RELATION_ISOLATED_BOARD     -3
4352 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4353
4354 void
4355 ParseBoard12 (char *string)
4356 {
4357 #if ZIPPY
4358     int i, takeback;
4359     char *bookHit = NULL; // [HGM] book
4360 #endif
4361     GameMode newGameMode;
4362     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4363     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4364     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4365     char to_play, board_chars[200];
4366     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4367     char black[32], white[32];
4368     Board board;
4369     int prevMove = currentMove;
4370     int ticking = 2;
4371     ChessMove moveType;
4372     int fromX, fromY, toX, toY;
4373     char promoChar;
4374     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4375     Boolean weird = FALSE, reqFlag = FALSE;
4376
4377     fromX = fromY = toX = toY = -1;
4378
4379     newGame = FALSE;
4380
4381     if (appData.debugMode)
4382       fprintf(debugFP, "Parsing board: %s\n", string);
4383
4384     move_str[0] = NULLCHAR;
4385     elapsed_time[0] = NULLCHAR;
4386     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4387         int  i = 0, j;
4388         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4389             if(string[i] == ' ') { ranks++; files = 0; }
4390             else files++;
4391             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4392             i++;
4393         }
4394         for(j = 0; j <i; j++) board_chars[j] = string[j];
4395         board_chars[i] = '\0';
4396         string += i + 1;
4397     }
4398     n = sscanf(string, PATTERN, &to_play, &double_push,
4399                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4400                &gamenum, white, black, &relation, &basetime, &increment,
4401                &white_stren, &black_stren, &white_time, &black_time,
4402                &moveNum, str, elapsed_time, move_str, &ics_flip,
4403                &ticking);
4404
4405     if (n < 21) {
4406         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4407         DisplayError(str, 0);
4408         return;
4409     }
4410
4411     /* Convert the move number to internal form */
4412     moveNum = (moveNum - 1) * 2;
4413     if (to_play == 'B') moveNum++;
4414     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4415       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4416                         0, 1);
4417       return;
4418     }
4419
4420     switch (relation) {
4421       case RELATION_OBSERVING_PLAYED:
4422       case RELATION_OBSERVING_STATIC:
4423         if (gamenum == -1) {
4424             /* Old ICC buglet */
4425             relation = RELATION_OBSERVING_STATIC;
4426         }
4427         newGameMode = IcsObserving;
4428         break;
4429       case RELATION_PLAYING_MYMOVE:
4430       case RELATION_PLAYING_NOTMYMOVE:
4431         newGameMode =
4432           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4433             IcsPlayingWhite : IcsPlayingBlack;
4434         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4435         break;
4436       case RELATION_EXAMINING:
4437         newGameMode = IcsExamining;
4438         break;
4439       case RELATION_ISOLATED_BOARD:
4440       default:
4441         /* Just display this board.  If user was doing something else,
4442            we will forget about it until the next board comes. */
4443         newGameMode = IcsIdle;
4444         break;
4445       case RELATION_STARTING_POSITION:
4446         newGameMode = gameMode;
4447         break;
4448     }
4449
4450     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4451         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4452          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4453       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4454       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4455       static int lastBgGame = -1;
4456       char *toSqr;
4457       for (k = 0; k < ranks; k++) {
4458         for (j = 0; j < files; j++)
4459           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4460         if(gameInfo.holdingsWidth > 1) {
4461              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4462              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4463         }
4464       }
4465       CopyBoard(partnerBoard, board);
4466       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4467         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4468         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4469       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4470       if(toSqr = strchr(str, '-')) {
4471         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4472         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4473       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4474       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4475       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4476       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4477       if(twoBoards) {
4478           DisplayWhiteClock(white_time*fac, to_play == 'W');
4479           DisplayBlackClock(black_time*fac, to_play != 'W');
4480           activePartner = to_play;
4481           if(gamenum != lastBgGame) {
4482               char buf[MSG_SIZ];
4483               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4484               DisplayTitle(buf);
4485           }
4486           lastBgGame = gamenum;
4487           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4488                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4489       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4490                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4491       if(!twoBoards) DisplayMessage(partnerStatus, "");
4492         partnerBoardValid = TRUE;
4493       return;
4494     }
4495
4496     if(appData.dualBoard && appData.bgObserve) {
4497         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4498             SendToICS(ics_prefix), SendToICS("pobserve\n");
4499         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4500             char buf[MSG_SIZ];
4501             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4502             SendToICS(buf);
4503         }
4504     }
4505
4506     /* Modify behavior for initial board display on move listing
4507        of wild games.
4508        */
4509     switch (ics_getting_history) {
4510       case H_FALSE:
4511       case H_REQUESTED:
4512         break;
4513       case H_GOT_REQ_HEADER:
4514       case H_GOT_UNREQ_HEADER:
4515         /* This is the initial position of the current game */
4516         gamenum = ics_gamenum;
4517         moveNum = 0;            /* old ICS bug workaround */
4518         if (to_play == 'B') {
4519           startedFromSetupPosition = TRUE;
4520           blackPlaysFirst = TRUE;
4521           moveNum = 1;
4522           if (forwardMostMove == 0) forwardMostMove = 1;
4523           if (backwardMostMove == 0) backwardMostMove = 1;
4524           if (currentMove == 0) currentMove = 1;
4525         }
4526         newGameMode = gameMode;
4527         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4528         break;
4529       case H_GOT_UNWANTED_HEADER:
4530         /* This is an initial board that we don't want */
4531         return;
4532       case H_GETTING_MOVES:
4533         /* Should not happen */
4534         DisplayError(_("Error gathering move list: extra board"), 0);
4535         ics_getting_history = H_FALSE;
4536         return;
4537     }
4538
4539    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4540                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4541                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4542      /* [HGM] We seem to have switched variant unexpectedly
4543       * Try to guess new variant from board size
4544       */
4545           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4546           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4547           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4548           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4549           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4550           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4551           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4552           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4553           /* Get a move list just to see the header, which
4554              will tell us whether this is really bug or zh */
4555           if (ics_getting_history == H_FALSE) {
4556             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559           }
4560     }
4561
4562     /* Take action if this is the first board of a new game, or of a
4563        different game than is currently being displayed.  */
4564     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4565         relation == RELATION_ISOLATED_BOARD) {
4566
4567         /* Forget the old game and get the history (if any) of the new one */
4568         if (gameMode != BeginningOfGame) {
4569           Reset(TRUE, TRUE);
4570         }
4571         newGame = TRUE;
4572         if (appData.autoRaiseBoard) BoardToTop();
4573         prevMove = -3;
4574         if (gamenum == -1) {
4575             newGameMode = IcsIdle;
4576         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4577                    appData.getMoveList && !reqFlag) {
4578             /* Need to get game history */
4579             ics_getting_history = H_REQUESTED;
4580             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4581             SendToICS(str);
4582         }
4583
4584         /* Initially flip the board to have black on the bottom if playing
4585            black or if the ICS flip flag is set, but let the user change
4586            it with the Flip View button. */
4587         flipView = appData.autoFlipView ?
4588           (newGameMode == IcsPlayingBlack) || ics_flip :
4589           appData.flipView;
4590
4591         /* Done with values from previous mode; copy in new ones */
4592         gameMode = newGameMode;
4593         ModeHighlight();
4594         ics_gamenum = gamenum;
4595         if (gamenum == gs_gamenum) {
4596             int klen = strlen(gs_kind);
4597             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4598             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4599             gameInfo.event = StrSave(str);
4600         } else {
4601             gameInfo.event = StrSave("ICS game");
4602         }
4603         gameInfo.site = StrSave(appData.icsHost);
4604         gameInfo.date = PGNDate();
4605         gameInfo.round = StrSave("-");
4606         gameInfo.white = StrSave(white);
4607         gameInfo.black = StrSave(black);
4608         timeControl = basetime * 60 * 1000;
4609         timeControl_2 = 0;
4610         timeIncrement = increment * 1000;
4611         movesPerSession = 0;
4612         gameInfo.timeControl = TimeControlTagValue();
4613         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4614   if (appData.debugMode) {
4615     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4616     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4617     setbuf(debugFP, NULL);
4618   }
4619
4620         gameInfo.outOfBook = NULL;
4621
4622         /* Do we have the ratings? */
4623         if (strcmp(player1Name, white) == 0 &&
4624             strcmp(player2Name, black) == 0) {
4625             if (appData.debugMode)
4626               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4627                       player1Rating, player2Rating);
4628             gameInfo.whiteRating = player1Rating;
4629             gameInfo.blackRating = player2Rating;
4630         } else if (strcmp(player2Name, white) == 0 &&
4631                    strcmp(player1Name, black) == 0) {
4632             if (appData.debugMode)
4633               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4634                       player2Rating, player1Rating);
4635             gameInfo.whiteRating = player2Rating;
4636             gameInfo.blackRating = player1Rating;
4637         }
4638         player1Name[0] = player2Name[0] = NULLCHAR;
4639
4640         /* Silence shouts if requested */
4641         if (appData.quietPlay &&
4642             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4643             SendToICS(ics_prefix);
4644             SendToICS("set shout 0\n");
4645         }
4646     }
4647
4648     /* Deal with midgame name changes */
4649     if (!newGame) {
4650         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4651             if (gameInfo.white) free(gameInfo.white);
4652             gameInfo.white = StrSave(white);
4653         }
4654         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4655             if (gameInfo.black) free(gameInfo.black);
4656             gameInfo.black = StrSave(black);
4657         }
4658     }
4659
4660     /* Throw away game result if anything actually changes in examine mode */
4661     if (gameMode == IcsExamining && !newGame) {
4662         gameInfo.result = GameUnfinished;
4663         if (gameInfo.resultDetails != NULL) {
4664             free(gameInfo.resultDetails);
4665             gameInfo.resultDetails = NULL;
4666         }
4667     }
4668
4669     /* In pausing && IcsExamining mode, we ignore boards coming
4670        in if they are in a different variation than we are. */
4671     if (pauseExamInvalid) return;
4672     if (pausing && gameMode == IcsExamining) {
4673         if (moveNum <= pauseExamForwardMostMove) {
4674             pauseExamInvalid = TRUE;
4675             forwardMostMove = pauseExamForwardMostMove;
4676             return;
4677         }
4678     }
4679
4680   if (appData.debugMode) {
4681     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4682   }
4683     /* Parse the board */
4684     for (k = 0; k < ranks; k++) {
4685       for (j = 0; j < files; j++)
4686         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4687       if(gameInfo.holdingsWidth > 1) {
4688            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4689            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4690       }
4691     }
4692     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4693       board[5][BOARD_RGHT+1] = WhiteAngel;
4694       board[6][BOARD_RGHT+1] = WhiteMarshall;
4695       board[1][0] = BlackMarshall;
4696       board[2][0] = BlackAngel;
4697       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4698     }
4699     CopyBoard(boards[moveNum], board);
4700     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4701     if (moveNum == 0) {
4702         startedFromSetupPosition =
4703           !CompareBoards(board, initialPosition);
4704         if(startedFromSetupPosition)
4705             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4706     }
4707
4708     /* [HGM] Set castling rights. Take the outermost Rooks,
4709        to make it also work for FRC opening positions. Note that board12
4710        is really defective for later FRC positions, as it has no way to
4711        indicate which Rook can castle if they are on the same side of King.
4712        For the initial position we grant rights to the outermost Rooks,
4713        and remember thos rights, and we then copy them on positions
4714        later in an FRC game. This means WB might not recognize castlings with
4715        Rooks that have moved back to their original position as illegal,
4716        but in ICS mode that is not its job anyway.
4717     */
4718     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4719     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4720
4721         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4722             if(board[0][i] == WhiteRook) j = i;
4723         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4724         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4725             if(board[0][i] == WhiteRook) j = i;
4726         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4727         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4728             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4729         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4730         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4731             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4732         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4733
4734         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4735         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4736         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4737             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4738         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4739             if(board[BOARD_HEIGHT-1][k] == bKing)
4740                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4741         if(gameInfo.variant == VariantTwoKings) {
4742             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4743             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4744             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4745         }
4746     } else { int r;
4747         r = boards[moveNum][CASTLING][0] = initialRights[0];
4748         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4749         r = boards[moveNum][CASTLING][1] = initialRights[1];
4750         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4751         r = boards[moveNum][CASTLING][3] = initialRights[3];
4752         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4753         r = boards[moveNum][CASTLING][4] = initialRights[4];
4754         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4755         /* wildcastle kludge: always assume King has rights */
4756         r = boards[moveNum][CASTLING][2] = initialRights[2];
4757         r = boards[moveNum][CASTLING][5] = initialRights[5];
4758     }
4759     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4760     boards[moveNum][EP_STATUS] = EP_NONE;
4761     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4762     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4763     if(double_push !=  -1) {
4764         int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4765         boards[moveNum][EP_FILE] = // also set new e.p. variables
4766         boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4767         boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4768         boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4769     } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4770
4771
4772     if (ics_getting_history == H_GOT_REQ_HEADER ||
4773         ics_getting_history == H_GOT_UNREQ_HEADER) {
4774         /* This was an initial position from a move list, not
4775            the current position */
4776         return;
4777     }
4778
4779     /* Update currentMove and known move number limits */
4780     newMove = newGame || moveNum > forwardMostMove;
4781
4782     if (newGame) {
4783         forwardMostMove = backwardMostMove = currentMove = moveNum;
4784         if (gameMode == IcsExamining && moveNum == 0) {
4785           /* Workaround for ICS limitation: we are not told the wild
4786              type when starting to examine a game.  But if we ask for
4787              the move list, the move list header will tell us */
4788             ics_getting_history = H_REQUESTED;
4789             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4790             SendToICS(str);
4791         }
4792     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4793                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4794 #if ZIPPY
4795         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4796         /* [HGM] applied this also to an engine that is silently watching        */
4797         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4798             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4799             gameInfo.variant == currentlyInitializedVariant) {
4800           takeback = forwardMostMove - moveNum;
4801           for (i = 0; i < takeback; i++) {
4802             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4803             SendToProgram("undo\n", &first);
4804           }
4805         }
4806 #endif
4807
4808         forwardMostMove = moveNum;
4809         if (!pausing || currentMove > forwardMostMove)
4810           currentMove = forwardMostMove;
4811     } else {
4812         /* New part of history that is not contiguous with old part */
4813         if (pausing && gameMode == IcsExamining) {
4814             pauseExamInvalid = TRUE;
4815             forwardMostMove = pauseExamForwardMostMove;
4816             return;
4817         }
4818         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4819 #if ZIPPY
4820             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4821                 // [HGM] when we will receive the move list we now request, it will be
4822                 // fed to the engine from the first move on. So if the engine is not
4823                 // in the initial position now, bring it there.
4824                 InitChessProgram(&first, 0);
4825             }
4826 #endif
4827             ics_getting_history = H_REQUESTED;
4828             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4829             SendToICS(str);
4830         }
4831         forwardMostMove = backwardMostMove = currentMove = moveNum;
4832     }
4833
4834     /* Update the clocks */
4835     if (strchr(elapsed_time, '.')) {
4836       /* Time is in ms */
4837       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4838       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4839     } else {
4840       /* Time is in seconds */
4841       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4842       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4843     }
4844
4845
4846 #if ZIPPY
4847     if (appData.zippyPlay && newGame &&
4848         gameMode != IcsObserving && gameMode != IcsIdle &&
4849         gameMode != IcsExamining)
4850       ZippyFirstBoard(moveNum, basetime, increment);
4851 #endif
4852
4853     /* Put the move on the move list, first converting
4854        to canonical algebraic form. */
4855     if (moveNum > 0) {
4856   if (appData.debugMode) {
4857     int f = forwardMostMove;
4858     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4859             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4860             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4861     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4862     fprintf(debugFP, "moveNum = %d\n", moveNum);
4863     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4864     setbuf(debugFP, NULL);
4865   }
4866         if (moveNum <= backwardMostMove) {
4867             /* We don't know what the board looked like before
4868                this move.  Punt. */
4869           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4870             strcat(parseList[moveNum - 1], " ");
4871             strcat(parseList[moveNum - 1], elapsed_time);
4872             moveList[moveNum - 1][0] = NULLCHAR;
4873         } else if (strcmp(move_str, "none") == 0) {
4874             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4875             /* Again, we don't know what the board looked like;
4876                this is really the start of the game. */
4877             parseList[moveNum - 1][0] = NULLCHAR;
4878             moveList[moveNum - 1][0] = NULLCHAR;
4879             backwardMostMove = moveNum;
4880             startedFromSetupPosition = TRUE;
4881             fromX = fromY = toX = toY = -1;
4882         } else {
4883           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4884           //                 So we parse the long-algebraic move string in stead of the SAN move
4885           int valid; char buf[MSG_SIZ], *prom;
4886
4887           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4888                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4889           // str looks something like "Q/a1-a2"; kill the slash
4890           if(str[1] == '/')
4891             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4892           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4893           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4894                 strcat(buf, prom); // long move lacks promo specification!
4895           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4896                 if(appData.debugMode)
4897                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4898                 safeStrCpy(move_str, buf, MSG_SIZ);
4899           }
4900           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4901                                 &fromX, &fromY, &toX, &toY, &promoChar)
4902                || ParseOneMove(buf, moveNum - 1, &moveType,
4903                                 &fromX, &fromY, &toX, &toY, &promoChar);
4904           // end of long SAN patch
4905           if (valid) {
4906             (void) CoordsToAlgebraic(boards[moveNum - 1],
4907                                      PosFlags(moveNum - 1),
4908                                      fromY, fromX, toY, toX, promoChar,
4909                                      parseList[moveNum-1]);
4910             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4911               case MT_NONE:
4912               case MT_STALEMATE:
4913               default:
4914                 break;
4915               case MT_CHECK:
4916                 if(!IS_SHOGI(gameInfo.variant))
4917                     strcat(parseList[moveNum - 1], "+");
4918                 break;
4919               case MT_CHECKMATE:
4920               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4921                 strcat(parseList[moveNum - 1], "#");
4922                 break;
4923             }
4924             strcat(parseList[moveNum - 1], " ");
4925             strcat(parseList[moveNum - 1], elapsed_time);
4926             /* currentMoveString is set as a side-effect of ParseOneMove */
4927             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4928             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4929             strcat(moveList[moveNum - 1], "\n");
4930
4931             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4932                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4933               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4934                 ChessSquare old, new = boards[moveNum][k][j];
4935                   if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4936                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4937                   if(old == new) continue;
4938                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4939                   else if(new == WhiteWazir || new == BlackWazir) {
4940                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4941                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4942                       else boards[moveNum][k][j] = old; // preserve type of Gold
4943                   } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4944                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4945               }
4946           } else {
4947             /* Move from ICS was illegal!?  Punt. */
4948             if (appData.debugMode) {
4949               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4950               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4951             }
4952             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4953             strcat(parseList[moveNum - 1], " ");
4954             strcat(parseList[moveNum - 1], elapsed_time);
4955             moveList[moveNum - 1][0] = NULLCHAR;
4956             fromX = fromY = toX = toY = -1;
4957           }
4958         }
4959   if (appData.debugMode) {
4960     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4961     setbuf(debugFP, NULL);
4962   }
4963
4964 #if ZIPPY
4965         /* Send move to chess program (BEFORE animating it). */
4966         if (appData.zippyPlay && !newGame && newMove &&
4967            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4968
4969             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4970                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4971                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4972                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4973                             move_str);
4974                     DisplayError(str, 0);
4975                 } else {
4976                     if (first.sendTime) {
4977                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4978                     }
4979                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4980                     if (firstMove && !bookHit) {
4981                         firstMove = FALSE;
4982                         if (first.useColors) {
4983                           SendToProgram(gameMode == IcsPlayingWhite ?
4984                                         "white\ngo\n" :
4985                                         "black\ngo\n", &first);
4986                         } else {
4987                           SendToProgram("go\n", &first);
4988                         }
4989                         first.maybeThinking = TRUE;
4990                     }
4991                 }
4992             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4993               if (moveList[moveNum - 1][0] == NULLCHAR) {
4994                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4995                 DisplayError(str, 0);
4996               } else {
4997                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4998                 SendMoveToProgram(moveNum - 1, &first);
4999               }
5000             }
5001         }
5002 #endif
5003     }
5004
5005     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
5006         /* If move comes from a remote source, animate it.  If it
5007            isn't remote, it will have already been animated. */
5008         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
5009             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
5010         }
5011         if (!pausing && appData.highlightLastMove) {
5012             SetHighlights(fromX, fromY, toX, toY);
5013         }
5014     }
5015
5016     /* Start the clocks */
5017     whiteFlag = blackFlag = FALSE;
5018     appData.clockMode = !(basetime == 0 && increment == 0);
5019     if (ticking == 0) {
5020       ics_clock_paused = TRUE;
5021       StopClocks();
5022     } else if (ticking == 1) {
5023       ics_clock_paused = FALSE;
5024     }
5025     if (gameMode == IcsIdle ||
5026         relation == RELATION_OBSERVING_STATIC ||
5027         relation == RELATION_EXAMINING ||
5028         ics_clock_paused)
5029       DisplayBothClocks();
5030     else
5031       StartClocks();
5032
5033     /* Display opponents and material strengths */
5034     if (gameInfo.variant != VariantBughouse &&
5035         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5036         if (tinyLayout || smallLayout) {
5037             if(gameInfo.variant == VariantNormal)
5038               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5039                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5040                     basetime, increment);
5041             else
5042               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5043                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5044                     basetime, increment, (int) gameInfo.variant);
5045         } else {
5046             if(gameInfo.variant == VariantNormal)
5047               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5048                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5049                     basetime, increment);
5050             else
5051               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5052                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5053                     basetime, increment, VariantName(gameInfo.variant));
5054         }
5055         DisplayTitle(str);
5056   if (appData.debugMode) {
5057     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5058   }
5059     }
5060
5061
5062     /* Display the board */
5063     if (!pausing && !appData.noGUI) {
5064
5065       if (appData.premove)
5066           if (!gotPremove ||
5067              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5068              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5069               ClearPremoveHighlights();
5070
5071       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5072         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5073       DrawPosition(j, boards[currentMove]);
5074
5075       DisplayMove(moveNum - 1);
5076       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5077             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5078               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5079         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5080       }
5081     }
5082
5083     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5084 #if ZIPPY
5085     if(bookHit) { // [HGM] book: simulate book reply
5086         static char bookMove[MSG_SIZ]; // a bit generous?
5087
5088         programStats.nodes = programStats.depth = programStats.time =
5089         programStats.score = programStats.got_only_move = 0;
5090         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5091
5092         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5093         strcat(bookMove, bookHit);
5094         HandleMachineMove(bookMove, &first);
5095     }
5096 #endif
5097 }
5098
5099 void
5100 GetMoveListEvent ()
5101 {
5102     char buf[MSG_SIZ];
5103     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5104         ics_getting_history = H_REQUESTED;
5105         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5106         SendToICS(buf);
5107     }
5108 }
5109
5110 void
5111 SendToBoth (char *msg)
5112 {   // to make it easy to keep two engines in step in dual analysis
5113     SendToProgram(msg, &first);
5114     if(second.analyzing) SendToProgram(msg, &second);
5115 }
5116
5117 void
5118 AnalysisPeriodicEvent (int force)
5119 {
5120     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5121          && !force) || !appData.periodicUpdates)
5122       return;
5123
5124     /* Send . command to Crafty to collect stats */
5125     SendToBoth(".\n");
5126
5127     /* Don't send another until we get a response (this makes
5128        us stop sending to old Crafty's which don't understand
5129        the "." command (sending illegal cmds resets node count & time,
5130        which looks bad)) */
5131     programStats.ok_to_send = 0;
5132 }
5133
5134 void
5135 ics_update_width (int new_width)
5136 {
5137         ics_printf("set width %d\n", new_width);
5138 }
5139
5140 void
5141 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5142 {
5143     char buf[MSG_SIZ];
5144
5145     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5146         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5147             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5148             SendToProgram(buf, cps);
5149             return;
5150         }
5151         // null move in variant where engine does not understand it (for analysis purposes)
5152         SendBoard(cps, moveNum + 1); // send position after move in stead.
5153         return;
5154     }
5155     if (cps->useUsermove) {
5156       SendToProgram("usermove ", cps);
5157     }
5158     if (cps->useSAN) {
5159       char *space;
5160       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5161         int len = space - parseList[moveNum];
5162         memcpy(buf, parseList[moveNum], len);
5163         buf[len++] = '\n';
5164         buf[len] = NULLCHAR;
5165       } else {
5166         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5167       }
5168       SendToProgram(buf, cps);
5169     } else {
5170       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5171         AlphaRank(moveList[moveNum], 4);
5172         SendToProgram(moveList[moveNum], cps);
5173         AlphaRank(moveList[moveNum], 4); // and back
5174       } else
5175       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5176        * the engine. It would be nice to have a better way to identify castle
5177        * moves here. */
5178       if(appData.fischerCastling && cps->useOOCastle) {
5179         int fromX = moveList[moveNum][0] - AAA;
5180         int fromY = moveList[moveNum][1] - ONE;
5181         int toX = moveList[moveNum][2] - AAA;
5182         int toY = moveList[moveNum][3] - ONE;
5183         if((boards[moveNum][fromY][fromX] == WhiteKing
5184             && boards[moveNum][toY][toX] == WhiteRook)
5185            || (boards[moveNum][fromY][fromX] == BlackKing
5186                && boards[moveNum][toY][toX] == BlackRook)) {
5187           if(toX > fromX) SendToProgram("O-O\n", cps);
5188           else SendToProgram("O-O-O\n", cps);
5189         }
5190         else SendToProgram(moveList[moveNum], cps);
5191       } else
5192       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5193         char *m = moveList[moveNum];
5194         static char c[2];
5195         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5196         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
5197           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5198                                                m[2], m[3] - '0',
5199                                                m[5], m[6] - '0',
5200                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5201         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5202           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5203           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5204                                                m[7], m[8] - '0',
5205                                                m[7], m[8] - '0',
5206                                                m[5], m[6] - '0',
5207                                                m[5], m[6] - '0',
5208                                                m[2], m[3] - '0', c);
5209         } else
5210           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5211                                                m[5], m[6] - '0',
5212                                                m[5], m[6] - '0',
5213                                                m[2], m[3] - '0', c);
5214           SendToProgram(buf, cps);
5215       } else
5216       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5217         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5218           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5219           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5220                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5221         } else
5222           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5223                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5224         SendToProgram(buf, cps);
5225       }
5226       else SendToProgram(moveList[moveNum], cps);
5227       /* End of additions by Tord */
5228     }
5229
5230     /* [HGM] setting up the opening has brought engine in force mode! */
5231     /*       Send 'go' if we are in a mode where machine should play. */
5232     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5233         (gameMode == TwoMachinesPlay   ||
5234 #if ZIPPY
5235          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5236 #endif
5237          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5238         SendToProgram("go\n", cps);
5239   if (appData.debugMode) {
5240     fprintf(debugFP, "(extra)\n");
5241   }
5242     }
5243     setboardSpoiledMachineBlack = 0;
5244 }
5245
5246 void
5247 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5248 {
5249     char user_move[MSG_SIZ];
5250     char suffix[4];
5251
5252     if(gameInfo.variant == VariantSChess && promoChar) {
5253         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5254         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5255     } else suffix[0] = NULLCHAR;
5256
5257     switch (moveType) {
5258       default:
5259         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5260                 (int)moveType, fromX, fromY, toX, toY);
5261         DisplayError(user_move + strlen("say "), 0);
5262         break;
5263       case WhiteKingSideCastle:
5264       case BlackKingSideCastle:
5265       case WhiteQueenSideCastleWild:
5266       case BlackQueenSideCastleWild:
5267       /* PUSH Fabien */
5268       case WhiteHSideCastleFR:
5269       case BlackHSideCastleFR:
5270       /* POP Fabien */
5271         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5272         break;
5273       case WhiteQueenSideCastle:
5274       case BlackQueenSideCastle:
5275       case WhiteKingSideCastleWild:
5276       case BlackKingSideCastleWild:
5277       /* PUSH Fabien */
5278       case WhiteASideCastleFR:
5279       case BlackASideCastleFR:
5280       /* POP Fabien */
5281         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5282         break;
5283       case WhiteNonPromotion:
5284       case BlackNonPromotion:
5285         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5286         break;
5287       case WhitePromotion:
5288       case BlackPromotion:
5289         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5290            gameInfo.variant == VariantMakruk)
5291           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5292                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5293                 PieceToChar(WhiteFerz));
5294         else if(gameInfo.variant == VariantGreat)
5295           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5296                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5297                 PieceToChar(WhiteMan));
5298         else
5299           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5300                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5301                 promoChar);
5302         break;
5303       case WhiteDrop:
5304       case BlackDrop:
5305       drop:
5306         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5307                  ToUpper(PieceToChar((ChessSquare) fromX)),
5308                  AAA + toX, ONE + toY);
5309         break;
5310       case IllegalMove:  /* could be a variant we don't quite understand */
5311         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5312       case NormalMove:
5313       case WhiteCapturesEnPassant:
5314       case BlackCapturesEnPassant:
5315         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5316                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5317         break;
5318     }
5319     SendToICS(user_move);
5320     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5321         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5322 }
5323
5324 void
5325 UploadGameEvent ()
5326 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5327     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5328     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5329     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5330       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5331       return;
5332     }
5333     if(gameMode != IcsExamining) { // is this ever not the case?
5334         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5335
5336         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5337           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5338         } else { // on FICS we must first go to general examine mode
5339           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5340         }
5341         if(gameInfo.variant != VariantNormal) {
5342             // try figure out wild number, as xboard names are not always valid on ICS
5343             for(i=1; i<=36; i++) {
5344               snprintf(buf, MSG_SIZ, "wild/%d", i);
5345                 if(StringToVariant(buf) == gameInfo.variant) break;
5346             }
5347             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5348             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5349             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5350         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5351         SendToICS(ics_prefix);
5352         SendToICS(buf);
5353         if(startedFromSetupPosition || backwardMostMove != 0) {
5354           fen = PositionToFEN(backwardMostMove, NULL, 1);
5355           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5356             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5357             SendToICS(buf);
5358           } else { // FICS: everything has to set by separate bsetup commands
5359             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5360             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5361             SendToICS(buf);
5362             if(!WhiteOnMove(backwardMostMove)) {
5363                 SendToICS("bsetup tomove black\n");
5364             }
5365             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5366             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5367             SendToICS(buf);
5368             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5369             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5370             SendToICS(buf);
5371             i = boards[backwardMostMove][EP_STATUS];
5372             if(i >= 0) { // set e.p.
5373               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5374                 SendToICS(buf);
5375             }
5376             bsetup++;
5377           }
5378         }
5379       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5380             SendToICS("bsetup done\n"); // switch to normal examining.
5381     }
5382     for(i = backwardMostMove; i<last; i++) {
5383         char buf[20];
5384         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5385         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5386             int len = strlen(moveList[i]);
5387             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5388             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5389         }
5390         SendToICS(buf);
5391     }
5392     SendToICS(ics_prefix);
5393     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5394 }
5395
5396 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5397 int legNr = 1;
5398
5399 void
5400 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5401 {
5402     if (rf == DROP_RANK) {
5403       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5404       sprintf(move, "%c@%c%c\n",
5405                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5406     } else {
5407         if (promoChar == 'x' || promoChar == NULLCHAR) {
5408           sprintf(move, "%c%c%c%c\n",
5409                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5410           if(killX >= 0 && killY >= 0) {
5411             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5412             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5413           }
5414         } else {
5415             sprintf(move, "%c%c%c%c%c\n",
5416                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5417           if(killX >= 0 && killY >= 0) {
5418             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5419             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5420           }
5421         }
5422     }
5423 }
5424
5425 void
5426 ProcessICSInitScript (FILE *f)
5427 {
5428     char buf[MSG_SIZ];
5429
5430     while (fgets(buf, MSG_SIZ, f)) {
5431         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5432     }
5433
5434     fclose(f);
5435 }
5436
5437
5438 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5439 int dragging;
5440 static ClickType lastClickType;
5441
5442 int
5443 PieceInString (char *s, ChessSquare piece)
5444 {
5445   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5446   while((p = strchr(s, ID))) {
5447     if(!suffix || p[1] == suffix) return TRUE;
5448     s = p;
5449   }
5450   return FALSE;
5451 }
5452
5453 int
5454 Partner (ChessSquare *p)
5455 { // change piece into promotion partner if one shogi-promotes to the other
5456   ChessSquare partner = promoPartner[*p];
5457   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5458   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5459   *p = partner;
5460   return 1;
5461 }
5462
5463 void
5464 Sweep (int step)
5465 {
5466     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5467     static int toggleFlag;
5468     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5469     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5470     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5471     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5472     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5473     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5474     do {
5475         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5476         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5477         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5478         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5479         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5480         if(!step) step = -1;
5481     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5482             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5483             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5484             promoSweep == pawn ||
5485             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5486             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5487     if(toX >= 0) {
5488         int victim = boards[currentMove][toY][toX];
5489         boards[currentMove][toY][toX] = promoSweep;
5490         DrawPosition(FALSE, boards[currentMove]);
5491         boards[currentMove][toY][toX] = victim;
5492     } else
5493     ChangeDragPiece(promoSweep);
5494 }
5495
5496 int
5497 PromoScroll (int x, int y)
5498 {
5499   int step = 0;
5500
5501   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5502   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5503   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5504   if(!step) return FALSE;
5505   lastX = x; lastY = y;
5506   if((promoSweep < BlackPawn) == flipView) step = -step;
5507   if(step > 0) selectFlag = 1;
5508   if(!selectFlag) Sweep(step);
5509   return FALSE;
5510 }
5511
5512 void
5513 NextPiece (int step)
5514 {
5515     ChessSquare piece = boards[currentMove][toY][toX];
5516     do {
5517         pieceSweep -= step;
5518         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5519         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5520         if(!step) step = -1;
5521     } while(PieceToChar(pieceSweep) == '.');
5522     boards[currentMove][toY][toX] = pieceSweep;
5523     DrawPosition(FALSE, boards[currentMove]);
5524     boards[currentMove][toY][toX] = piece;
5525 }
5526 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5527 void
5528 AlphaRank (char *move, int n)
5529 {
5530 //    char *p = move, c; int x, y;
5531
5532     if (appData.debugMode) {
5533         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5534     }
5535
5536     if(move[1]=='*' &&
5537        move[2]>='0' && move[2]<='9' &&
5538        move[3]>='a' && move[3]<='x'    ) {
5539         move[1] = '@';
5540         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5541         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5542     } else
5543     if(move[0]>='0' && move[0]<='9' &&
5544        move[1]>='a' && move[1]<='x' &&
5545        move[2]>='0' && move[2]<='9' &&
5546        move[3]>='a' && move[3]<='x'    ) {
5547         /* input move, Shogi -> normal */
5548         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5549         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5550         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5551         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5552     } else
5553     if(move[1]=='@' &&
5554        move[3]>='0' && move[3]<='9' &&
5555        move[2]>='a' && move[2]<='x'    ) {
5556         move[1] = '*';
5557         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5558         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5559     } else
5560     if(
5561        move[0]>='a' && move[0]<='x' &&
5562        move[3]>='0' && move[3]<='9' &&
5563        move[2]>='a' && move[2]<='x'    ) {
5564          /* output move, normal -> Shogi */
5565         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5566         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5567         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5568         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5569         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5570     }
5571     if (appData.debugMode) {
5572         fprintf(debugFP, "   out = '%s'\n", move);
5573     }
5574 }
5575
5576 char yy_textstr[8000];
5577
5578 /* Parser for moves from gnuchess, ICS, or user typein box */
5579 Boolean
5580 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5581 {
5582     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5583
5584     switch (*moveType) {
5585       case WhitePromotion:
5586       case BlackPromotion:
5587       case WhiteNonPromotion:
5588       case BlackNonPromotion:
5589       case NormalMove:
5590       case FirstLeg:
5591       case WhiteCapturesEnPassant:
5592       case BlackCapturesEnPassant:
5593       case WhiteKingSideCastle:
5594       case WhiteQueenSideCastle:
5595       case BlackKingSideCastle:
5596       case BlackQueenSideCastle:
5597       case WhiteKingSideCastleWild:
5598       case WhiteQueenSideCastleWild:
5599       case BlackKingSideCastleWild:
5600       case BlackQueenSideCastleWild:
5601       /* Code added by Tord: */
5602       case WhiteHSideCastleFR:
5603       case WhiteASideCastleFR:
5604       case BlackHSideCastleFR:
5605       case BlackASideCastleFR:
5606       /* End of code added by Tord */
5607       case IllegalMove:         /* bug or odd chess variant */
5608         if(currentMoveString[1] == '@') { // illegal drop
5609           *fromX = WhiteOnMove(moveNum) ?
5610             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611             (int) CharToPiece(ToLower(currentMoveString[0]));
5612           goto drop;
5613         }
5614         *fromX = currentMoveString[0] - AAA;
5615         *fromY = currentMoveString[1] - ONE;
5616         *toX = currentMoveString[2] - AAA;
5617         *toY = currentMoveString[3] - ONE;
5618         *promoChar = currentMoveString[4];
5619         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5620         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5621             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5622     if (appData.debugMode) {
5623         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5624     }
5625             *fromX = *fromY = *toX = *toY = 0;
5626             return FALSE;
5627         }
5628         if (appData.testLegality) {
5629           return (*moveType != IllegalMove);
5630         } else {
5631           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5632                          // [HGM] lion: if this is a double move we are less critical
5633                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5634         }
5635
5636       case WhiteDrop:
5637       case BlackDrop:
5638         *fromX = *moveType == WhiteDrop ?
5639           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5640           (int) CharToPiece(ToLower(currentMoveString[0]));
5641       drop:
5642         *fromY = DROP_RANK;
5643         *toX = currentMoveString[2] - AAA;
5644         *toY = currentMoveString[3] - ONE;
5645         *promoChar = NULLCHAR;
5646         return TRUE;
5647
5648       case AmbiguousMove:
5649       case ImpossibleMove:
5650       case EndOfFile:
5651       case ElapsedTime:
5652       case Comment:
5653       case PGNTag:
5654       case NAG:
5655       case WhiteWins:
5656       case BlackWins:
5657       case GameIsDrawn:
5658       default:
5659     if (appData.debugMode) {
5660         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5661     }
5662         /* bug? */
5663         *fromX = *fromY = *toX = *toY = 0;
5664         *promoChar = NULLCHAR;
5665         return FALSE;
5666     }
5667 }
5668
5669 Boolean pushed = FALSE;
5670 char *lastParseAttempt;
5671
5672 void
5673 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5674 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5675   int fromX, fromY, toX, toY; char promoChar;
5676   ChessMove moveType;
5677   Boolean valid;
5678   int nr = 0;
5679
5680   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5681   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5682     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5683     pushed = TRUE;
5684   }
5685   endPV = forwardMostMove;
5686   do {
5687     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5688     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5689     lastParseAttempt = pv;
5690     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5691     if(!valid && nr == 0 &&
5692        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5693         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5694         // Hande case where played move is different from leading PV move
5695         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5696         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5697         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5698         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5699           endPV += 2; // if position different, keep this
5700           moveList[endPV-1][0] = fromX + AAA;
5701           moveList[endPV-1][1] = fromY + ONE;
5702           moveList[endPV-1][2] = toX + AAA;
5703           moveList[endPV-1][3] = toY + ONE;
5704           parseList[endPV-1][0] = NULLCHAR;
5705           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5706         }
5707       }
5708     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5709     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5710     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5711     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5712         valid++; // allow comments in PV
5713         continue;
5714     }
5715     nr++;
5716     if(endPV+1 > framePtr) break; // no space, truncate
5717     if(!valid) break;
5718     endPV++;
5719     CopyBoard(boards[endPV], boards[endPV-1]);
5720     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5721     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5722     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5723     CoordsToAlgebraic(boards[endPV - 1],
5724                              PosFlags(endPV - 1),
5725                              fromY, fromX, toY, toX, promoChar,
5726                              parseList[endPV - 1]);
5727   } while(valid);
5728   if(atEnd == 2) return; // used hidden, for PV conversion
5729   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5730   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5731   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5732                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5733   DrawPosition(TRUE, boards[currentMove]);
5734 }
5735
5736 int
5737 MultiPV (ChessProgramState *cps, int kind)
5738 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5739         int i;
5740         for(i=0; i<cps->nrOptions; i++) {
5741             char *s = cps->option[i].name;
5742             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5743             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5744                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5745         }
5746         return -1;
5747 }
5748
5749 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5750 static int multi, pv_margin;
5751 static ChessProgramState *activeCps;
5752
5753 Boolean
5754 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5755 {
5756         int startPV, lineStart, origIndex = index;
5757         char *p, buf2[MSG_SIZ];
5758         ChessProgramState *cps = (pane ? &second : &first);
5759
5760         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5761         lastX = x; lastY = y;
5762         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5763         lineStart = startPV = index;
5764         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5765         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5766         index = startPV;
5767         do{ while(buf[index] && buf[index] != '\n') index++;
5768         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5769         buf[index] = 0;
5770         if(lineStart == 0 && gameMode == AnalyzeMode) {
5771             int n = 0;
5772             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5773             if(n == 0) { // click not on "fewer" or "more"
5774                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5775                     pv_margin = cps->option[multi].value;
5776                     activeCps = cps; // non-null signals margin adjustment
5777                 }
5778             } else if((multi = MultiPV(cps, 1)) >= 0) {
5779                 n += cps->option[multi].value; if(n < 1) n = 1;
5780                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5781                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5782                 cps->option[multi].value = n;
5783                 *start = *end = 0;
5784                 return FALSE;
5785             }
5786         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5787                 ExcludeClick(origIndex - lineStart);
5788                 return FALSE;
5789         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5790                 Collapse(origIndex - lineStart);
5791                 return FALSE;
5792         }
5793         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5794         *start = startPV; *end = index-1;
5795         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5796         return TRUE;
5797 }
5798
5799 char *
5800 PvToSAN (char *pv)
5801 {
5802         static char buf[10*MSG_SIZ];
5803         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5804         *buf = NULLCHAR;
5805         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5806         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5807         for(i = forwardMostMove; i<endPV; i++){
5808             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5809             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5810             k += strlen(buf+k);
5811         }
5812         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5813         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5814         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5815         endPV = savedEnd;
5816         return buf;
5817 }
5818
5819 Boolean
5820 LoadPV (int x, int y)
5821 { // called on right mouse click to load PV
5822   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5823   lastX = x; lastY = y;
5824   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5825   extendGame = FALSE;
5826   return TRUE;
5827 }
5828
5829 void
5830 UnLoadPV ()
5831 {
5832   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5833   if(activeCps) {
5834     if(pv_margin != activeCps->option[multi].value) {
5835       char buf[MSG_SIZ];
5836       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5837       SendToProgram(buf, activeCps);
5838       activeCps->option[multi].value = pv_margin;
5839     }
5840     activeCps = NULL;
5841     return;
5842   }
5843   if(endPV < 0) return;
5844   if(appData.autoCopyPV) CopyFENToClipboard();
5845   endPV = -1;
5846   if(extendGame && currentMove > forwardMostMove) {
5847         Boolean saveAnimate = appData.animate;
5848         if(pushed) {
5849             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5850                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5851             } else storedGames--; // abandon shelved tail of original game
5852         }
5853         pushed = FALSE;
5854         forwardMostMove = currentMove;
5855         currentMove = oldFMM;
5856         appData.animate = FALSE;
5857         ToNrEvent(forwardMostMove);
5858         appData.animate = saveAnimate;
5859   }
5860   currentMove = forwardMostMove;
5861   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5862   ClearPremoveHighlights();
5863   DrawPosition(TRUE, boards[currentMove]);
5864 }
5865
5866 void
5867 MovePV (int x, int y, int h)
5868 { // step through PV based on mouse coordinates (called on mouse move)
5869   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5870
5871   if(activeCps) { // adjusting engine's multi-pv margin
5872     if(x > lastX) pv_margin++; else
5873     if(x < lastX) pv_margin -= (pv_margin > 0);
5874     if(x != lastX) {
5875       char buf[MSG_SIZ];
5876       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5877       DisplayMessage(buf, "");
5878     }
5879     lastX = x;
5880     return;
5881   }
5882   // we must somehow check if right button is still down (might be released off board!)
5883   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5884   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5885   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5886   if(!step) return;
5887   lastX = x; lastY = y;
5888
5889   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5890   if(endPV < 0) return;
5891   if(y < margin) step = 1; else
5892   if(y > h - margin) step = -1;
5893   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5894   currentMove += step;
5895   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5896   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5897                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5898   DrawPosition(FALSE, boards[currentMove]);
5899 }
5900
5901
5902 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5903 // All positions will have equal probability, but the current method will not provide a unique
5904 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5905 #define DARK 1
5906 #define LITE 2
5907 #define ANY 3
5908
5909 int squaresLeft[4];
5910 int piecesLeft[(int)BlackPawn];
5911 int seed, nrOfShuffles;
5912
5913 void
5914 GetPositionNumber ()
5915 {       // sets global variable seed
5916         int i;
5917
5918         seed = appData.defaultFrcPosition;
5919         if(seed < 0) { // randomize based on time for negative FRC position numbers
5920                 for(i=0; i<50; i++) seed += random();
5921                 seed = random() ^ random() >> 8 ^ random() << 8;
5922                 if(seed<0) seed = -seed;
5923         }
5924 }
5925
5926 int
5927 put (Board board, int pieceType, int rank, int n, int shade)
5928 // put the piece on the (n-1)-th empty squares of the given shade
5929 {
5930         int i;
5931
5932         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5933                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5934                         board[rank][i] = (ChessSquare) pieceType;
5935                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5936                         squaresLeft[ANY]--;
5937                         piecesLeft[pieceType]--;
5938                         return i;
5939                 }
5940         }
5941         return -1;
5942 }
5943
5944
5945 void
5946 AddOnePiece (Board board, int pieceType, int rank, int shade)
5947 // calculate where the next piece goes, (any empty square), and put it there
5948 {
5949         int i;
5950
5951         i = seed % squaresLeft[shade];
5952         nrOfShuffles *= squaresLeft[shade];
5953         seed /= squaresLeft[shade];
5954         put(board, pieceType, rank, i, shade);
5955 }
5956
5957 void
5958 AddTwoPieces (Board board, int pieceType, int rank)
5959 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5960 {
5961         int i, n=squaresLeft[ANY], j=n-1, k;
5962
5963         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5964         i = seed % k;  // pick one
5965         nrOfShuffles *= k;
5966         seed /= k;
5967         while(i >= j) i -= j--;
5968         j = n - 1 - j; i += j;
5969         put(board, pieceType, rank, j, ANY);
5970         put(board, pieceType, rank, i, ANY);
5971 }
5972
5973 void
5974 SetUpShuffle (Board board, int number)
5975 {
5976         int i, p, first=1;
5977
5978         GetPositionNumber(); nrOfShuffles = 1;
5979
5980         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5981         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5982         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5983
5984         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5985
5986         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5987             p = (int) board[0][i];
5988             if(p < (int) BlackPawn) piecesLeft[p] ++;
5989             board[0][i] = EmptySquare;
5990         }
5991
5992         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5993             // shuffles restricted to allow normal castling put KRR first
5994             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5995                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5996             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5997                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5998             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5999                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
6000             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
6001                 put(board, WhiteRook, 0, 0, ANY);
6002             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
6003         }
6004
6005         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
6006             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
6007             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
6008                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
6009                 while(piecesLeft[p] >= 2) {
6010                     AddOnePiece(board, p, 0, LITE);
6011                     AddOnePiece(board, p, 0, DARK);
6012                 }
6013                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6014             }
6015
6016         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6017             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6018             // but we leave King and Rooks for last, to possibly obey FRC restriction
6019             if(p == (int)WhiteRook) continue;
6020             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6021             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
6022         }
6023
6024         // now everything is placed, except perhaps King (Unicorn) and Rooks
6025
6026         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6027             // Last King gets castling rights
6028             while(piecesLeft[(int)WhiteUnicorn]) {
6029                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6030                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6031             }
6032
6033             while(piecesLeft[(int)WhiteKing]) {
6034                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6035                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6036             }
6037
6038
6039         } else {
6040             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6041             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6042         }
6043
6044         // Only Rooks can be left; simply place them all
6045         while(piecesLeft[(int)WhiteRook]) {
6046                 i = put(board, WhiteRook, 0, 0, ANY);
6047                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6048                         if(first) {
6049                                 first=0;
6050                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6051                         }
6052                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6053                 }
6054         }
6055         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6056             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6057         }
6058
6059         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6060 }
6061
6062 int
6063 ptclen (const char *s, char *escapes)
6064 {
6065     int n = 0;
6066     if(!*escapes) return strlen(s);
6067     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6068     return n;
6069 }
6070
6071 int
6072 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6073 /* [HGM] moved here from winboard.c because of its general usefulness */
6074 /*       Basically a safe strcpy that uses the last character as King */
6075 {
6076     int result = FALSE; int NrPieces;
6077     unsigned char partner[EmptySquare];
6078
6079     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6080                     && NrPieces >= 12 && !(NrPieces&1)) {
6081         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6082
6083         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6084         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6085             char *p, c=0;
6086             if(map[j] == '/') offs = WhitePBishop - i, j++;
6087             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6088             table[i+offs] = map[j++];
6089             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6090             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6091             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6092         }
6093         table[(int) WhiteKing]  = map[j++];
6094         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6095             char *p, c=0;
6096             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6097             i = WHITE_TO_BLACK ii;
6098             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6099             table[i+offs] = map[j++];
6100             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6101             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6102             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6103         }
6104         table[(int) BlackKing]  = map[j++];
6105
6106
6107         if(*escapes) { // set up promotion pairing
6108             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6109             // pieceToChar entirely filled, so we can look up specified partners
6110             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6111                 int c = table[i];
6112                 if(c == '^' || c == '-') { // has specified partner
6113                     int p;
6114                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6115                     if(c == '^') table[i] = '+';
6116                     if(p < EmptySquare) {
6117                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6118                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6119                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6120                     }
6121                 } else if(c == '*') {
6122                     table[i] = partner[i];
6123                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6124                 }
6125             }
6126         }
6127
6128         result = TRUE;
6129     }
6130
6131     return result;
6132 }
6133
6134 int
6135 SetCharTable (unsigned char *table, const char * map)
6136 {
6137     return SetCharTableEsc(table, map, "");
6138 }
6139
6140 void
6141 Prelude (Board board)
6142 {       // [HGM] superchess: random selection of exo-pieces
6143         int i, j, k; ChessSquare p;
6144         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6145
6146         GetPositionNumber(); // use FRC position number
6147
6148         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6149             SetCharTable(pieceToChar, appData.pieceToCharTable);
6150             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6151                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6152         }
6153
6154         j = seed%4;                 seed /= 4;
6155         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6156         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6157         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6158         j = seed%3 + (seed%3 >= j); seed /= 3;
6159         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6160         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6161         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6162         j = seed%3;                 seed /= 3;
6163         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6164         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6165         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6166         j = seed%2 + (seed%2 >= j); seed /= 2;
6167         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6168         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6169         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6170         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6171         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6172         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6173         put(board, exoPieces[0],    0, 0, ANY);
6174         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6175 }
6176
6177 void
6178 InitPosition (int redraw)
6179 {
6180     ChessSquare (* pieces)[BOARD_FILES];
6181     int i, j, pawnRow=1, pieceRows=1, overrule,
6182     oldx = gameInfo.boardWidth,
6183     oldy = gameInfo.boardHeight,
6184     oldh = gameInfo.holdingsWidth;
6185     static int oldv;
6186
6187     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6188
6189     /* [AS] Initialize pv info list [HGM] and game status */
6190     {
6191         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6192             pvInfoList[i].depth = 0;
6193             boards[i][EP_STATUS] = EP_NONE;
6194             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6195         }
6196
6197         initialRulePlies = 0; /* 50-move counter start */
6198
6199         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6200         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6201     }
6202
6203
6204     /* [HGM] logic here is completely changed. In stead of full positions */
6205     /* the initialized data only consist of the two backranks. The switch */
6206     /* selects which one we will use, which is than copied to the Board   */
6207     /* initialPosition, which for the rest is initialized by Pawns and    */
6208     /* empty squares. This initial position is then copied to boards[0],  */
6209     /* possibly after shuffling, so that it remains available.            */
6210
6211     gameInfo.holdingsWidth = 0; /* default board sizes */
6212     gameInfo.boardWidth    = 8;
6213     gameInfo.boardHeight   = 8;
6214     gameInfo.holdingsSize  = 0;
6215     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6216     for(i=0; i<BOARD_FILES-6; i++)
6217       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6218     initialPosition[EP_STATUS] = EP_NONE;
6219     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6220     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6221     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6222          SetCharTable(pieceNickName, appData.pieceNickNames);
6223     else SetCharTable(pieceNickName, "............");
6224     pieces = FIDEArray;
6225
6226     switch (gameInfo.variant) {
6227     case VariantFischeRandom:
6228       shuffleOpenings = TRUE;
6229       appData.fischerCastling = TRUE;
6230     default:
6231       break;
6232     case VariantShatranj:
6233       pieces = ShatranjArray;
6234       nrCastlingRights = 0;
6235       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6236       break;
6237     case VariantMakruk:
6238       pieces = makrukArray;
6239       nrCastlingRights = 0;
6240       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6241       break;
6242     case VariantASEAN:
6243       pieces = aseanArray;
6244       nrCastlingRights = 0;
6245       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6246       break;
6247     case VariantTwoKings:
6248       pieces = twoKingsArray;
6249       break;
6250     case VariantGrand:
6251       pieces = GrandArray;
6252       nrCastlingRights = 0;
6253       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6254       gameInfo.boardWidth = 10;
6255       gameInfo.boardHeight = 10;
6256       gameInfo.holdingsSize = 7;
6257       break;
6258     case VariantCapaRandom:
6259       shuffleOpenings = TRUE;
6260       appData.fischerCastling = TRUE;
6261     case VariantCapablanca:
6262       pieces = CapablancaArray;
6263       gameInfo.boardWidth = 10;
6264       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6265       break;
6266     case VariantGothic:
6267       pieces = GothicArray;
6268       gameInfo.boardWidth = 10;
6269       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6270       break;
6271     case VariantSChess:
6272       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6273       gameInfo.holdingsSize = 7;
6274       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6275       break;
6276     case VariantJanus:
6277       pieces = JanusArray;
6278       gameInfo.boardWidth = 10;
6279       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6280       nrCastlingRights = 6;
6281         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6282         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6283         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6284         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6285         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6286         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6287       break;
6288     case VariantFalcon:
6289       pieces = FalconArray;
6290       gameInfo.boardWidth = 10;
6291       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6292       break;
6293     case VariantXiangqi:
6294       pieces = XiangqiArray;
6295       gameInfo.boardWidth  = 9;
6296       gameInfo.boardHeight = 10;
6297       nrCastlingRights = 0;
6298       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6299       break;
6300     case VariantShogi:
6301       pieces = ShogiArray;
6302       gameInfo.boardWidth  = 9;
6303       gameInfo.boardHeight = 9;
6304       gameInfo.holdingsSize = 7;
6305       nrCastlingRights = 0;
6306       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6307       break;
6308     case VariantChu:
6309       pieces = ChuArray; pieceRows = 3;
6310       gameInfo.boardWidth  = 12;
6311       gameInfo.boardHeight = 12;
6312       nrCastlingRights = 0;
6313 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6314   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6315       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6316                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6317       break;
6318     case VariantCourier:
6319       pieces = CourierArray;
6320       gameInfo.boardWidth  = 12;
6321       nrCastlingRights = 0;
6322       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6323       break;
6324     case VariantKnightmate:
6325       pieces = KnightmateArray;
6326       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6327       break;
6328     case VariantSpartan:
6329       pieces = SpartanArray;
6330       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6331       break;
6332     case VariantLion:
6333       pieces = lionArray;
6334       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6335       break;
6336     case VariantChuChess:
6337       pieces = ChuChessArray;
6338       gameInfo.boardWidth = 10;
6339       gameInfo.boardHeight = 10;
6340       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6341       break;
6342     case VariantFairy:
6343       pieces = fairyArray;
6344       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6345       break;
6346     case VariantGreat:
6347       pieces = GreatArray;
6348       gameInfo.boardWidth = 10;
6349       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6350       gameInfo.holdingsSize = 8;
6351       break;
6352     case VariantSuper:
6353       pieces = FIDEArray;
6354       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6355       gameInfo.holdingsSize = 8;
6356       startedFromSetupPosition = TRUE;
6357       break;
6358     case VariantCrazyhouse:
6359     case VariantBughouse:
6360       pieces = FIDEArray;
6361       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6362       gameInfo.holdingsSize = 5;
6363       break;
6364     case VariantWildCastle:
6365       pieces = FIDEArray;
6366       /* !!?shuffle with kings guaranteed to be on d or e file */
6367       shuffleOpenings = 1;
6368       break;
6369     case VariantNoCastle:
6370       /* !!?unconstrained back-rank shuffle */
6371       shuffleOpenings = 1;
6372     case VariantSuicide:
6373       pieces = FIDEArray;
6374       nrCastlingRights = 0;
6375       break;
6376     }
6377
6378     overrule = 0;
6379     if(appData.NrFiles >= 0) {
6380         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6381         gameInfo.boardWidth = appData.NrFiles;
6382     }
6383     if(appData.NrRanks >= 0) {
6384         gameInfo.boardHeight = appData.NrRanks;
6385     }
6386     if(appData.holdingsSize >= 0) {
6387         i = appData.holdingsSize;
6388 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6389         gameInfo.holdingsSize = i;
6390     }
6391     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6392     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6393         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6394
6395     if(!handSize) handSize = BOARD_HEIGHT;
6396     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6397     if(pawnRow < 1) pawnRow = 1;
6398     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6399        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6400     if(gameInfo.variant == VariantChu) pawnRow = 3;
6401
6402     /* User pieceToChar list overrules defaults */
6403     if(appData.pieceToCharTable != NULL)
6404         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6405
6406     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6407
6408         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6409             s = (ChessSquare) 0; /* account holding counts in guard band */
6410         for( i=0; i<BOARD_HEIGHT; i++ )
6411             initialPosition[i][j] = s;
6412
6413         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6414         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6415         initialPosition[pawnRow][j] = WhitePawn;
6416         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6417         if(gameInfo.variant == VariantXiangqi) {
6418             if(j&1) {
6419                 initialPosition[pawnRow][j] =
6420                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6421                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6422                    initialPosition[2][j] = WhiteCannon;
6423                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6424                 }
6425             }
6426         }
6427         if(gameInfo.variant == VariantChu) {
6428              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6429                initialPosition[pawnRow+1][j] = WhiteCobra,
6430                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6431              for(i=1; i<pieceRows; i++) {
6432                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6433                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6434              }
6435         }
6436         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6437             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6438                initialPosition[0][j] = WhiteRook;
6439                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6440             }
6441         }
6442         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6443     }
6444     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6445     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6446
6447             j=BOARD_LEFT+1;
6448             initialPosition[1][j] = WhiteBishop;
6449             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6450             j=BOARD_RGHT-2;
6451             initialPosition[1][j] = WhiteRook;
6452             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6453     }
6454
6455     if( nrCastlingRights == -1) {
6456         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6457         /*       This sets default castling rights from none to normal corners   */
6458         /* Variants with other castling rights must set them themselves above    */
6459         nrCastlingRights = 6;
6460
6461         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6462         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6463         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6464         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6465         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6466         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6467      }
6468
6469      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6470      if(gameInfo.variant == VariantGreat) { // promotion commoners
6471         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6472         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6473         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6474         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6475      }
6476      if( gameInfo.variant == VariantSChess ) {
6477       initialPosition[1][0] = BlackMarshall;
6478       initialPosition[2][0] = BlackAngel;
6479       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6480       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6481       initialPosition[1][1] = initialPosition[2][1] =
6482       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6483      }
6484      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6485   if (appData.debugMode) {
6486     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6487   }
6488     if(shuffleOpenings) {
6489         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6490         startedFromSetupPosition = TRUE;
6491     }
6492     if(startedFromPositionFile) {
6493       /* [HGM] loadPos: use PositionFile for every new game */
6494       CopyBoard(initialPosition, filePosition);
6495       for(i=0; i<nrCastlingRights; i++)
6496           initialRights[i] = filePosition[CASTLING][i];
6497       startedFromSetupPosition = TRUE;
6498     }
6499     if(*appData.men) LoadPieceDesc(appData.men);
6500
6501     CopyBoard(boards[0], initialPosition);
6502
6503     if(oldx != gameInfo.boardWidth ||
6504        oldy != gameInfo.boardHeight ||
6505        oldv != gameInfo.variant ||
6506        oldh != gameInfo.holdingsWidth
6507                                          )
6508             InitDrawingSizes(-2 ,0);
6509
6510     oldv = gameInfo.variant;
6511     if (redraw)
6512       DrawPosition(TRUE, boards[currentMove]);
6513 }
6514
6515 void
6516 SendBoard (ChessProgramState *cps, int moveNum)
6517 {
6518     char message[MSG_SIZ];
6519
6520     if (cps->useSetboard) {
6521       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6522       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6523       SendToProgram(message, cps);
6524       free(fen);
6525
6526     } else {
6527       ChessSquare *bp;
6528       int i, j, left=0, right=BOARD_WIDTH;
6529       /* Kludge to set black to move, avoiding the troublesome and now
6530        * deprecated "black" command.
6531        */
6532       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6533         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6534
6535       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6536
6537       SendToProgram("edit\n", cps);
6538       SendToProgram("#\n", cps);
6539       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6540         bp = &boards[moveNum][i][left];
6541         for (j = left; j < right; j++, bp++) {
6542           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6543           if ((int) *bp < (int) BlackPawn) {
6544             if(j == BOARD_RGHT+1)
6545                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6546             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6547             if(message[0] == '+' || message[0] == '~') {
6548               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6549                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6550                         AAA + j, ONE + i - '0');
6551             }
6552             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6553                 message[1] = BOARD_RGHT   - 1 - j + '1';
6554                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6555             }
6556             SendToProgram(message, cps);
6557           }
6558         }
6559       }
6560
6561       SendToProgram("c\n", cps);
6562       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6563         bp = &boards[moveNum][i][left];
6564         for (j = left; j < right; j++, bp++) {
6565           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6566           if (((int) *bp != (int) EmptySquare)
6567               && ((int) *bp >= (int) BlackPawn)) {
6568             if(j == BOARD_LEFT-2)
6569                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6570             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6571                     AAA + j, ONE + i - '0');
6572             if(message[0] == '+' || message[0] == '~') {
6573               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6574                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6575                         AAA + j, ONE + i - '0');
6576             }
6577             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6578                 message[1] = BOARD_RGHT   - 1 - j + '1';
6579                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6580             }
6581             SendToProgram(message, cps);
6582           }
6583         }
6584       }
6585
6586       SendToProgram(".\n", cps);
6587     }
6588     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6589 }
6590
6591 char exclusionHeader[MSG_SIZ];
6592 int exCnt, excludePtr;
6593 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6594 static Exclusion excluTab[200];
6595 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6596
6597 static void
6598 WriteMap (int s)
6599 {
6600     int j;
6601     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6602     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6603 }
6604
6605 static void
6606 ClearMap ()
6607 {
6608     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6609     excludePtr = 24; exCnt = 0;
6610     WriteMap(0);
6611 }
6612
6613 static void
6614 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6615 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6616     char buf[2*MOVE_LEN], *p;
6617     Exclusion *e = excluTab;
6618     int i;
6619     for(i=0; i<exCnt; i++)
6620         if(e[i].ff == fromX && e[i].fr == fromY &&
6621            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6622     if(i == exCnt) { // was not in exclude list; add it
6623         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6624         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6625             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6626             return; // abort
6627         }
6628         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6629         excludePtr++; e[i].mark = excludePtr++;
6630         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6631         exCnt++;
6632     }
6633     exclusionHeader[e[i].mark] = state;
6634 }
6635
6636 static int
6637 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6638 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6639     char buf[MSG_SIZ];
6640     int j, k;
6641     ChessMove moveType;
6642     if((signed char)promoChar == -1) { // kludge to indicate best move
6643         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6644             return 1; // if unparsable, abort
6645     }
6646     // update exclusion map (resolving toggle by consulting existing state)
6647     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6648     j = k%8; k >>= 3;
6649     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6650     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6651          excludeMap[k] |=   1<<j;
6652     else excludeMap[k] &= ~(1<<j);
6653     // update header
6654     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6655     // inform engine
6656     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6657     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6658     SendToBoth(buf);
6659     return (state == '+');
6660 }
6661
6662 static void
6663 ExcludeClick (int index)
6664 {
6665     int i, j;
6666     Exclusion *e = excluTab;
6667     if(index < 25) { // none, best or tail clicked
6668         if(index < 13) { // none: include all
6669             WriteMap(0); // clear map
6670             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6671             SendToBoth("include all\n"); // and inform engine
6672         } else if(index > 18) { // tail
6673             if(exclusionHeader[19] == '-') { // tail was excluded
6674                 SendToBoth("include all\n");
6675                 WriteMap(0); // clear map completely
6676                 // now re-exclude selected moves
6677                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6678                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6679             } else { // tail was included or in mixed state
6680                 SendToBoth("exclude all\n");
6681                 WriteMap(0xFF); // fill map completely
6682                 // now re-include selected moves
6683                 j = 0; // count them
6684                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6685                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6686                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6687             }
6688         } else { // best
6689             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6690         }
6691     } else {
6692         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6693             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6694             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6695             break;
6696         }
6697     }
6698 }
6699
6700 ChessSquare
6701 DefaultPromoChoice (int white)
6702 {
6703     ChessSquare result;
6704     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6705        gameInfo.variant == VariantMakruk)
6706         result = WhiteFerz; // no choice
6707     else if(gameInfo.variant == VariantASEAN)
6708         result = WhiteRook; // no choice
6709     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6710         result= WhiteKing; // in Suicide Q is the last thing we want
6711     else if(gameInfo.variant == VariantSpartan)
6712         result = white ? WhiteQueen : WhiteAngel;
6713     else result = WhiteQueen;
6714     if(!white) result = WHITE_TO_BLACK result;
6715     return result;
6716 }
6717
6718 static int autoQueen; // [HGM] oneclick
6719
6720 int
6721 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6722 {
6723     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6724     /* [HGM] add Shogi promotions */
6725     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6726     ChessSquare piece, partner;
6727     ChessMove moveType;
6728     Boolean premove;
6729
6730     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6731     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6732
6733     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6734       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6735         return FALSE;
6736
6737     if(legal[toY][toX] == 4) return FALSE;
6738
6739     piece = boards[currentMove][fromY][fromX];
6740     if(gameInfo.variant == VariantChu) {
6741         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6742         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6743         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6744     } else if(gameInfo.variant == VariantShogi) {
6745         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6746         highestPromotingPiece = (int)WhiteAlfil;
6747         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6748     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6749         promotionZoneSize = 3;
6750     }
6751
6752     // Treat Lance as Pawn when it is not representing Amazon or Lance
6753     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6754         if(piece == WhiteLance) piece = WhitePawn; else
6755         if(piece == BlackLance) piece = BlackPawn;
6756     }
6757
6758     // next weed out all moves that do not touch the promotion zone at all
6759     if((int)piece >= BlackPawn) {
6760         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6761              return FALSE;
6762         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6763         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6764     } else {
6765         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6766            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6767         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6768              return FALSE;
6769     }
6770
6771     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6772
6773     // weed out mandatory Shogi promotions
6774     if(gameInfo.variant == VariantShogi) {
6775         if(piece >= BlackPawn) {
6776             if(toY == 0 && piece == BlackPawn ||
6777                toY == 0 && piece == BlackQueen ||
6778                toY <= 1 && piece == BlackKnight) {
6779                 *promoChoice = '+';
6780                 return FALSE;
6781             }
6782         } else {
6783             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6784                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6785                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6786                 *promoChoice = '+';
6787                 return FALSE;
6788             }
6789         }
6790     }
6791
6792     // weed out obviously illegal Pawn moves
6793     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6794         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6795         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6796         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6797         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6798         // note we are not allowed to test for valid (non-)capture, due to premove
6799     }
6800
6801     // we either have a choice what to promote to, or (in Shogi) whether to promote
6802     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6803        gameInfo.variant == VariantMakruk) {
6804         ChessSquare p=BlackFerz;  // no choice
6805         while(p < EmptySquare) {  //but make sure we use piece that exists
6806             *promoChoice = PieceToChar(p++);
6807             if(*promoChoice != '.') break;
6808         }
6809         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6810     }
6811     // no sense asking what we must promote to if it is going to explode...
6812     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6813         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6814         return FALSE;
6815     }
6816     // give caller the default choice even if we will not make it
6817     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6818     partner = piece; // pieces can promote if the pieceToCharTable says so
6819     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6820     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6821     if(        sweepSelect && gameInfo.variant != VariantGreat
6822                            && gameInfo.variant != VariantGrand
6823                            && gameInfo.variant != VariantSuper) return FALSE;
6824     if(autoQueen) return FALSE; // predetermined
6825
6826     // suppress promotion popup on illegal moves that are not premoves
6827     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6828               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6829     if(appData.testLegality && !premove) {
6830         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6831                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6832         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6833         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6834             return FALSE;
6835     }
6836
6837     return TRUE;
6838 }
6839
6840 int
6841 InPalace (int row, int column)
6842 {   /* [HGM] for Xiangqi */
6843     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6844          column < (BOARD_WIDTH + 4)/2 &&
6845          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6846     return FALSE;
6847 }
6848
6849 int
6850 PieceForSquare (int x, int y)
6851 {
6852   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6853   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6854   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6855      return boards[currentMove][y][x];
6856 }
6857
6858 ChessSquare
6859 More (Board board, int col, int start, int end)
6860 {
6861     int k;
6862     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6863     return EmptySquare;
6864 }
6865
6866 void
6867 DrawPosition (int repaint, Board board)
6868 {
6869     Board compactedBoard;
6870     if(handSize > BOARD_HEIGHT && board) {
6871         int k;
6872         CopyBoard(compactedBoard, board);
6873         if(handOffsets & 1) {
6874             for(k=0; k<BOARD_HEIGHT; k++) {
6875                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6876                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6877             }
6878             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6879         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6880         if(!(handOffsets & 2)) {
6881             for(k=0; k<BOARD_HEIGHT; k++) {
6882                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6883                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6884             }
6885             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6886         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6887         DrawPositionX(TRUE, compactedBoard);
6888     } else DrawPositionX(repaint, board);
6889 }
6890
6891 int
6892 OKToStartUserMove (int x, int y)
6893 {
6894     ChessSquare from_piece;
6895     int white_piece;
6896
6897     if (matchMode) return FALSE;
6898     if (gameMode == EditPosition) return TRUE;
6899
6900     if (x >= 0 && y >= 0)
6901       from_piece = boards[currentMove][y][x];
6902     else
6903       from_piece = EmptySquare;
6904
6905     if (from_piece == EmptySquare) return FALSE;
6906
6907     white_piece = (int)from_piece >= (int)WhitePawn &&
6908       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6909
6910     switch (gameMode) {
6911       case AnalyzeFile:
6912       case TwoMachinesPlay:
6913       case EndOfGame:
6914         return FALSE;
6915
6916       case IcsObserving:
6917       case IcsIdle:
6918         return FALSE;
6919
6920       case MachinePlaysWhite:
6921       case IcsPlayingBlack:
6922         if (appData.zippyPlay) return FALSE;
6923         if (white_piece) {
6924             DisplayMoveError(_("You are playing Black"));
6925             return FALSE;
6926         }
6927         break;
6928
6929       case MachinePlaysBlack:
6930       case IcsPlayingWhite:
6931         if (appData.zippyPlay) return FALSE;
6932         if (!white_piece) {
6933             DisplayMoveError(_("You are playing White"));
6934             return FALSE;
6935         }
6936         break;
6937
6938       case PlayFromGameFile:
6939             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6940       case EditGame:
6941       case AnalyzeMode:
6942         if (!white_piece && WhiteOnMove(currentMove)) {
6943             DisplayMoveError(_("It is White's turn"));
6944             return FALSE;
6945         }
6946         if (white_piece && !WhiteOnMove(currentMove)) {
6947             DisplayMoveError(_("It is Black's turn"));
6948             return FALSE;
6949         }
6950         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6951             /* Editing correspondence game history */
6952             /* Could disallow this or prompt for confirmation */
6953             cmailOldMove = -1;
6954         }
6955         break;
6956
6957       case BeginningOfGame:
6958         if (appData.icsActive) return FALSE;
6959         if (!appData.noChessProgram) {
6960             if (!white_piece) {
6961                 DisplayMoveError(_("You are playing White"));
6962                 return FALSE;
6963             }
6964         }
6965         break;
6966
6967       case Training:
6968         if (!white_piece && WhiteOnMove(currentMove)) {
6969             DisplayMoveError(_("It is White's turn"));
6970             return FALSE;
6971         }
6972         if (white_piece && !WhiteOnMove(currentMove)) {
6973             DisplayMoveError(_("It is Black's turn"));
6974             return FALSE;
6975         }
6976         break;
6977
6978       default:
6979       case IcsExamining:
6980         break;
6981     }
6982     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6983         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6984         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6985         && gameMode != AnalyzeFile && gameMode != Training) {
6986         DisplayMoveError(_("Displayed position is not current"));
6987         return FALSE;
6988     }
6989     return TRUE;
6990 }
6991
6992 Boolean
6993 OnlyMove (int *x, int *y, Boolean captures)
6994 {
6995     DisambiguateClosure cl;
6996     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6997     switch(gameMode) {
6998       case MachinePlaysBlack:
6999       case IcsPlayingWhite:
7000       case BeginningOfGame:
7001         if(!WhiteOnMove(currentMove)) return FALSE;
7002         break;
7003       case MachinePlaysWhite:
7004       case IcsPlayingBlack:
7005         if(WhiteOnMove(currentMove)) return FALSE;
7006         break;
7007       case EditGame:
7008         break;
7009       default:
7010         return FALSE;
7011     }
7012     cl.pieceIn = EmptySquare;
7013     cl.rfIn = *y;
7014     cl.ffIn = *x;
7015     cl.rtIn = -1;
7016     cl.ftIn = -1;
7017     cl.promoCharIn = NULLCHAR;
7018     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7019     if( cl.kind == NormalMove ||
7020         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7021         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7022         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7023       fromX = cl.ff;
7024       fromY = cl.rf;
7025       *x = cl.ft;
7026       *y = cl.rt;
7027       return TRUE;
7028     }
7029     if(cl.kind != ImpossibleMove) return FALSE;
7030     cl.pieceIn = EmptySquare;
7031     cl.rfIn = -1;
7032     cl.ffIn = -1;
7033     cl.rtIn = *y;
7034     cl.ftIn = *x;
7035     cl.promoCharIn = NULLCHAR;
7036     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7037     if( cl.kind == NormalMove ||
7038         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7039         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7040         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7041       fromX = cl.ff;
7042       fromY = cl.rf;
7043       *x = cl.ft;
7044       *y = cl.rt;
7045       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7046       return TRUE;
7047     }
7048     return FALSE;
7049 }
7050
7051 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7052 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7053 int lastLoadGameUseList = FALSE;
7054 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7055 ChessMove lastLoadGameStart = EndOfFile;
7056 int doubleClick;
7057 Boolean addToBookFlag;
7058 static Board rightsBoard, nullBoard;
7059
7060 void
7061 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7062 {
7063     ChessMove moveType;
7064     ChessSquare pup;
7065     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7066
7067     /* Check if the user is playing in turn.  This is complicated because we
7068        let the user "pick up" a piece before it is his turn.  So the piece he
7069        tried to pick up may have been captured by the time he puts it down!
7070        Therefore we use the color the user is supposed to be playing in this
7071        test, not the color of the piece that is currently on the starting
7072        square---except in EditGame mode, where the user is playing both
7073        sides; fortunately there the capture race can't happen.  (It can
7074        now happen in IcsExamining mode, but that's just too bad.  The user
7075        will get a somewhat confusing message in that case.)
7076        */
7077
7078     switch (gameMode) {
7079       case AnalyzeFile:
7080       case TwoMachinesPlay:
7081       case EndOfGame:
7082       case IcsObserving:
7083       case IcsIdle:
7084         /* We switched into a game mode where moves are not accepted,
7085            perhaps while the mouse button was down. */
7086         return;
7087
7088       case MachinePlaysWhite:
7089         /* User is moving for Black */
7090         if (WhiteOnMove(currentMove)) {
7091             DisplayMoveError(_("It is White's turn"));
7092             return;
7093         }
7094         break;
7095
7096       case MachinePlaysBlack:
7097         /* User is moving for White */
7098         if (!WhiteOnMove(currentMove)) {
7099             DisplayMoveError(_("It is Black's turn"));
7100             return;
7101         }
7102         break;
7103
7104       case PlayFromGameFile:
7105             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7106       case EditGame:
7107       case IcsExamining:
7108       case BeginningOfGame:
7109       case AnalyzeMode:
7110       case Training:
7111         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7112         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7113             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7114             /* User is moving for Black */
7115             if (WhiteOnMove(currentMove)) {
7116                 DisplayMoveError(_("It is White's turn"));
7117                 return;
7118             }
7119         } else {
7120             /* User is moving for White */
7121             if (!WhiteOnMove(currentMove)) {
7122                 DisplayMoveError(_("It is Black's turn"));
7123                 return;
7124             }
7125         }
7126         break;
7127
7128       case IcsPlayingBlack:
7129         /* User is moving for Black */
7130         if (WhiteOnMove(currentMove)) {
7131             if (!appData.premove) {
7132                 DisplayMoveError(_("It is White's turn"));
7133             } else if (toX >= 0 && toY >= 0) {
7134                 premoveToX = toX;
7135                 premoveToY = toY;
7136                 premoveFromX = fromX;
7137                 premoveFromY = fromY;
7138                 premovePromoChar = promoChar;
7139                 gotPremove = 1;
7140                 if (appData.debugMode)
7141                     fprintf(debugFP, "Got premove: fromX %d,"
7142                             "fromY %d, toX %d, toY %d\n",
7143                             fromX, fromY, toX, toY);
7144             }
7145             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7146             return;
7147         }
7148         break;
7149
7150       case IcsPlayingWhite:
7151         /* User is moving for White */
7152         if (!WhiteOnMove(currentMove)) {
7153             if (!appData.premove) {
7154                 DisplayMoveError(_("It is Black's turn"));
7155             } else if (toX >= 0 && toY >= 0) {
7156                 premoveToX = toX;
7157                 premoveToY = toY;
7158                 premoveFromX = fromX;
7159                 premoveFromY = fromY;
7160                 premovePromoChar = promoChar;
7161                 gotPremove = 1;
7162                 if (appData.debugMode)
7163                     fprintf(debugFP, "Got premove: fromX %d,"
7164                             "fromY %d, toX %d, toY %d\n",
7165                             fromX, fromY, toX, toY);
7166             }
7167             DrawPosition(TRUE, boards[currentMove]);
7168             return;
7169         }
7170         break;
7171
7172       default:
7173         break;
7174
7175       case EditPosition:
7176         /* EditPosition, empty square, or different color piece;
7177            click-click move is possible */
7178         if (toX == -2 || toY == -2) {
7179             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7180             DrawPosition(FALSE, boards[currentMove]);
7181             return;
7182         } else if (toX >= 0 && toY >= 0) {
7183             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7184                 ChessSquare p = boards[0][rf][ff];
7185                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7186                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7187                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7188                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7189                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7190                     gatingPiece = p;
7191                 }
7192             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7193             boards[0][toY][toX] = boards[0][fromY][fromX];
7194             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7195                 if(boards[0][fromY][0] != EmptySquare) {
7196                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7197                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7198                 }
7199             } else
7200             if(fromX == BOARD_RGHT+1) {
7201                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7202                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7203                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7204                 }
7205             } else
7206             boards[0][fromY][fromX] = gatingPiece;
7207             ClearHighlights();
7208             DrawPosition(FALSE, boards[currentMove]);
7209             return;
7210         }
7211         return;
7212     }
7213
7214     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7215     pup = boards[currentMove][toY][toX];
7216
7217     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7218     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7219          if( pup != EmptySquare ) return;
7220          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7221            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7222                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7223            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7224            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7225            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7226            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7227          fromY = DROP_RANK;
7228     }
7229
7230     /* [HGM] always test for legality, to get promotion info */
7231     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7232                                          fromY, fromX, toY, toX, promoChar);
7233
7234     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7235
7236     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7237
7238     /* [HGM] but possibly ignore an IllegalMove result */
7239     if (appData.testLegality) {
7240         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7241             DisplayMoveError(_("Illegal move"));
7242             return;
7243         }
7244     }
7245
7246     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7247         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7248              ClearPremoveHighlights(); // was included
7249         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7250         DrawPosition(FALSE, NULL);
7251         return;
7252     }
7253
7254     if(addToBookFlag) { // adding moves to book
7255         char buf[MSG_SIZ], move[MSG_SIZ];
7256         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7257         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7258                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7259         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7260         AddBookMove(buf);
7261         addToBookFlag = FALSE;
7262         ClearHighlights();
7263         return;
7264     }
7265
7266     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7267 }
7268
7269 /* Common tail of UserMoveEvent and DropMenuEvent */
7270 int
7271 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7272 {
7273     char *bookHit = 0;
7274
7275     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7276         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7277         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7278         if(WhiteOnMove(currentMove)) {
7279             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7280         } else {
7281             if(!boards[currentMove][handSize-1-k][1]) return 0;
7282         }
7283     }
7284
7285     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7286        move type in caller when we know the move is a legal promotion */
7287     if(moveType == NormalMove && promoChar)
7288         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7289
7290     /* [HGM] <popupFix> The following if has been moved here from
7291        UserMoveEvent(). Because it seemed to belong here (why not allow
7292        piece drops in training games?), and because it can only be
7293        performed after it is known to what we promote. */
7294     if (gameMode == Training) {
7295       /* compare the move played on the board to the next move in the
7296        * game. If they match, display the move and the opponent's response.
7297        * If they don't match, display an error message.
7298        */
7299       int saveAnimate;
7300       Board testBoard;
7301       CopyBoard(testBoard, boards[currentMove]);
7302       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7303
7304       if (CompareBoards(testBoard, boards[currentMove+1])) {
7305         ForwardInner(currentMove+1);
7306
7307         /* Autoplay the opponent's response.
7308          * if appData.animate was TRUE when Training mode was entered,
7309          * the response will be animated.
7310          */
7311         saveAnimate = appData.animate;
7312         appData.animate = animateTraining;
7313         ForwardInner(currentMove+1);
7314         appData.animate = saveAnimate;
7315
7316         /* check for the end of the game */
7317         if (currentMove >= forwardMostMove) {
7318           gameMode = PlayFromGameFile;
7319           ModeHighlight();
7320           SetTrainingModeOff();
7321           DisplayInformation(_("End of game"));
7322         }
7323       } else {
7324         DisplayError(_("Incorrect move"), 0);
7325       }
7326       return 1;
7327     }
7328
7329   /* Ok, now we know that the move is good, so we can kill
7330      the previous line in Analysis Mode */
7331   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7332                                 && currentMove < forwardMostMove) {
7333     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7334     else forwardMostMove = currentMove;
7335   }
7336
7337   ClearMap();
7338
7339   /* If we need the chess program but it's dead, restart it */
7340   ResurrectChessProgram();
7341
7342   /* A user move restarts a paused game*/
7343   if (pausing)
7344     PauseEvent();
7345
7346   thinkOutput[0] = NULLCHAR;
7347
7348   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7349
7350   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7351     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7352     return 1;
7353   }
7354
7355   if (gameMode == BeginningOfGame) {
7356     if (appData.noChessProgram) {
7357       gameMode = EditGame;
7358       SetGameInfo();
7359     } else {
7360       char buf[MSG_SIZ];
7361       gameMode = MachinePlaysBlack;
7362       StartClocks();
7363       SetGameInfo();
7364       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7365       DisplayTitle(buf);
7366       if (first.sendName) {
7367         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7368         SendToProgram(buf, &first);
7369       }
7370       StartClocks();
7371     }
7372     ModeHighlight();
7373   }
7374
7375   /* Relay move to ICS or chess engine */
7376   if (appData.icsActive) {
7377     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7378         gameMode == IcsExamining) {
7379       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7380         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7381         SendToICS("draw ");
7382         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7383       }
7384       // also send plain move, in case ICS does not understand atomic claims
7385       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7386       ics_user_moved = 1;
7387     }
7388   } else {
7389     if (first.sendTime && (gameMode == BeginningOfGame ||
7390                            gameMode == MachinePlaysWhite ||
7391                            gameMode == MachinePlaysBlack)) {
7392       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7393     }
7394     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7395          // [HGM] book: if program might be playing, let it use book
7396         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7397         first.maybeThinking = TRUE;
7398     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7399         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7400         SendBoard(&first, currentMove+1);
7401         if(second.analyzing) {
7402             if(!second.useSetboard) SendToProgram("undo\n", &second);
7403             SendBoard(&second, currentMove+1);
7404         }
7405     } else {
7406         SendMoveToProgram(forwardMostMove-1, &first);
7407         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7408     }
7409     if (currentMove == cmailOldMove + 1) {
7410       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7411     }
7412   }
7413
7414   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7415
7416   switch (gameMode) {
7417   case EditGame:
7418     if(appData.testLegality)
7419     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7420     case MT_NONE:
7421     case MT_CHECK:
7422       break;
7423     case MT_CHECKMATE:
7424     case MT_STAINMATE:
7425       if (WhiteOnMove(currentMove)) {
7426         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7427       } else {
7428         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7429       }
7430       break;
7431     case MT_STALEMATE:
7432       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7433       break;
7434     }
7435     break;
7436
7437   case MachinePlaysBlack:
7438   case MachinePlaysWhite:
7439     /* disable certain menu options while machine is thinking */
7440     SetMachineThinkingEnables();
7441     break;
7442
7443   default:
7444     break;
7445   }
7446
7447   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7448   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7449
7450   if(bookHit) { // [HGM] book: simulate book reply
7451         static char bookMove[MSG_SIZ]; // a bit generous?
7452
7453         programStats.nodes = programStats.depth = programStats.time =
7454         programStats.score = programStats.got_only_move = 0;
7455         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7456
7457         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7458         strcat(bookMove, bookHit);
7459         HandleMachineMove(bookMove, &first);
7460   }
7461   return 1;
7462 }
7463
7464 void
7465 MarkByFEN(char *fen)
7466 {
7467         int r, f;
7468         if(!appData.markers || !appData.highlightDragging) return;
7469         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7470         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7471         while(*fen) {
7472             int s = 0;
7473             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7474             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7475             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7476             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7477             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7478             if(*fen == 'T') marker[r][f++] = 0; else
7479             if(*fen == 'Y') marker[r][f++] = 1; else
7480             if(*fen == 'G') marker[r][f++] = 3; else
7481             if(*fen == 'B') marker[r][f++] = 4; else
7482             if(*fen == 'C') marker[r][f++] = 5; else
7483             if(*fen == 'M') marker[r][f++] = 6; else
7484             if(*fen == 'W') marker[r][f++] = 7; else
7485             if(*fen == 'D') marker[r][f++] = 8; else
7486             if(*fen == 'R') marker[r][f++] = 2; else {
7487                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7488               f += s; fen -= s>0;
7489             }
7490             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7491             if(r < 0) break;
7492             fen++;
7493         }
7494         DrawPosition(TRUE, NULL);
7495 }
7496
7497 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7498
7499 void
7500 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7501 {
7502     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7503     Markers *m = (Markers *) closure;
7504     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7505                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7506         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7507                          || kind == WhiteCapturesEnPassant
7508                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7509     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7510 }
7511
7512 static int hoverSavedValid;
7513
7514 void
7515 MarkTargetSquares (int clear)
7516 {
7517   int x, y, sum=0;
7518   if(clear) { // no reason to ever suppress clearing
7519     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7520     hoverSavedValid = 0;
7521     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7522   } else {
7523     int capt = 0;
7524     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7525        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7526     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7527     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7528       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7529       if(capt)
7530       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7531     }
7532   }
7533   DrawPosition(FALSE, NULL);
7534 }
7535
7536 int
7537 Explode (Board board, int fromX, int fromY, int toX, int toY)
7538 {
7539     if(gameInfo.variant == VariantAtomic &&
7540        (board[toY][toX] != EmptySquare ||                     // capture?
7541         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7542                          board[fromY][fromX] == BlackPawn   )
7543       )) {
7544         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7545         return TRUE;
7546     }
7547     return FALSE;
7548 }
7549
7550 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7551
7552 int
7553 CanPromote (ChessSquare piece, int y)
7554 {
7555         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7556         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7557         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7558         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7559            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7560           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7561            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7562         return (piece == BlackPawn && y <= zone ||
7563                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7564                 piece == BlackLance && y <= zone ||
7565                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7566 }
7567
7568 void
7569 HoverEvent (int xPix, int yPix, int x, int y)
7570 {
7571         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7572         int r, f;
7573         if(!first.highlight) return;
7574         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7575         if(x == oldX && y == oldY) return; // only do something if we enter new square
7576         oldFromX = fromX; oldFromY = fromY;
7577         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7578           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7579             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7580           hoverSavedValid = 1;
7581         } else if(oldX != x || oldY != y) {
7582           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7583           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7584           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7585             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7586           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7587             char buf[MSG_SIZ];
7588             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7589             SendToProgram(buf, &first);
7590           }
7591           oldX = x; oldY = y;
7592 //        SetHighlights(fromX, fromY, x, y);
7593         }
7594 }
7595
7596 void ReportClick(char *action, int x, int y)
7597 {
7598         char buf[MSG_SIZ]; // Inform engine of what user does
7599         int r, f;
7600         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7601           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7602             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7603         if(!first.highlight || gameMode == EditPosition) return;
7604         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7605         SendToProgram(buf, &first);
7606 }
7607
7608 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7609 Boolean deferChoice;
7610 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7611
7612 void
7613 LeftClick (ClickType clickType, int xPix, int yPix)
7614 {
7615     int x, y;
7616     static Boolean saveAnimate;
7617     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7618     char promoChoice = NULLCHAR;
7619     ChessSquare piece;
7620     static TimeMark lastClickTime, prevClickTime;
7621
7622     if(flashing) return;
7623
7624   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7625     x = EventToSquare(xPix, BOARD_WIDTH);
7626     y = EventToSquare(yPix, BOARD_HEIGHT);
7627     if (!flipView && y >= 0) {
7628         y = BOARD_HEIGHT - 1 - y;
7629     }
7630     if (flipView && x >= 0) {
7631         x = BOARD_WIDTH - 1 - x;
7632     }
7633
7634     // map clicks in offsetted holdings back to true coords (or switch the offset)
7635     if(x == BOARD_RGHT+1) {
7636         if(handOffsets & 1) {
7637             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7638             y += handSize - BOARD_HEIGHT;
7639         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7640     }
7641     if(x == BOARD_LEFT-2) {
7642         if(!(handOffsets & 2)) {
7643             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7644             y += handSize - BOARD_HEIGHT;
7645         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7646     }
7647
7648     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7649         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7650         static int dummy;
7651         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7652         right = TRUE;
7653         return;
7654     }
7655
7656     createX = createY = -1;
7657
7658     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7659
7660     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7661
7662     if (clickType == Press) ErrorPopDown();
7663     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7664
7665     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7666         defaultPromoChoice = promoSweep;
7667         promoSweep = EmptySquare;   // terminate sweep
7668         promoDefaultAltered = TRUE;
7669         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7670     }
7671
7672     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7673         if(clickType == Release) return; // ignore upclick of click-click destination
7674         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7675         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7676         if(gameInfo.holdingsWidth &&
7677                 (WhiteOnMove(currentMove)
7678                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7679                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7680             // click in right holdings, for determining promotion piece
7681             ChessSquare p = boards[currentMove][y][x];
7682             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7683             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7684             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7685                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7686                 fromX = fromY = -1;
7687                 return;
7688             }
7689         }
7690         DrawPosition(FALSE, boards[currentMove]);
7691         return;
7692     }
7693
7694     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7695     if(clickType == Press
7696             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7697               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7698               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7699         return;
7700
7701     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7702         // could be static click on premove from-square: abort premove
7703         gotPremove = 0;
7704         ClearPremoveHighlights();
7705     }
7706
7707     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7708         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7709
7710     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7711         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7712                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7713         defaultPromoChoice = DefaultPromoChoice(side);
7714     }
7715
7716     autoQueen = appData.alwaysPromoteToQueen;
7717
7718     if (fromX == -1) {
7719       int originalY = y;
7720       gatingPiece = EmptySquare;
7721       if (clickType != Press) {
7722         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7723             DragPieceEnd(xPix, yPix); dragging = 0;
7724             DrawPosition(FALSE, NULL);
7725         }
7726         return;
7727       }
7728       doubleClick = FALSE;
7729       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7730         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7731       }
7732       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7733       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7734          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7735          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7736             /* First square */
7737             if (OKToStartUserMove(fromX, fromY)) {
7738                 second = 0;
7739                 ReportClick("lift", x, y);
7740                 MarkTargetSquares(0);
7741                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7742                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7743                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7744                     promoSweep = defaultPromoChoice;
7745                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7746                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7747                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7748                 }
7749                 if (appData.highlightDragging) {
7750                     SetHighlights(fromX, fromY, -1, -1);
7751                 } else {
7752                     ClearHighlights();
7753                 }
7754             } else fromX = fromY = -1;
7755             return;
7756         }
7757     }
7758
7759     /* fromX != -1 */
7760     if (clickType == Press && gameMode != EditPosition) {
7761         ChessSquare fromP;
7762         ChessSquare toP;
7763         int frc;
7764
7765         // ignore off-board to clicks
7766         if(y < 0 || x < 0) return;
7767
7768         /* Check if clicking again on the same color piece */
7769         fromP = boards[currentMove][fromY][fromX];
7770         toP = boards[currentMove][y][x];
7771         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7772         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7773             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7774            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7775              WhitePawn <= toP && toP <= WhiteKing &&
7776              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7777              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7778             (BlackPawn <= fromP && fromP <= BlackKing &&
7779              BlackPawn <= toP && toP <= BlackKing &&
7780              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7781              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7782             /* Clicked again on same color piece -- changed his mind */
7783             second = (x == fromX && y == fromY);
7784             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7785             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7786                 second = FALSE; // first double-click rather than scond click
7787                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7788             }
7789             promoDefaultAltered = FALSE;
7790            if(!second) MarkTargetSquares(1);
7791            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7792             if (appData.highlightDragging) {
7793                 SetHighlights(x, y, -1, -1);
7794             } else {
7795                 ClearHighlights();
7796             }
7797             if (OKToStartUserMove(x, y)) {
7798                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7799                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7800                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7801                  gatingPiece = boards[currentMove][fromY][fromX];
7802                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7803                 fromX = x;
7804                 fromY = y; dragging = 1;
7805                 if(!second) ReportClick("lift", x, y);
7806                 MarkTargetSquares(0);
7807                 DragPieceBegin(xPix, yPix, FALSE);
7808                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7809                     promoSweep = defaultPromoChoice;
7810                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7811                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7812                 }
7813             }
7814            }
7815            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7816            second = FALSE;
7817         }
7818         // ignore clicks on holdings
7819         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7820     }
7821
7822     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7823         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7824         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7825         return;
7826     }
7827
7828     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7829         DragPieceEnd(xPix, yPix); dragging = 0;
7830         if(clearFlag) {
7831             // a deferred attempt to click-click move an empty square on top of a piece
7832             boards[currentMove][y][x] = EmptySquare;
7833             ClearHighlights();
7834             DrawPosition(FALSE, boards[currentMove]);
7835             fromX = fromY = -1; clearFlag = 0;
7836             return;
7837         }
7838         if (appData.animateDragging) {
7839             /* Undo animation damage if any */
7840             DrawPosition(FALSE, NULL);
7841         }
7842         if (second) {
7843             /* Second up/down in same square; just abort move */
7844             second = 0;
7845             fromX = fromY = -1;
7846             gatingPiece = EmptySquare;
7847             ClearHighlights();
7848             gotPremove = 0;
7849             ClearPremoveHighlights();
7850             MarkTargetSquares(-1);
7851             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7852         } else {
7853             /* First upclick in same square; start click-click mode */
7854             SetHighlights(x, y, -1, -1);
7855         }
7856         return;
7857     }
7858
7859     clearFlag = 0;
7860
7861     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7862        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7863         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7864         DisplayMessage(_("only marked squares are legal"),"");
7865         DrawPosition(TRUE, NULL);
7866         return; // ignore to-click
7867     }
7868
7869     /* we now have a different from- and (possibly off-board) to-square */
7870     /* Completed move */
7871     if(!sweepSelecting) {
7872         toX = x;
7873         toY = y;
7874     }
7875
7876     piece = boards[currentMove][fromY][fromX];
7877
7878     saveAnimate = appData.animate;
7879     if (clickType == Press) {
7880         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7881         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7882             // must be Edit Position mode with empty-square selected
7883             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7884             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7885             return;
7886         }
7887         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7888             return;
7889         }
7890         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7891             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7892         } else
7893         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7894         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7895           if(appData.sweepSelect) {
7896             promoSweep = defaultPromoChoice;
7897             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7898             selectFlag = 0; lastX = xPix; lastY = yPix;
7899             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7900             saveFlash = appData.flashCount; appData.flashCount = 0;
7901             Sweep(0); // Pawn that is going to promote: preview promotion piece
7902             sweepSelecting = 1;
7903             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7904             MarkTargetSquares(1);
7905           }
7906           return; // promo popup appears on up-click
7907         }
7908         /* Finish clickclick move */
7909         if (appData.animate || appData.highlightLastMove) {
7910             SetHighlights(fromX, fromY, toX, toY);
7911         } else {
7912             ClearHighlights();
7913         }
7914         MarkTargetSquares(1);
7915     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7916         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7917         *promoRestrict = 0; appData.flashCount = saveFlash;
7918         if (appData.animate || appData.highlightLastMove) {
7919             SetHighlights(fromX, fromY, toX, toY);
7920         } else {
7921             ClearHighlights();
7922         }
7923         MarkTargetSquares(1);
7924     } else {
7925 #if 0
7926 // [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
7927         /* Finish drag move */
7928         if (appData.highlightLastMove) {
7929             SetHighlights(fromX, fromY, toX, toY);
7930         } else {
7931             ClearHighlights();
7932         }
7933 #endif
7934         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7935           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7936         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7937         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7938           dragging *= 2;            // flag button-less dragging if we are dragging
7939           MarkTargetSquares(1);
7940           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7941           else {
7942             kill2X = killX; kill2Y = killY;
7943             killX = x; killY = y;     // remember this square as intermediate
7944             ReportClick("put", x, y); // and inform engine
7945             ReportClick("lift", x, y);
7946             MarkTargetSquares(0);
7947             return;
7948           }
7949         }
7950         DragPieceEnd(xPix, yPix); dragging = 0;
7951         /* Don't animate move and drag both */
7952         appData.animate = FALSE;
7953         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7954     }
7955
7956     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7957     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7958         ChessSquare piece = boards[currentMove][fromY][fromX];
7959         if(gameMode == EditPosition && piece != EmptySquare &&
7960            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7961             int n;
7962
7963             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7964                 n = PieceToNumber(piece - (int)BlackPawn);
7965                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7966                 boards[currentMove][handSize-1 - n][0] = piece;
7967                 boards[currentMove][handSize-1 - n][1]++;
7968             } else
7969             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7970                 n = PieceToNumber(piece);
7971                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7972                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7973                 boards[currentMove][n][BOARD_WIDTH-2]++;
7974             }
7975             boards[currentMove][fromY][fromX] = EmptySquare;
7976         }
7977         ClearHighlights();
7978         fromX = fromY = -1;
7979         MarkTargetSquares(1);
7980         DrawPosition(TRUE, boards[currentMove]);
7981         return;
7982     }
7983
7984     // off-board moves should not be highlighted
7985     if(x < 0 || y < 0) {
7986         ClearHighlights();
7987         DrawPosition(FALSE, NULL);
7988     } else ReportClick("put", x, y);
7989
7990     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7991  }
7992
7993     if(legal[toY][toX] == 2) { // highlight-induced promotion
7994         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7995         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7996     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7997       if(!*promoRestrict) {           // but has not done that yet
7998         deferChoice = TRUE;           // set up retry for when it does
7999         return;                       // and wait for that
8000       }
8001       promoChoice = ToLower(*promoRestrict); // force engine's choice
8002       deferChoice = FALSE;
8003     }
8004
8005     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
8006         SetHighlights(fromX, fromY, toX, toY);
8007         MarkTargetSquares(1);
8008         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
8009             // [HGM] super: promotion to captured piece selected from holdings
8010             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
8011             promotionChoice = TRUE;
8012             // kludge follows to temporarily execute move on display, without promoting yet
8013             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8014             boards[currentMove][toY][toX] = p;
8015             DrawPosition(FALSE, boards[currentMove]);
8016             boards[currentMove][fromY][fromX] = p; // take back, but display stays
8017             boards[currentMove][toY][toX] = q;
8018             DisplayMessage("Click in holdings to choose piece", "");
8019             return;
8020         }
8021         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8022         PromotionPopUp(promoChoice);
8023     } else {
8024         int oldMove = currentMove;
8025         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8026         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8027         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8028         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8029         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8030            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8031             DrawPosition(TRUE, boards[currentMove]);
8032         else DrawPosition(FALSE, NULL);
8033         fromX = fromY = -1;
8034         flashing = 0;
8035     }
8036     appData.animate = saveAnimate;
8037     if (appData.animate || appData.animateDragging) {
8038         /* Undo animation damage if needed */
8039 //      DrawPosition(FALSE, NULL);
8040     }
8041 }
8042
8043 int
8044 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8045 {   // front-end-free part taken out of PieceMenuPopup
8046     int whichMenu; int xSqr, ySqr;
8047
8048     if(seekGraphUp) { // [HGM] seekgraph
8049         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8050         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8051         return -2;
8052     }
8053
8054     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8055          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8056         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8057         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8058         if(action == Press)   {
8059             originalFlip = flipView;
8060             flipView = !flipView; // temporarily flip board to see game from partners perspective
8061             DrawPosition(TRUE, partnerBoard);
8062             DisplayMessage(partnerStatus, "");
8063             partnerUp = TRUE;
8064         } else if(action == Release) {
8065             flipView = originalFlip;
8066             DrawPosition(TRUE, boards[currentMove]);
8067             partnerUp = FALSE;
8068         }
8069         return -2;
8070     }
8071
8072     xSqr = EventToSquare(x, BOARD_WIDTH);
8073     ySqr = EventToSquare(y, BOARD_HEIGHT);
8074     if (action == Release) {
8075         if(pieceSweep != EmptySquare) {
8076             EditPositionMenuEvent(pieceSweep, toX, toY);
8077             pieceSweep = EmptySquare;
8078         } else UnLoadPV(); // [HGM] pv
8079     }
8080     if (action != Press) return -2; // return code to be ignored
8081     switch (gameMode) {
8082       case IcsExamining:
8083         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8084       case EditPosition:
8085         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8086         if (xSqr < 0 || ySqr < 0) return -1;
8087         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8088         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8089         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8090             ChessSquare p = boards[currentMove][ySqr][xSqr];
8091             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8092             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8093             return -2;
8094         }
8095         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8096         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8097         NextPiece(0);
8098         return 2; // grab
8099       case IcsObserving:
8100         if(!appData.icsEngineAnalyze) return -1;
8101       case IcsPlayingWhite:
8102       case IcsPlayingBlack:
8103         if(!appData.zippyPlay) goto noZip;
8104       case AnalyzeMode:
8105       case AnalyzeFile:
8106       case MachinePlaysWhite:
8107       case MachinePlaysBlack:
8108       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8109         if (!appData.dropMenu) {
8110           LoadPV(x, y);
8111           return 2; // flag front-end to grab mouse events
8112         }
8113         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8114            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8115       case EditGame:
8116       noZip:
8117         if (xSqr < 0 || ySqr < 0) return -1;
8118         if (!appData.dropMenu || appData.testLegality &&
8119             gameInfo.variant != VariantBughouse &&
8120             gameInfo.variant != VariantCrazyhouse) return -1;
8121         whichMenu = 1; // drop menu
8122         break;
8123       default:
8124         return -1;
8125     }
8126
8127     if (((*fromX = xSqr) < 0) ||
8128         ((*fromY = ySqr) < 0)) {
8129         *fromX = *fromY = -1;
8130         return -1;
8131     }
8132     if (flipView)
8133       *fromX = BOARD_WIDTH - 1 - *fromX;
8134     else
8135       *fromY = BOARD_HEIGHT - 1 - *fromY;
8136
8137     return whichMenu;
8138 }
8139
8140 void
8141 Wheel (int dir, int x, int y)
8142 {
8143     if(gameMode == EditPosition) {
8144         int xSqr = EventToSquare(x, BOARD_WIDTH);
8145         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8146         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8147         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8148         do {
8149             boards[currentMove][ySqr][xSqr] += dir;
8150             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8151             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8152         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8153         DrawPosition(FALSE, boards[currentMove]);
8154     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8155 }
8156
8157 void
8158 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8159 {
8160 //    char * hint = lastHint;
8161     FrontEndProgramStats stats;
8162
8163     stats.which = cps == &first ? 0 : 1;
8164     stats.depth = cpstats->depth;
8165     stats.nodes = cpstats->nodes;
8166     stats.score = cpstats->score;
8167     stats.time = cpstats->time;
8168     stats.pv = cpstats->movelist;
8169     stats.hint = lastHint;
8170     stats.an_move_index = 0;
8171     stats.an_move_count = 0;
8172
8173     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8174         stats.hint = cpstats->move_name;
8175         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8176         stats.an_move_count = cpstats->nr_moves;
8177     }
8178
8179     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
8180
8181     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8182         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8183
8184     SetProgramStats( &stats );
8185 }
8186
8187 void
8188 ClearEngineOutputPane (int which)
8189 {
8190     static FrontEndProgramStats dummyStats;
8191     dummyStats.which = which;
8192     dummyStats.pv = "#";
8193     SetProgramStats( &dummyStats );
8194 }
8195
8196 #define MAXPLAYERS 500
8197
8198 char *
8199 TourneyStandings (int display)
8200 {
8201     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8202     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8203     char result, *p, *names[MAXPLAYERS];
8204
8205     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8206         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8207     names[0] = p = strdup(appData.participants);
8208     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8209
8210     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8211
8212     while(result = appData.results[nr]) {
8213         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8214         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8215         wScore = bScore = 0;
8216         switch(result) {
8217           case '+': wScore = 2; break;
8218           case '-': bScore = 2; break;
8219           case '=': wScore = bScore = 1; break;
8220           case ' ':
8221           case '*': return strdup("busy"); // tourney not finished
8222         }
8223         score[w] += wScore;
8224         score[b] += bScore;
8225         games[w]++;
8226         games[b]++;
8227         nr++;
8228     }
8229     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8230     for(w=0; w<nPlayers; w++) {
8231         bScore = -1;
8232         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8233         ranking[w] = b; points[w] = bScore; score[b] = -2;
8234     }
8235     p = malloc(nPlayers*34+1);
8236     for(w=0; w<nPlayers && w<display; w++)
8237         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8238     free(names[0]);
8239     return p;
8240 }
8241
8242 void
8243 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8244 {       // count all piece types
8245         int p, f, r;
8246         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8247         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8248         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8249                 p = board[r][f];
8250                 pCnt[p]++;
8251                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8252                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8253                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8254                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8255                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8256                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8257         }
8258 }
8259
8260 int
8261 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8262 {
8263         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8264         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8265
8266         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8267         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8268         if(myPawns == 2 && nMine == 3) // KPP
8269             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8270         if(myPawns == 1 && nMine == 2) // KP
8271             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8272         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8273             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8274         if(myPawns) return FALSE;
8275         if(pCnt[WhiteRook+side])
8276             return pCnt[BlackRook-side] ||
8277                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8278                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8279                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8280         if(pCnt[WhiteCannon+side]) {
8281             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8282             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8283         }
8284         if(pCnt[WhiteKnight+side])
8285             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8286         return FALSE;
8287 }
8288
8289 int
8290 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8291 {
8292         VariantClass v = gameInfo.variant;
8293
8294         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8295         if(v == VariantShatranj) return TRUE; // always winnable through baring
8296         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8297         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8298
8299         if(v == VariantXiangqi) {
8300                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8301
8302                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8303                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8304                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8305                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8306                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8307                 if(stale) // we have at least one last-rank P plus perhaps C
8308                     return majors // KPKX
8309                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8310                 else // KCA*E*
8311                     return pCnt[WhiteFerz+side] // KCAK
8312                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8313                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8314                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8315
8316         } else if(v == VariantKnightmate) {
8317                 if(nMine == 1) return FALSE;
8318                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8319         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8320                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8321
8322                 if(nMine == 1) return FALSE; // bare King
8323                 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
8324                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8325                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8326                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8327                 if(pCnt[WhiteKnight+side])
8328                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8329                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8330                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8331                 if(nBishops)
8332                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8333                 if(pCnt[WhiteAlfil+side])
8334                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8335                 if(pCnt[WhiteWazir+side])
8336                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8337         }
8338
8339         return TRUE;
8340 }
8341
8342 int
8343 CompareWithRights (Board b1, Board b2)
8344 {
8345     int rights = 0;
8346     if(!CompareBoards(b1, b2)) return FALSE;
8347     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8348     /* compare castling rights */
8349     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8350            rights++; /* King lost rights, while rook still had them */
8351     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8352         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8353            rights++; /* but at least one rook lost them */
8354     }
8355     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8356            rights++;
8357     if( b1[CASTLING][5] != NoRights ) {
8358         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8359            rights++;
8360     }
8361     return rights == 0;
8362 }
8363
8364 int
8365 Adjudicate (ChessProgramState *cps)
8366 {       // [HGM] some adjudications useful with buggy engines
8367         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8368         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8369         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8370         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8371         int k, drop, count = 0; static int bare = 1;
8372         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8373         Boolean canAdjudicate = !appData.icsActive;
8374
8375         // most tests only when we understand the game, i.e. legality-checking on
8376             if( appData.testLegality )
8377             {   /* [HGM] Some more adjudications for obstinate engines */
8378                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8379                 static int moveCount = 6;
8380                 ChessMove result;
8381                 char *reason = NULL;
8382
8383                 /* Count what is on board. */
8384                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8385
8386                 /* Some material-based adjudications that have to be made before stalemate test */
8387                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8388                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8389                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8390                      if(canAdjudicate && appData.checkMates) {
8391                          if(engineOpponent)
8392                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8393                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8394                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8395                          return 1;
8396                      }
8397                 }
8398
8399                 /* Bare King in Shatranj (loses) or Losers (wins) */
8400                 if( nrW == 1 || nrB == 1) {
8401                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8402                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8403                      if(canAdjudicate && appData.checkMates) {
8404                          if(engineOpponent)
8405                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8406                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8407                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8408                          return 1;
8409                      }
8410                   } else
8411                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8412                   {    /* bare King */
8413                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8414                         if(canAdjudicate && appData.checkMates) {
8415                             /* but only adjudicate if adjudication enabled */
8416                             if(engineOpponent)
8417                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8418                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8419                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8420                             return 1;
8421                         }
8422                   }
8423                 } else bare = 1;
8424
8425
8426             // don't wait for engine to announce game end if we can judge ourselves
8427             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8428               case MT_CHECK:
8429                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8430                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8431                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8432                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8433                             checkCnt++;
8434                         if(checkCnt >= 2) {
8435                             reason = "Xboard adjudication: 3rd check";
8436                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8437                             break;
8438                         }
8439                     }
8440                 }
8441               case MT_NONE:
8442               default:
8443                 break;
8444               case MT_STEALMATE:
8445               case MT_STALEMATE:
8446               case MT_STAINMATE:
8447                 reason = "Xboard adjudication: Stalemate";
8448                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8449                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8450                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8451                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8452                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8453                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8454                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8455                                                                         EP_CHECKMATE : EP_WINS);
8456                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8457                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8458                 }
8459                 break;
8460               case MT_CHECKMATE:
8461                 reason = "Xboard adjudication: Checkmate";
8462                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8463                 if(gameInfo.variant == VariantShogi) {
8464                     if(forwardMostMove > backwardMostMove
8465                        && moveList[forwardMostMove-1][1] == '@'
8466                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8467                         reason = "XBoard adjudication: pawn-drop mate";
8468                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8469                     }
8470                 }
8471                 break;
8472             }
8473
8474                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8475                     case EP_STALEMATE:
8476                         result = GameIsDrawn; break;
8477                     case EP_CHECKMATE:
8478                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8479                     case EP_WINS:
8480                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8481                     default:
8482                         result = EndOfFile;
8483                 }
8484                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8485                     if(engineOpponent)
8486                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8487                     GameEnds( result, reason, GE_XBOARD );
8488                     return 1;
8489                 }
8490
8491                 /* Next absolutely insufficient mating material. */
8492                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8493                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8494                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8495
8496                      /* always flag draws, for judging claims */
8497                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8498
8499                      if(canAdjudicate && appData.materialDraws) {
8500                          /* but only adjudicate them if adjudication enabled */
8501                          if(engineOpponent) {
8502                            SendToProgram("force\n", engineOpponent); // suppress reply
8503                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8504                          }
8505                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8506                          return 1;
8507                      }
8508                 }
8509
8510                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8511                 if(gameInfo.variant == VariantXiangqi ?
8512                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8513                  : nrW + nrB == 4 &&
8514                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8515                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8516                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8517                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8518                    ) ) {
8519                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8520                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8521                           if(engineOpponent) {
8522                             SendToProgram("force\n", engineOpponent); // suppress reply
8523                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8524                           }
8525                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8526                           return 1;
8527                      }
8528                 } else moveCount = 6;
8529
8530                 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8531                   (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8532                     int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8533                     count = forwardMostMove;
8534                     while(count >= backwardMostMove) {
8535                         int np = nr[WhitePawn] + nr[BlackPawn];
8536                         if(wom) mine = nrW, his = nrB, c = BlackPawn;
8537                         else    mine = nrB, his = nrW, c = WhitePawn;
8538                         if(mine > 1 && np) { count++; break; }
8539                         if(mine > 1) maxcnt = 64; else
8540                         maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8541                                                             nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8542                         while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8543                         if(count == backwardMostMove) break;
8544                         if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8545                         Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8546                     }
8547                     if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8548                         boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8549                         if(canAdjudicate && appData.ruleMoves >= 0) {
8550                             GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8551                             return 1;
8552                         }
8553                     }
8554                 }
8555             }
8556
8557         // Repetition draws and 50-move rule can be applied independently of legality testing
8558
8559                 /* Check for rep-draws */
8560                 count = 0;
8561                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8562                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8563                 for(k = forwardMostMove-2;
8564                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8565                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8566                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8567                     k-=2)
8568                 {   int rights=0;
8569                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8570                         /* compare castling rights */
8571                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8572                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8573                                 rights++; /* King lost rights, while rook still had them */
8574                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8575                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8576                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8577                                    rights++; /* but at least one rook lost them */
8578                         }
8579                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8580                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8581                                 rights++;
8582                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8583                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8584                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8585                                    rights++;
8586                         }
8587                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8588                             && appData.drawRepeats > 1) {
8589                              /* adjudicate after user-specified nr of repeats */
8590                              int result = GameIsDrawn;
8591                              char *details = "XBoard adjudication: repetition draw";
8592                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8593                                 // [HGM] xiangqi: check for forbidden perpetuals
8594                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8595                                 for(m=forwardMostMove; m>k; m-=2) {
8596                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8597                                         ourPerpetual = 0; // the current mover did not always check
8598                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8599                                         hisPerpetual = 0; // the opponent did not always check
8600                                 }
8601                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8602                                                                         ourPerpetual, hisPerpetual);
8603                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8604                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8605                                     details = "Xboard adjudication: perpetual checking";
8606                                 } else
8607                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8608                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8609                                 } else
8610                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8611                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8612                                         result = BlackWins;
8613                                         details = "Xboard adjudication: repetition";
8614                                     }
8615                                 } else // it must be XQ
8616                                 // Now check for perpetual chases
8617                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8618                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8619                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8620                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8621                                         static char resdet[MSG_SIZ];
8622                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8623                                         details = resdet;
8624                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8625                                     } else
8626                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8627                                         break; // Abort repetition-checking loop.
8628                                 }
8629                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8630                              }
8631                              if(engineOpponent) {
8632                                SendToProgram("force\n", engineOpponent); // suppress reply
8633                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8634                              }
8635                              GameEnds( result, details, GE_XBOARD );
8636                              return 1;
8637                         }
8638                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8639                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8640                     }
8641                 }
8642
8643                 /* Now we test for 50-move draws. Determine ply count */
8644                 count = forwardMostMove;
8645                 /* look for last irreversble move */
8646                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8647                     count--;
8648                 /* if we hit starting position, add initial plies */
8649                 if( count == backwardMostMove )
8650                     count -= initialRulePlies;
8651                 count = forwardMostMove - count;
8652                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8653                         // adjust reversible move counter for checks in Xiangqi
8654                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8655                         if(i < backwardMostMove) i = backwardMostMove;
8656                         while(i <= forwardMostMove) {
8657                                 lastCheck = inCheck; // check evasion does not count
8658                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8659                                 if(inCheck || lastCheck) count--; // check does not count
8660                                 i++;
8661                         }
8662                 }
8663                 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8664                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8665                          /* this is used to judge if draw claims are legal */
8666                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8667                          if(engineOpponent) {
8668                            SendToProgram("force\n", engineOpponent); // suppress reply
8669                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8670                          }
8671                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8672                          return 1;
8673                 }
8674
8675                 /* if draw offer is pending, treat it as a draw claim
8676                  * when draw condition present, to allow engines a way to
8677                  * claim draws before making their move to avoid a race
8678                  * condition occurring after their move
8679                  */
8680                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8681                          char *p = NULL;
8682                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8683                              p = "Draw claim: 50-move rule";
8684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8685                              p = "Draw claim: 3-fold repetition";
8686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8687                              p = "Draw claim: insufficient mating material";
8688                          if( p != NULL && canAdjudicate) {
8689                              if(engineOpponent) {
8690                                SendToProgram("force\n", engineOpponent); // suppress reply
8691                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8692                              }
8693                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8694                              return 1;
8695                          }
8696                 }
8697
8698                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8699                     if(engineOpponent) {
8700                       SendToProgram("force\n", engineOpponent); // suppress reply
8701                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8702                     }
8703                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8704                     return 1;
8705                 }
8706         return 0;
8707 }
8708
8709 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8710 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8711 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8712
8713 static int
8714 BitbaseProbe ()
8715 {
8716     int pieces[10], squares[10], cnt=0, r, f, res;
8717     static int loaded;
8718     static PPROBE_EGBB probeBB;
8719     if(!appData.testLegality) return 10;
8720     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8721     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8722     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8723     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8724         ChessSquare piece = boards[forwardMostMove][r][f];
8725         int black = (piece >= BlackPawn);
8726         int type = piece - black*BlackPawn;
8727         if(piece == EmptySquare) continue;
8728         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8729         if(type == WhiteKing) type = WhiteQueen + 1;
8730         type = egbbCode[type];
8731         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8732         pieces[cnt] = type + black*6;
8733         if(++cnt > 5) return 11;
8734     }
8735     pieces[cnt] = squares[cnt] = 0;
8736     // probe EGBB
8737     if(loaded == 2) return 13; // loading failed before
8738     if(loaded == 0) {
8739         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8740         HMODULE lib;
8741         PLOAD_EGBB loadBB;
8742         loaded = 2; // prepare for failure
8743         if(!path) return 13; // no egbb installed
8744         strncpy(buf, path + 8, MSG_SIZ);
8745         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8746         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8747         lib = LoadLibrary(buf);
8748         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8749         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8750         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8751         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8752         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8753         loaded = 1; // success!
8754     }
8755     res = probeBB(forwardMostMove & 1, pieces, squares);
8756     return res > 0 ? 1 : res < 0 ? -1 : 0;
8757 }
8758
8759 char *
8760 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8761 {   // [HGM] book: this routine intercepts moves to simulate book replies
8762     char *bookHit = NULL;
8763
8764     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8765         char buf[MSG_SIZ];
8766         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8767         SendToProgram(buf, cps);
8768     }
8769     //first determine if the incoming move brings opponent into his book
8770     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8771         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8772     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8773     if(bookHit != NULL && !cps->bookSuspend) {
8774         // make sure opponent is not going to reply after receiving move to book position
8775         SendToProgram("force\n", cps);
8776         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8777     }
8778     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8779     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8780     // now arrange restart after book miss
8781     if(bookHit) {
8782         // after a book hit we never send 'go', and the code after the call to this routine
8783         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8784         char buf[MSG_SIZ], *move = bookHit;
8785         if(cps->useSAN) {
8786             int fromX, fromY, toX, toY;
8787             char promoChar;
8788             ChessMove moveType;
8789             move = buf + 30;
8790             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8791                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8792                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8793                                     PosFlags(forwardMostMove),
8794                                     fromY, fromX, toY, toX, promoChar, move);
8795             } else {
8796                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8797                 bookHit = NULL;
8798             }
8799         }
8800         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8801         SendToProgram(buf, cps);
8802         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8803     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8804         SendToProgram("go\n", cps);
8805         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8806     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8807         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8808             SendToProgram("go\n", cps);
8809         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8810     }
8811     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8812 }
8813
8814 int
8815 LoadError (char *errmess, ChessProgramState *cps)
8816 {   // unloads engine and switches back to -ncp mode if it was first
8817     if(cps->initDone) return FALSE;
8818     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8819     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8820     cps->pr = NoProc;
8821     if(cps == &first) {
8822         appData.noChessProgram = TRUE;
8823         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8824         gameMode = BeginningOfGame; ModeHighlight();
8825         SetNCPMode();
8826     }
8827     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8828     DisplayMessage("", ""); // erase waiting message
8829     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8830     return TRUE;
8831 }
8832
8833 char *savedMessage;
8834 ChessProgramState *savedState;
8835 void
8836 DeferredBookMove (void)
8837 {
8838         if(savedState->lastPing != savedState->lastPong)
8839                     ScheduleDelayedEvent(DeferredBookMove, 10);
8840         else
8841         HandleMachineMove(savedMessage, savedState);
8842 }
8843
8844 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8845 static ChessProgramState *stalledEngine;
8846 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8847
8848 void
8849 HandleMachineMove (char *message, ChessProgramState *cps)
8850 {
8851     static char firstLeg[20], legs;
8852     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8853     char realname[MSG_SIZ];
8854     int fromX, fromY, toX, toY;
8855     ChessMove moveType;
8856     char promoChar, roar;
8857     char *p, *pv=buf1;
8858     int oldError;
8859     char *bookHit;
8860
8861     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8862         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8863         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8864             DisplayError(_("Invalid pairing from pairing engine"), 0);
8865             return;
8866         }
8867         pairingReceived = 1;
8868         NextMatchGame();
8869         return; // Skim the pairing messages here.
8870     }
8871
8872     oldError = cps->userError; cps->userError = 0;
8873
8874 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8875     /*
8876      * Kludge to ignore BEL characters
8877      */
8878     while (*message == '\007') message++;
8879
8880     /*
8881      * [HGM] engine debug message: ignore lines starting with '#' character
8882      */
8883     if(cps->debug && *message == '#') return;
8884
8885     /*
8886      * Look for book output
8887      */
8888     if (cps == &first && bookRequested) {
8889         if (message[0] == '\t' || message[0] == ' ') {
8890             /* Part of the book output is here; append it */
8891             strcat(bookOutput, message);
8892             strcat(bookOutput, "  \n");
8893             return;
8894         } else if (bookOutput[0] != NULLCHAR) {
8895             /* All of book output has arrived; display it */
8896             char *p = bookOutput;
8897             while (*p != NULLCHAR) {
8898                 if (*p == '\t') *p = ' ';
8899                 p++;
8900             }
8901             DisplayInformation(bookOutput);
8902             bookRequested = FALSE;
8903             /* Fall through to parse the current output */
8904         }
8905     }
8906
8907     /*
8908      * Look for machine move.
8909      */
8910     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8911         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8912     {
8913         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8914             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8915             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8916             stalledEngine = cps;
8917             if(appData.ponderNextMove) { // bring opponent out of ponder
8918                 if(gameMode == TwoMachinesPlay) {
8919                     if(cps->other->pause)
8920                         PauseEngine(cps->other);
8921                     else
8922                         SendToProgram("easy\n", cps->other);
8923                 }
8924             }
8925             StopClocks();
8926             return;
8927         }
8928
8929       if(cps->usePing) {
8930
8931         /* This method is only useful on engines that support ping */
8932         if(abortEngineThink) {
8933             if (appData.debugMode) {
8934                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8935             }
8936             SendToProgram("undo\n", cps);
8937             return;
8938         }
8939
8940         if (cps->lastPing != cps->lastPong) {
8941             /* Extra move from before last new; ignore */
8942             if (appData.debugMode) {
8943                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8944             }
8945           return;
8946         }
8947
8948       } else {
8949
8950         int machineWhite = FALSE;
8951
8952         switch (gameMode) {
8953           case BeginningOfGame:
8954             /* Extra move from before last reset; ignore */
8955             if (appData.debugMode) {
8956                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8957             }
8958             return;
8959
8960           case EndOfGame:
8961           case IcsIdle:
8962           default:
8963             /* Extra move after we tried to stop.  The mode test is
8964                not a reliable way of detecting this problem, but it's
8965                the best we can do on engines that don't support ping.
8966             */
8967             if (appData.debugMode) {
8968                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8969                         cps->which, gameMode);
8970             }
8971             SendToProgram("undo\n", cps);
8972             return;
8973
8974           case MachinePlaysWhite:
8975           case IcsPlayingWhite:
8976             machineWhite = TRUE;
8977             break;
8978
8979           case MachinePlaysBlack:
8980           case IcsPlayingBlack:
8981             machineWhite = FALSE;
8982             break;
8983
8984           case TwoMachinesPlay:
8985             machineWhite = (cps->twoMachinesColor[0] == 'w');
8986             break;
8987         }
8988         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8989             if (appData.debugMode) {
8990                 fprintf(debugFP,
8991                         "Ignoring move out of turn by %s, gameMode %d"
8992                         ", forwardMost %d\n",
8993                         cps->which, gameMode, forwardMostMove);
8994             }
8995             return;
8996         }
8997       }
8998
8999         if(cps->alphaRank) AlphaRank(machineMove, 4);
9000
9001         // [HGM] lion: (some very limited) support for Alien protocol
9002         killX = killY = kill2X = kill2Y = -1;
9003         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
9004             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
9005             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
9006             return;
9007         }
9008         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
9009             char *q = strchr(p+1, ',');            // second comma?
9010             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
9011             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
9012             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9013         }
9014         if(firstLeg[0]) { // there was a previous leg;
9015             // only support case where same piece makes two step
9016             char buf[20], *p = machineMove+1, *q = buf+1, f;
9017             safeStrCpy(buf, machineMove, 20);
9018             while(isdigit(*q)) q++; // find start of to-square
9019             safeStrCpy(machineMove, firstLeg, 20);
9020             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9021             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
9022             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
9023             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9024             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9025             firstLeg[0] = NULLCHAR; legs = 0;
9026         }
9027
9028         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9029                               &fromX, &fromY, &toX, &toY, &promoChar)) {
9030             /* Machine move could not be parsed; ignore it. */
9031           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9032                     machineMove, _(cps->which));
9033             DisplayMoveError(buf1);
9034             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9035                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9036             if (gameMode == TwoMachinesPlay) {
9037               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9038                        buf1, GE_XBOARD);
9039             }
9040             return;
9041         }
9042
9043         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9044         /* So we have to redo legality test with true e.p. status here,  */
9045         /* to make sure an illegal e.p. capture does not slip through,   */
9046         /* to cause a forfeit on a justified illegal-move complaint      */
9047         /* of the opponent.                                              */
9048         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9049            ChessMove moveType;
9050            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9051                              fromY, fromX, toY, toX, promoChar);
9052             if(moveType == IllegalMove) {
9053               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9054                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9055                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9056                            buf1, GE_XBOARD);
9057                 return;
9058            } else if(!appData.fischerCastling)
9059            /* [HGM] Kludge to handle engines that send FRC-style castling
9060               when they shouldn't (like TSCP-Gothic) */
9061            switch(moveType) {
9062              case WhiteASideCastleFR:
9063              case BlackASideCastleFR:
9064                toX+=2;
9065                currentMoveString[2]++;
9066                break;
9067              case WhiteHSideCastleFR:
9068              case BlackHSideCastleFR:
9069                toX--;
9070                currentMoveString[2]--;
9071                break;
9072              default: ; // nothing to do, but suppresses warning of pedantic compilers
9073            }
9074         }
9075         hintRequested = FALSE;
9076         lastHint[0] = NULLCHAR;
9077         bookRequested = FALSE;
9078         /* Program may be pondering now */
9079         cps->maybeThinking = TRUE;
9080         if (cps->sendTime == 2) cps->sendTime = 1;
9081         if (cps->offeredDraw) cps->offeredDraw--;
9082
9083         /* [AS] Save move info*/
9084         pvInfoList[ forwardMostMove ].score = programStats.score;
9085         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9086         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9087
9088         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9089
9090         /* Test suites abort the 'game' after one move */
9091         if(*appData.finger) {
9092            static FILE *f;
9093            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9094            if(!f) f = fopen(appData.finger, "w");
9095            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9096            else { DisplayFatalError("Bad output file", errno, 0); return; }
9097            free(fen);
9098            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9099         }
9100         if(appData.epd) {
9101            if(solvingTime >= 0) {
9102               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9103               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9104            } else {
9105               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9106               if(solvingTime == -2) second.matchWins++;
9107            }
9108            OutputKibitz(2, buf1);
9109            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9110         }
9111
9112         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9113         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9114             int count = 0;
9115
9116             while( count < adjudicateLossPlies ) {
9117                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9118
9119                 if( count & 1 ) {
9120                     score = -score; /* Flip score for winning side */
9121                 }
9122
9123                 if( score > appData.adjudicateLossThreshold ) {
9124                     break;
9125                 }
9126
9127                 count++;
9128             }
9129
9130             if( count >= adjudicateLossPlies ) {
9131                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9132
9133                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9134                     "Xboard adjudication",
9135                     GE_XBOARD );
9136
9137                 return;
9138             }
9139         }
9140
9141         if(Adjudicate(cps)) {
9142             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9143             return; // [HGM] adjudicate: for all automatic game ends
9144         }
9145
9146 #if ZIPPY
9147         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9148             first.initDone) {
9149           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9150                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9151                 SendToICS("draw ");
9152                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9153           }
9154           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9155           ics_user_moved = 1;
9156           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9157                 char buf[3*MSG_SIZ];
9158
9159                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9160                         programStats.score / 100.,
9161                         programStats.depth,
9162                         programStats.time / 100.,
9163                         (unsigned int)programStats.nodes,
9164                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9165                         programStats.movelist);
9166                 SendToICS(buf);
9167           }
9168         }
9169 #endif
9170
9171         /* [AS] Clear stats for next move */
9172         ClearProgramStats();
9173         thinkOutput[0] = NULLCHAR;
9174         hiddenThinkOutputState = 0;
9175
9176         bookHit = NULL;
9177         if (gameMode == TwoMachinesPlay) {
9178             /* [HGM] relaying draw offers moved to after reception of move */
9179             /* and interpreting offer as claim if it brings draw condition */
9180             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9181                 SendToProgram("draw\n", cps->other);
9182             }
9183             if (cps->other->sendTime) {
9184                 SendTimeRemaining(cps->other,
9185                                   cps->other->twoMachinesColor[0] == 'w');
9186             }
9187             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9188             if (firstMove && !bookHit) {
9189                 firstMove = FALSE;
9190                 if (cps->other->useColors) {
9191                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9192                 }
9193                 SendToProgram("go\n", cps->other);
9194             }
9195             cps->other->maybeThinking = TRUE;
9196         }
9197
9198         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9199
9200         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9201
9202         if (!pausing && appData.ringBellAfterMoves) {
9203             if(!roar) RingBell();
9204         }
9205
9206         /*
9207          * Reenable menu items that were disabled while
9208          * machine was thinking
9209          */
9210         if (gameMode != TwoMachinesPlay)
9211             SetUserThinkingEnables();
9212
9213         // [HGM] book: after book hit opponent has received move and is now in force mode
9214         // force the book reply into it, and then fake that it outputted this move by jumping
9215         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9216         if(bookHit) {
9217                 static char bookMove[MSG_SIZ]; // a bit generous?
9218
9219                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9220                 strcat(bookMove, bookHit);
9221                 message = bookMove;
9222                 cps = cps->other;
9223                 programStats.nodes = programStats.depth = programStats.time =
9224                 programStats.score = programStats.got_only_move = 0;
9225                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9226
9227                 if(cps->lastPing != cps->lastPong) {
9228                     savedMessage = message; // args for deferred call
9229                     savedState = cps;
9230                     ScheduleDelayedEvent(DeferredBookMove, 10);
9231                     return;
9232                 }
9233                 goto FakeBookMove;
9234         }
9235
9236         return;
9237     }
9238
9239     /* Set special modes for chess engines.  Later something general
9240      *  could be added here; for now there is just one kludge feature,
9241      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9242      *  when "xboard" is given as an interactive command.
9243      */
9244     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9245         cps->useSigint = FALSE;
9246         cps->useSigterm = FALSE;
9247     }
9248     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9249       ParseFeatures(message+8, cps); if(tryNr && tryNr < 3) tryNr = 3;
9250       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9251     }
9252
9253     if (!strncmp(message, "setup ", 6) && 
9254         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9255           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9256                                         ) { // [HGM] allow first engine to define opening position
9257       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9258       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9259       *buf = NULLCHAR;
9260       if(sscanf(message, "setup (%s", buf) == 1) {
9261         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9262         ASSIGN(appData.pieceToCharTable, buf);
9263       }
9264       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9265       if(dummy >= 3) {
9266         while(message[s] && message[s++] != ' ');
9267         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9268            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9269 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9270             if(hand > h) handSize = hand; else handSize = h;
9271             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9272             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9273           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9274           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9275           startedFromSetupPosition = FALSE;
9276         }
9277       }
9278       if(startedFromSetupPosition) return;
9279       ParseFEN(boards[0], &dummy, message+s, FALSE);
9280       DrawPosition(TRUE, boards[0]);
9281       CopyBoard(initialPosition, boards[0]);
9282       startedFromSetupPosition = TRUE;
9283       return;
9284     }
9285     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9286       ChessSquare piece = WhitePawn;
9287       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9288       if(*p == '+') promoted++, ID = *++p;
9289       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9290       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9291       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9292       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9293       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9294       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9295       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9296       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9297                                                && gameInfo.variant != VariantGreat
9298                                                && gameInfo.variant != VariantFairy    ) return;
9299       if(piece < EmptySquare) {
9300         pieceDefs = TRUE;
9301         ASSIGN(pieceDesc[piece], buf1);
9302         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9303       }
9304       return;
9305     }
9306     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9307       if(deferChoice) {
9308         LeftClick(Press, 0, 0); // finish the click that was interrupted
9309       } else if(promoSweep != EmptySquare) {
9310         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9311         if(strlen(promoRestrict) > 1) Sweep(0);
9312       }
9313       return;
9314     }
9315     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9316      * want this, I was asked to put it in, and obliged.
9317      */
9318     if (!strncmp(message, "setboard ", 9)) {
9319         Board initial_position;
9320
9321         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9322
9323         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9324             DisplayError(_("Bad FEN received from engine"), 0);
9325             return ;
9326         } else {
9327            Reset(TRUE, FALSE);
9328            CopyBoard(boards[0], initial_position);
9329            initialRulePlies = FENrulePlies;
9330            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9331            else gameMode = MachinePlaysBlack;
9332            DrawPosition(FALSE, boards[currentMove]);
9333         }
9334         return;
9335     }
9336
9337     /*
9338      * Look for communication commands
9339      */
9340     if (!strncmp(message, "telluser ", 9)) {
9341         if(message[9] == '\\' && message[10] == '\\')
9342             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9343         PlayTellSound();
9344         DisplayNote(message + 9);
9345         return;
9346     }
9347     if (!strncmp(message, "tellusererror ", 14)) {
9348         cps->userError = 1;
9349         if(message[14] == '\\' && message[15] == '\\')
9350             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9351         PlayTellSound();
9352         DisplayError(message + 14, 0);
9353         return;
9354     }
9355     if (!strncmp(message, "tellopponent ", 13)) {
9356       if (appData.icsActive) {
9357         if (loggedOn) {
9358           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9359           SendToICS(buf1);
9360         }
9361       } else {
9362         DisplayNote(message + 13);
9363       }
9364       return;
9365     }
9366     if (!strncmp(message, "tellothers ", 11)) {
9367       if (appData.icsActive) {
9368         if (loggedOn) {
9369           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9370           SendToICS(buf1);
9371         }
9372       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9373       return;
9374     }
9375     if (!strncmp(message, "tellall ", 8)) {
9376       if (appData.icsActive) {
9377         if (loggedOn) {
9378           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9379           SendToICS(buf1);
9380         }
9381       } else {
9382         DisplayNote(message + 8);
9383       }
9384       return;
9385     }
9386     if (strncmp(message, "warning", 7) == 0) {
9387         /* Undocumented feature, use tellusererror in new code */
9388         DisplayError(message, 0);
9389         return;
9390     }
9391     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9392         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9393         strcat(realname, " query");
9394         AskQuestion(realname, buf2, buf1, cps->pr);
9395         return;
9396     }
9397     /* Commands from the engine directly to ICS.  We don't allow these to be
9398      *  sent until we are logged on. Crafty kibitzes have been known to
9399      *  interfere with the login process.
9400      */
9401     if (loggedOn) {
9402         if (!strncmp(message, "tellics ", 8)) {
9403             SendToICS(message + 8);
9404             SendToICS("\n");
9405             return;
9406         }
9407         if (!strncmp(message, "tellicsnoalias ", 15)) {
9408             SendToICS(ics_prefix);
9409             SendToICS(message + 15);
9410             SendToICS("\n");
9411             return;
9412         }
9413         /* The following are for backward compatibility only */
9414         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9415             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9416             SendToICS(ics_prefix);
9417             SendToICS(message);
9418             SendToICS("\n");
9419             return;
9420         }
9421     }
9422     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9423         if(initPing == cps->lastPong) {
9424             if(gameInfo.variant == VariantUnknown) {
9425                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9426                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9427                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9428             }
9429             initPing = -1;
9430         }
9431         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9432             abortEngineThink = FALSE;
9433             DisplayMessage("", "");
9434             ThawUI();
9435         }
9436         return;
9437     }
9438     if(!strncmp(message, "highlight ", 10)) {
9439         if(appData.testLegality && !*engineVariant && appData.markers) return;
9440         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9441         return;
9442     }
9443     if(!strncmp(message, "click ", 6)) {
9444         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9445         if(appData.testLegality || !appData.oneClick) return;
9446         sscanf(message+6, "%c%d%c", &f, &y, &c);
9447         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9448         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9449         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9450         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9451         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9452         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9453             LeftClick(Release, lastLeftX, lastLeftY);
9454         controlKey  = (c == ',');
9455         LeftClick(Press, x, y);
9456         LeftClick(Release, x, y);
9457         first.highlight = f;
9458         return;
9459     }
9460     if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9461         int nr = (cps == &second);
9462         appData.isUCI[nr] = isUCI = 1;
9463         ReplaceEngine(cps, nr); // retry install as UCI
9464         return;
9465     }
9466     /*
9467      * If the move is illegal, cancel it and redraw the board.
9468      * Also deal with other error cases.  Matching is rather loose
9469      * here to accommodate engines written before the spec.
9470      */
9471     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9472         strncmp(message, "Error", 5) == 0) {
9473         if (StrStr(message, "name") ||
9474             StrStr(message, "rating") || StrStr(message, "?") ||
9475             StrStr(message, "result") || StrStr(message, "board") ||
9476             StrStr(message, "bk") || StrStr(message, "computer") ||
9477             StrStr(message, "variant") || StrStr(message, "hint") ||
9478             StrStr(message, "random") || StrStr(message, "depth") ||
9479             StrStr(message, "accepted")) {
9480             return;
9481         }
9482         if (StrStr(message, "protover")) {
9483           /* Program is responding to input, so it's apparently done
9484              initializing, and this error message indicates it is
9485              protocol version 1.  So we don't need to wait any longer
9486              for it to initialize and send feature commands. */
9487           FeatureDone(cps, 1);
9488           cps->protocolVersion = 1;
9489           return;
9490         }
9491         cps->maybeThinking = FALSE;
9492
9493         if (StrStr(message, "draw")) {
9494             /* Program doesn't have "draw" command */
9495             cps->sendDrawOffers = 0;
9496             return;
9497         }
9498         if (cps->sendTime != 1 &&
9499             (StrStr(message, "time") || StrStr(message, "otim"))) {
9500           /* Program apparently doesn't have "time" or "otim" command */
9501           cps->sendTime = 0;
9502           return;
9503         }
9504         if (StrStr(message, "analyze")) {
9505             cps->analysisSupport = FALSE;
9506             cps->analyzing = FALSE;
9507 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9508             EditGameEvent(); // [HGM] try to preserve loaded game
9509             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9510             DisplayError(buf2, 0);
9511             return;
9512         }
9513         if (StrStr(message, "(no matching move)st")) {
9514           /* Special kludge for GNU Chess 4 only */
9515           cps->stKludge = TRUE;
9516           SendTimeControl(cps, movesPerSession, timeControl,
9517                           timeIncrement, appData.searchDepth,
9518                           searchTime);
9519           return;
9520         }
9521         if (StrStr(message, "(no matching move)sd")) {
9522           /* Special kludge for GNU Chess 4 only */
9523           cps->sdKludge = TRUE;
9524           SendTimeControl(cps, movesPerSession, timeControl,
9525                           timeIncrement, appData.searchDepth,
9526                           searchTime);
9527           return;
9528         }
9529         if (!StrStr(message, "llegal")) {
9530             return;
9531         }
9532         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9533             gameMode == IcsIdle) return;
9534         if (forwardMostMove <= backwardMostMove) return;
9535         if (pausing) PauseEvent();
9536       if(appData.forceIllegal) {
9537             // [HGM] illegal: machine refused move; force position after move into it
9538           SendToProgram("force\n", cps);
9539           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9540                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9541                 // when black is to move, while there might be nothing on a2 or black
9542                 // might already have the move. So send the board as if white has the move.
9543                 // But first we must change the stm of the engine, as it refused the last move
9544                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9545                 if(WhiteOnMove(forwardMostMove)) {
9546                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9547                     SendBoard(cps, forwardMostMove); // kludgeless board
9548                 } else {
9549                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9550                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9551                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9552                 }
9553           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9554             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9555                  gameMode == TwoMachinesPlay)
9556               SendToProgram("go\n", cps);
9557             return;
9558       } else
9559         if (gameMode == PlayFromGameFile) {
9560             /* Stop reading this game file */
9561             gameMode = EditGame;
9562             ModeHighlight();
9563         }
9564         /* [HGM] illegal-move claim should forfeit game when Xboard */
9565         /* only passes fully legal moves                            */
9566         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9567             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9568                                 "False illegal-move claim", GE_XBOARD );
9569             return; // do not take back move we tested as valid
9570         }
9571         currentMove = forwardMostMove-1;
9572         DisplayMove(currentMove-1); /* before DisplayMoveError */
9573         SwitchClocks(forwardMostMove-1); // [HGM] race
9574         DisplayBothClocks();
9575         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9576                 parseList[currentMove], _(cps->which));
9577         DisplayMoveError(buf1);
9578         DrawPosition(FALSE, boards[currentMove]);
9579
9580         SetUserThinkingEnables();
9581         return;
9582     }
9583     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9584         /* Program has a broken "time" command that
9585            outputs a string not ending in newline.
9586            Don't use it. */
9587         cps->sendTime = 0;
9588     }
9589     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9590         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9591             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9592     }
9593
9594     /*
9595      * If chess program startup fails, exit with an error message.
9596      * Attempts to recover here are futile. [HGM] Well, we try anyway
9597      */
9598     if ((StrStr(message, "unknown host") != NULL)
9599         || (StrStr(message, "No remote directory") != NULL)
9600         || (StrStr(message, "not found") != NULL)
9601         || (StrStr(message, "No such file") != NULL)
9602         || (StrStr(message, "can't alloc") != NULL)
9603         || (StrStr(message, "Permission denied") != NULL)) {
9604
9605         cps->maybeThinking = FALSE;
9606         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9607                 _(cps->which), cps->program, cps->host, message);
9608         RemoveInputSource(cps->isr);
9609         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9610             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9611             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9612         }
9613         return;
9614     }
9615
9616     /*
9617      * Look for hint output
9618      */
9619     if (sscanf(message, "Hint: %s", buf1) == 1) {
9620         if (cps == &first && hintRequested) {
9621             hintRequested = FALSE;
9622             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9623                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9624                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9625                                     PosFlags(forwardMostMove),
9626                                     fromY, fromX, toY, toX, promoChar, buf1);
9627                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9628                 DisplayInformation(buf2);
9629             } else {
9630                 /* Hint move could not be parsed!? */
9631               snprintf(buf2, sizeof(buf2),
9632                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9633                         buf1, _(cps->which));
9634                 DisplayError(buf2, 0);
9635             }
9636         } else {
9637           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9638         }
9639         return;
9640     }
9641
9642     /*
9643      * Ignore other messages if game is not in progress
9644      */
9645     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9646         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9647
9648     /*
9649      * look for win, lose, draw, or draw offer
9650      */
9651     if (strncmp(message, "1-0", 3) == 0) {
9652         char *p, *q, *r = "";
9653         p = strchr(message, '{');
9654         if (p) {
9655             q = strchr(p, '}');
9656             if (q) {
9657                 *q = NULLCHAR;
9658                 r = p + 1;
9659             }
9660         }
9661         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9662         return;
9663     } else if (strncmp(message, "0-1", 3) == 0) {
9664         char *p, *q, *r = "";
9665         p = strchr(message, '{');
9666         if (p) {
9667             q = strchr(p, '}');
9668             if (q) {
9669                 *q = NULLCHAR;
9670                 r = p + 1;
9671             }
9672         }
9673         /* Kludge for Arasan 4.1 bug */
9674         if (strcmp(r, "Black resigns") == 0) {
9675             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9676             return;
9677         }
9678         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9679         return;
9680     } else if (strncmp(message, "1/2", 3) == 0) {
9681         char *p, *q, *r = "";
9682         p = strchr(message, '{');
9683         if (p) {
9684             q = strchr(p, '}');
9685             if (q) {
9686                 *q = NULLCHAR;
9687                 r = p + 1;
9688             }
9689         }
9690
9691         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9692         return;
9693
9694     } else if (strncmp(message, "White resign", 12) == 0) {
9695         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9696         return;
9697     } else if (strncmp(message, "Black resign", 12) == 0) {
9698         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9699         return;
9700     } else if (strncmp(message, "White matches", 13) == 0 ||
9701                strncmp(message, "Black matches", 13) == 0   ) {
9702         /* [HGM] ignore GNUShogi noises */
9703         return;
9704     } else if (strncmp(message, "White", 5) == 0 &&
9705                message[5] != '(' &&
9706                StrStr(message, "Black") == NULL) {
9707         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9708         return;
9709     } else if (strncmp(message, "Black", 5) == 0 &&
9710                message[5] != '(') {
9711         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9712         return;
9713     } else if (strcmp(message, "resign") == 0 ||
9714                strcmp(message, "computer resigns") == 0) {
9715         switch (gameMode) {
9716           case MachinePlaysBlack:
9717           case IcsPlayingBlack:
9718             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9719             break;
9720           case MachinePlaysWhite:
9721           case IcsPlayingWhite:
9722             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9723             break;
9724           case TwoMachinesPlay:
9725             if (cps->twoMachinesColor[0] == 'w')
9726               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9727             else
9728               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9729             break;
9730           default:
9731             /* can't happen */
9732             break;
9733         }
9734         return;
9735     } else if (strncmp(message, "opponent mates", 14) == 0) {
9736         switch (gameMode) {
9737           case MachinePlaysBlack:
9738           case IcsPlayingBlack:
9739             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9740             break;
9741           case MachinePlaysWhite:
9742           case IcsPlayingWhite:
9743             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9744             break;
9745           case TwoMachinesPlay:
9746             if (cps->twoMachinesColor[0] == 'w')
9747               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9748             else
9749               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9750             break;
9751           default:
9752             /* can't happen */
9753             break;
9754         }
9755         return;
9756     } else if (strncmp(message, "computer mates", 14) == 0) {
9757         switch (gameMode) {
9758           case MachinePlaysBlack:
9759           case IcsPlayingBlack:
9760             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9761             break;
9762           case MachinePlaysWhite:
9763           case IcsPlayingWhite:
9764             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9765             break;
9766           case TwoMachinesPlay:
9767             if (cps->twoMachinesColor[0] == 'w')
9768               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9769             else
9770               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9771             break;
9772           default:
9773             /* can't happen */
9774             break;
9775         }
9776         return;
9777     } else if (strncmp(message, "checkmate", 9) == 0) {
9778         if (WhiteOnMove(forwardMostMove)) {
9779             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9780         } else {
9781             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9782         }
9783         return;
9784     } else if (strstr(message, "Draw") != NULL ||
9785                strstr(message, "game is a draw") != NULL) {
9786         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9787         return;
9788     } else if (strstr(message, "offer") != NULL &&
9789                strstr(message, "draw") != NULL) {
9790 #if ZIPPY
9791         if (appData.zippyPlay && first.initDone) {
9792             /* Relay offer to ICS */
9793             SendToICS(ics_prefix);
9794             SendToICS("draw\n");
9795         }
9796 #endif
9797         cps->offeredDraw = 2; /* valid until this engine moves twice */
9798         if (gameMode == TwoMachinesPlay) {
9799             if (cps->other->offeredDraw) {
9800                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9801             /* [HGM] in two-machine mode we delay relaying draw offer      */
9802             /* until after we also have move, to see if it is really claim */
9803             }
9804         } else if (gameMode == MachinePlaysWhite ||
9805                    gameMode == MachinePlaysBlack) {
9806           if (userOfferedDraw) {
9807             DisplayInformation(_("Machine accepts your draw offer"));
9808             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9809           } else {
9810             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9811           }
9812         }
9813     }
9814
9815
9816     /*
9817      * Look for thinking output
9818      */
9819     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9820           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9821                                 ) {
9822         int plylev, mvleft, mvtot, curscore, time;
9823         char mvname[MOVE_LEN];
9824         u64 nodes; // [DM]
9825         char plyext;
9826         int ignore = FALSE;
9827         int prefixHint = FALSE;
9828         mvname[0] = NULLCHAR;
9829
9830         switch (gameMode) {
9831           case MachinePlaysBlack:
9832           case IcsPlayingBlack:
9833             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9834             break;
9835           case MachinePlaysWhite:
9836           case IcsPlayingWhite:
9837             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9838             break;
9839           case AnalyzeMode:
9840           case AnalyzeFile:
9841             break;
9842           case IcsObserving: /* [DM] icsEngineAnalyze */
9843             if (!appData.icsEngineAnalyze) ignore = TRUE;
9844             break;
9845           case TwoMachinesPlay:
9846             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9847                 ignore = TRUE;
9848             }
9849             break;
9850           default:
9851             ignore = TRUE;
9852             break;
9853         }
9854
9855         if (!ignore) {
9856             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9857             int solved = 0;
9858             buf1[0] = NULLCHAR;
9859             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9860                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9861                 char score_buf[MSG_SIZ];
9862
9863                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9864                     nodes += u64Const(0x100000000);
9865
9866                 if (plyext != ' ' && plyext != '\t') {
9867                     time *= 100;
9868                 }
9869
9870                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9871                 if( cps->scoreIsAbsolute &&
9872                     ( gameMode == MachinePlaysBlack ||
9873                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9874                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9875                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9876                      !WhiteOnMove(currentMove)
9877                     ) )
9878                 {
9879                     curscore = -curscore;
9880                 }
9881
9882                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9883
9884                 if(*bestMove) { // rememer time best EPD move was first found
9885                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9886                     ChessMove mt; char *p = bestMove;
9887                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9888                     solved = 0;
9889                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9890                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9891                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9892                             solved = 1;
9893                             break;
9894                         }
9895                         while(*p && *p != ' ') p++;
9896                         while(*p == ' ') p++;
9897                     }
9898                     if(!solved) solvingTime = -1;
9899                 }
9900                 if(*avoidMove && !solved) {
9901                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9902                     ChessMove mt; char *p = avoidMove, solved = 1;
9903                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9904                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9905                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9906                             solved = 0; solvingTime = -2;
9907                             break;
9908                         }
9909                         while(*p && *p != ' ') p++;
9910                         while(*p == ' ') p++;
9911                     }
9912                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9913                 }
9914
9915                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9916                         char buf[MSG_SIZ];
9917                         FILE *f;
9918                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9919                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9920                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9921                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9922                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9923                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9924                                 fclose(f);
9925                         }
9926                         else
9927                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9928                           DisplayError(_("failed writing PV"), 0);
9929                 }
9930
9931                 tempStats.depth = plylev;
9932                 tempStats.nodes = nodes;
9933                 tempStats.time = time;
9934                 tempStats.score = curscore;
9935                 tempStats.got_only_move = 0;
9936
9937                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9938                         int ticklen;
9939
9940                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9941                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9942                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9943                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9944                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9945                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9946                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9947                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9948                 }
9949
9950                 /* Buffer overflow protection */
9951                 if (pv[0] != NULLCHAR) {
9952                     if (strlen(pv) >= sizeof(tempStats.movelist)
9953                         && appData.debugMode) {
9954                         fprintf(debugFP,
9955                                 "PV is too long; using the first %u bytes.\n",
9956                                 (unsigned) sizeof(tempStats.movelist) - 1);
9957                     }
9958
9959                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9960                 } else {
9961                     sprintf(tempStats.movelist, " no PV\n");
9962                 }
9963
9964                 if (tempStats.seen_stat) {
9965                     tempStats.ok_to_send = 1;
9966                 }
9967
9968                 if (strchr(tempStats.movelist, '(') != NULL) {
9969                     tempStats.line_is_book = 1;
9970                     tempStats.nr_moves = 0;
9971                     tempStats.moves_left = 0;
9972                 } else {
9973                     tempStats.line_is_book = 0;
9974                 }
9975
9976                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9977                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9978
9979                 SendProgramStatsToFrontend( cps, &tempStats );
9980
9981                 /*
9982                     [AS] Protect the thinkOutput buffer from overflow... this
9983                     is only useful if buf1 hasn't overflowed first!
9984                 */
9985                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9986                 if(curscore >= MATE_SCORE) 
9987                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9988                 else if(curscore <= -MATE_SCORE) 
9989                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9990                 else
9991                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9992                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9993                          plylev,
9994                          (gameMode == TwoMachinesPlay ?
9995                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9996                          score_buf,
9997                          prefixHint ? lastHint : "",
9998                          prefixHint ? " " : "" );
9999
10000                 if( buf1[0] != NULLCHAR ) {
10001                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
10002
10003                     if( strlen(pv) > max_len ) {
10004                         if( appData.debugMode) {
10005                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
10006                         }
10007                         pv[max_len+1] = '\0';
10008                     }
10009
10010                     strcat( thinkOutput, pv);
10011                 }
10012
10013                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10014                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10015                     DisplayMove(currentMove - 1);
10016                 }
10017                 return;
10018
10019             } else if ((p=StrStr(message, "(only move)")) != NULL) {
10020                 /* crafty (9.25+) says "(only move) <move>"
10021                  * if there is only 1 legal move
10022                  */
10023                 sscanf(p, "(only move) %s", buf1);
10024                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10025                 sprintf(programStats.movelist, "%s (only move)", buf1);
10026                 programStats.depth = 1;
10027                 programStats.nr_moves = 1;
10028                 programStats.moves_left = 1;
10029                 programStats.nodes = 1;
10030                 programStats.time = 1;
10031                 programStats.got_only_move = 1;
10032
10033                 /* Not really, but we also use this member to
10034                    mean "line isn't going to change" (Crafty
10035                    isn't searching, so stats won't change) */
10036                 programStats.line_is_book = 1;
10037
10038                 SendProgramStatsToFrontend( cps, &programStats );
10039
10040                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10041                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10042                     DisplayMove(currentMove - 1);
10043                 }
10044                 return;
10045             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10046                               &time, &nodes, &plylev, &mvleft,
10047                               &mvtot, mvname) >= 5) {
10048                 /* The stat01: line is from Crafty (9.29+) in response
10049                    to the "." command */
10050                 programStats.seen_stat = 1;
10051                 cps->maybeThinking = TRUE;
10052
10053                 if (programStats.got_only_move || !appData.periodicUpdates)
10054                   return;
10055
10056                 programStats.depth = plylev;
10057                 programStats.time = time;
10058                 programStats.nodes = nodes;
10059                 programStats.moves_left = mvleft;
10060                 programStats.nr_moves = mvtot;
10061                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10062                 programStats.ok_to_send = 1;
10063                 programStats.movelist[0] = '\0';
10064
10065                 SendProgramStatsToFrontend( cps, &programStats );
10066
10067                 return;
10068
10069             } else if (strncmp(message,"++",2) == 0) {
10070                 /* Crafty 9.29+ outputs this */
10071                 programStats.got_fail = 2;
10072                 return;
10073
10074             } else if (strncmp(message,"--",2) == 0) {
10075                 /* Crafty 9.29+ outputs this */
10076                 programStats.got_fail = 1;
10077                 return;
10078
10079             } else if (thinkOutput[0] != NULLCHAR &&
10080                        strncmp(message, "    ", 4) == 0) {
10081                 unsigned message_len;
10082
10083                 p = message;
10084                 while (*p && *p == ' ') p++;
10085
10086                 message_len = strlen( p );
10087
10088                 /* [AS] Avoid buffer overflow */
10089                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10090                     strcat(thinkOutput, " ");
10091                     strcat(thinkOutput, p);
10092                 }
10093
10094                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10095                     strcat(programStats.movelist, " ");
10096                     strcat(programStats.movelist, p);
10097                 }
10098
10099                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10100                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10101                     DisplayMove(currentMove - 1);
10102                 }
10103                 return;
10104             }
10105         }
10106         else {
10107             buf1[0] = NULLCHAR;
10108
10109             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10110                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10111             {
10112                 ChessProgramStats cpstats;
10113
10114                 if (plyext != ' ' && plyext != '\t') {
10115                     time *= 100;
10116                 }
10117
10118                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10119                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10120                     curscore = -curscore;
10121                 }
10122
10123                 cpstats.depth = plylev;
10124                 cpstats.nodes = nodes;
10125                 cpstats.time = time;
10126                 cpstats.score = curscore;
10127                 cpstats.got_only_move = 0;
10128                 cpstats.movelist[0] = '\0';
10129
10130                 if (buf1[0] != NULLCHAR) {
10131                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10132                 }
10133
10134                 cpstats.ok_to_send = 0;
10135                 cpstats.line_is_book = 0;
10136                 cpstats.nr_moves = 0;
10137                 cpstats.moves_left = 0;
10138
10139                 SendProgramStatsToFrontend( cps, &cpstats );
10140             }
10141         }
10142     }
10143 }
10144
10145
10146 /* Parse a game score from the character string "game", and
10147    record it as the history of the current game.  The game
10148    score is NOT assumed to start from the standard position.
10149    The display is not updated in any way.
10150    */
10151 void
10152 ParseGameHistory (char *game)
10153 {
10154     ChessMove moveType;
10155     int fromX, fromY, toX, toY, boardIndex, mask;
10156     char promoChar;
10157     char *p, *q;
10158     char buf[MSG_SIZ];
10159
10160     if (appData.debugMode)
10161       fprintf(debugFP, "Parsing game history: %s\n", game);
10162
10163     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10164     gameInfo.site = StrSave(appData.icsHost);
10165     gameInfo.date = PGNDate();
10166     gameInfo.round = StrSave("-");
10167
10168     /* Parse out names of players */
10169     while (*game == ' ') game++;
10170     p = buf;
10171     while (*game != ' ') *p++ = *game++;
10172     *p = NULLCHAR;
10173     gameInfo.white = StrSave(buf);
10174     while (*game == ' ') game++;
10175     p = buf;
10176     while (*game != ' ' && *game != '\n') *p++ = *game++;
10177     *p = NULLCHAR;
10178     gameInfo.black = StrSave(buf);
10179
10180     /* Parse moves */
10181     boardIndex = blackPlaysFirst ? 1 : 0;
10182     yynewstr(game);
10183     for (;;) {
10184         yyboardindex = boardIndex;
10185         moveType = (ChessMove) Myylex();
10186         switch (moveType) {
10187           case IllegalMove:             /* maybe suicide chess, etc. */
10188   if (appData.debugMode) {
10189     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10190     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10191     setbuf(debugFP, NULL);
10192   }
10193           case WhitePromotion:
10194           case BlackPromotion:
10195           case WhiteNonPromotion:
10196           case BlackNonPromotion:
10197           case NormalMove:
10198           case FirstLeg:
10199           case WhiteCapturesEnPassant:
10200           case BlackCapturesEnPassant:
10201           case WhiteKingSideCastle:
10202           case WhiteQueenSideCastle:
10203           case BlackKingSideCastle:
10204           case BlackQueenSideCastle:
10205           case WhiteKingSideCastleWild:
10206           case WhiteQueenSideCastleWild:
10207           case BlackKingSideCastleWild:
10208           case BlackQueenSideCastleWild:
10209           /* PUSH Fabien */
10210           case WhiteHSideCastleFR:
10211           case WhiteASideCastleFR:
10212           case BlackHSideCastleFR:
10213           case BlackASideCastleFR:
10214           /* POP Fabien */
10215             fromX = currentMoveString[0] - AAA;
10216             fromY = currentMoveString[1] - ONE;
10217             toX = currentMoveString[2] - AAA;
10218             toY = currentMoveString[3] - ONE;
10219             promoChar = currentMoveString[4];
10220             break;
10221           case WhiteDrop:
10222           case BlackDrop:
10223             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10224             fromX = moveType == WhiteDrop ?
10225               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10226             (int) CharToPiece(ToLower(currentMoveString[0]));
10227             fromY = DROP_RANK;
10228             toX = currentMoveString[2] - AAA;
10229             toY = currentMoveString[3] - ONE;
10230             promoChar = NULLCHAR;
10231             break;
10232           case AmbiguousMove:
10233             /* bug? */
10234             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10235   if (appData.debugMode) {
10236     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10237     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10238     setbuf(debugFP, NULL);
10239   }
10240             DisplayError(buf, 0);
10241             return;
10242           case ImpossibleMove:
10243             /* bug? */
10244             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10245   if (appData.debugMode) {
10246     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10247     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10248     setbuf(debugFP, NULL);
10249   }
10250             DisplayError(buf, 0);
10251             return;
10252           case EndOfFile:
10253             if (boardIndex < backwardMostMove) {
10254                 /* Oops, gap.  How did that happen? */
10255                 DisplayError(_("Gap in move list"), 0);
10256                 return;
10257             }
10258             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10259             if (boardIndex > forwardMostMove) {
10260                 forwardMostMove = boardIndex;
10261             }
10262             return;
10263           case ElapsedTime:
10264             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10265                 strcat(parseList[boardIndex-1], " ");
10266                 strcat(parseList[boardIndex-1], yy_text);
10267             }
10268             continue;
10269           case Comment:
10270           case PGNTag:
10271           case NAG:
10272           default:
10273             /* ignore */
10274             continue;
10275           case WhiteWins:
10276           case BlackWins:
10277           case GameIsDrawn:
10278           case GameUnfinished:
10279             if (gameMode == IcsExamining) {
10280                 if (boardIndex < backwardMostMove) {
10281                     /* Oops, gap.  How did that happen? */
10282                     return;
10283                 }
10284                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10285                 return;
10286             }
10287             gameInfo.result = moveType;
10288             p = strchr(yy_text, '{');
10289             if (p == NULL) p = strchr(yy_text, '(');
10290             if (p == NULL) {
10291                 p = yy_text;
10292                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10293             } else {
10294                 q = strchr(p, *p == '{' ? '}' : ')');
10295                 if (q != NULL) *q = NULLCHAR;
10296                 p++;
10297             }
10298             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10299             gameInfo.resultDetails = StrSave(p);
10300             continue;
10301         }
10302         if (boardIndex >= forwardMostMove &&
10303             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10304             backwardMostMove = blackPlaysFirst ? 1 : 0;
10305             return;
10306         }
10307         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10308                                  fromY, fromX, toY, toX, promoChar,
10309                                  parseList[boardIndex]);
10310         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10311         /* currentMoveString is set as a side-effect of yylex */
10312         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10313         strcat(moveList[boardIndex], "\n");
10314         boardIndex++;
10315         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10316         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10317         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10318           case MT_NONE:
10319           case MT_STALEMATE:
10320           default:
10321             break;
10322           case MT_CHECK:
10323             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10324             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10325                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10326                 break;
10327             }
10328           case MT_CHECKMATE:
10329           case MT_STAINMATE:
10330             strcat(parseList[boardIndex - 1], "#");
10331             break;
10332         }
10333     }
10334 }
10335
10336
10337 /* Apply a move to the given board  */
10338 void
10339 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10340 {
10341   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10342   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10343
10344     /* [HGM] compute & store e.p. status and castling rights for new position */
10345     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10346
10347       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10348       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10349       board[EP_STATUS] = EP_NONE;
10350       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10351
10352   if (fromY == DROP_RANK) {
10353         /* must be first */
10354         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10355             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10356             return;
10357         }
10358         piece = board[toY][toX] = (ChessSquare) fromX;
10359   } else {
10360 //      ChessSquare victim;
10361       int i;
10362
10363       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10364 //           victim = board[killY][killX],
10365            killed = board[killY][killX],
10366            board[killY][killX] = EmptySquare,
10367            board[EP_STATUS] = EP_CAPTURE;
10368            if( kill2X >= 0 && kill2Y >= 0)
10369              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10370       }
10371
10372       if( board[toY][toX] != EmptySquare ) {
10373            board[EP_STATUS] = EP_CAPTURE;
10374            if( (fromX != toX || fromY != toY) && // not igui!
10375                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10376                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10377                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10378            }
10379       }
10380
10381       pawn = board[fromY][fromX];
10382       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10383         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10384             captured = board[lastRank][lastFile]; // remove victim
10385             board[lastRank][lastFile] = EmptySquare;
10386             pawn = EmptySquare; // kludge to suppress old e.p. code
10387         }
10388       }
10389       if( pawn == WhiteLance || pawn == BlackLance ) {
10390            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10391                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10392                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10393            }
10394       }
10395       if( pawn == WhitePawn ) {
10396            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10397                board[EP_STATUS] = EP_PAWN_MOVE;
10398            if( toY-fromY>=2) {
10399                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10400                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10401                         gameInfo.variant != VariantBerolina || toX < fromX)
10402                       board[EP_STATUS] = toX | berolina;
10403                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10404                         gameInfo.variant != VariantBerolina || toX > fromX)
10405                       board[EP_STATUS] = toX;
10406                board[LAST_TO] = toX + 256*toY;
10407            }
10408       } else
10409       if( pawn == BlackPawn ) {
10410            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10411                board[EP_STATUS] = EP_PAWN_MOVE;
10412            if( toY-fromY<= -2) {
10413                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10414                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10415                         gameInfo.variant != VariantBerolina || toX < fromX)
10416                       board[EP_STATUS] = toX | berolina;
10417                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10418                         gameInfo.variant != VariantBerolina || toX > fromX)
10419                       board[EP_STATUS] = toX;
10420                board[LAST_TO] = toX + 256*toY;
10421            }
10422        }
10423
10424        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10425        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10426        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10427        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10428
10429        for(i=0; i<nrCastlingRights; i++) {
10430            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10431               board[CASTLING][i] == toX   && castlingRank[i] == toY
10432              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10433        }
10434
10435        if(gameInfo.variant == VariantSChess) { // update virginity
10436            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10437            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10438            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10439            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10440        }
10441
10442      if (fromX == toX && fromY == toY && killX < 0) return;
10443
10444      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10445      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10446      if(gameInfo.variant == VariantKnightmate)
10447          king += (int) WhiteUnicorn - (int) WhiteKing;
10448
10449     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10450        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10451         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10452         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10453         board[EP_STATUS] = EP_NONE; // capture was fake!
10454     } else
10455     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10456         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10457         board[toY][toX] = piece;
10458         board[EP_STATUS] = EP_NONE; // capture was fake!
10459     } else
10460     /* Code added by Tord: */
10461     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10462     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10463         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10464       board[EP_STATUS] = EP_NONE; // capture was fake!
10465       board[fromY][fromX] = EmptySquare;
10466       board[toY][toX] = EmptySquare;
10467       if((toX > fromX) != (piece == WhiteRook)) {
10468         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10469       } else {
10470         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10471       }
10472     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10473                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10474       board[EP_STATUS] = EP_NONE;
10475       board[fromY][fromX] = EmptySquare;
10476       board[toY][toX] = EmptySquare;
10477       if((toX > fromX) != (piece == BlackRook)) {
10478         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10479       } else {
10480         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10481       }
10482     /* End of code added by Tord */
10483
10484     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10485         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10486         board[toY][toX] = piece;
10487     } else if (board[fromY][fromX] == king
10488         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10489         && toY == fromY && toX > fromX+1) {
10490         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10491                                                                                              ; // castle with nearest piece
10492         board[fromY][toX-1] = board[fromY][rookX];
10493         board[fromY][rookX] = EmptySquare;
10494         board[fromY][fromX] = EmptySquare;
10495         board[toY][toX] = king;
10496     } else if (board[fromY][fromX] == king
10497         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10498                && toY == fromY && toX < fromX-1) {
10499         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10500                                                                                   ; // castle with nearest piece
10501         board[fromY][toX+1] = board[fromY][rookX];
10502         board[fromY][rookX] = EmptySquare;
10503         board[fromY][fromX] = EmptySquare;
10504         board[toY][toX] = king;
10505     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10506                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10507                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10508                ) {
10509         /* white pawn promotion */
10510         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10511         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10512             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10513         board[fromY][fromX] = EmptySquare;
10514     } else if ((fromY >= BOARD_HEIGHT>>1)
10515                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10516                && (toX != fromX)
10517                && gameInfo.variant != VariantXiangqi
10518                && gameInfo.variant != VariantBerolina
10519                && (pawn == WhitePawn)
10520                && (board[toY][toX] == EmptySquare)) {
10521         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10522         board[fromY][fromX] = EmptySquare;
10523         board[toY][toX] = piece;
10524         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10525     } else if ((fromY == BOARD_HEIGHT-4)
10526                && (toX == fromX)
10527                && gameInfo.variant == VariantBerolina
10528                && (board[fromY][fromX] == WhitePawn)
10529                && (board[toY][toX] == EmptySquare)) {
10530         board[fromY][fromX] = EmptySquare;
10531         board[toY][toX] = WhitePawn;
10532         if(oldEP & EP_BEROLIN_A) {
10533                 captured = board[fromY][fromX-1];
10534                 board[fromY][fromX-1] = EmptySquare;
10535         }else{  captured = board[fromY][fromX+1];
10536                 board[fromY][fromX+1] = EmptySquare;
10537         }
10538     } else if (board[fromY][fromX] == king
10539         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10540                && toY == fromY && toX > fromX+1) {
10541         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10542                                                                                              ;
10543         board[fromY][toX-1] = board[fromY][rookX];
10544         board[fromY][rookX] = EmptySquare;
10545         board[fromY][fromX] = EmptySquare;
10546         board[toY][toX] = king;
10547     } else if (board[fromY][fromX] == king
10548         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10549                && toY == fromY && toX < fromX-1) {
10550         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10551                                                                                 ;
10552         board[fromY][toX+1] = board[fromY][rookX];
10553         board[fromY][rookX] = EmptySquare;
10554         board[fromY][fromX] = EmptySquare;
10555         board[toY][toX] = king;
10556     } else if (fromY == 7 && fromX == 3
10557                && board[fromY][fromX] == BlackKing
10558                && toY == 7 && toX == 5) {
10559         board[fromY][fromX] = EmptySquare;
10560         board[toY][toX] = BlackKing;
10561         board[fromY][7] = EmptySquare;
10562         board[toY][4] = BlackRook;
10563     } else if (fromY == 7 && fromX == 3
10564                && board[fromY][fromX] == BlackKing
10565                && toY == 7 && toX == 1) {
10566         board[fromY][fromX] = EmptySquare;
10567         board[toY][toX] = BlackKing;
10568         board[fromY][0] = EmptySquare;
10569         board[toY][2] = BlackRook;
10570     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10571                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10572                && toY < promoRank && promoChar
10573                ) {
10574         /* black pawn promotion */
10575         board[toY][toX] = CharToPiece(ToLower(promoChar));
10576         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10577             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10578         board[fromY][fromX] = EmptySquare;
10579     } else if ((fromY < BOARD_HEIGHT>>1)
10580                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10581                && (toX != fromX)
10582                && gameInfo.variant != VariantXiangqi
10583                && gameInfo.variant != VariantBerolina
10584                && (pawn == BlackPawn)
10585                && (board[toY][toX] == EmptySquare)) {
10586         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10587         board[fromY][fromX] = EmptySquare;
10588         board[toY][toX] = piece;
10589         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10590     } else if ((fromY == 3)
10591                && (toX == fromX)
10592                && gameInfo.variant == VariantBerolina
10593                && (board[fromY][fromX] == BlackPawn)
10594                && (board[toY][toX] == EmptySquare)) {
10595         board[fromY][fromX] = EmptySquare;
10596         board[toY][toX] = BlackPawn;
10597         if(oldEP & EP_BEROLIN_A) {
10598                 captured = board[fromY][fromX-1];
10599                 board[fromY][fromX-1] = EmptySquare;
10600         }else{  captured = board[fromY][fromX+1];
10601                 board[fromY][fromX+1] = EmptySquare;
10602         }
10603     } else {
10604         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10605         board[fromY][fromX] = EmptySquare;
10606         board[toY][toX] = piece;
10607     }
10608   }
10609
10610     if (gameInfo.holdingsWidth != 0) {
10611
10612       /* !!A lot more code needs to be written to support holdings  */
10613       /* [HGM] OK, so I have written it. Holdings are stored in the */
10614       /* penultimate board files, so they are automaticlly stored   */
10615       /* in the game history.                                       */
10616       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10617                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10618         /* Delete from holdings, by decreasing count */
10619         /* and erasing image if necessary            */
10620         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10621         if(p < (int) BlackPawn) { /* white drop */
10622              p -= (int)WhitePawn;
10623                  p = PieceToNumber((ChessSquare)p);
10624              if(p >= gameInfo.holdingsSize) p = 0;
10625              if(--board[p][BOARD_WIDTH-2] <= 0)
10626                   board[p][BOARD_WIDTH-1] = EmptySquare;
10627              if((int)board[p][BOARD_WIDTH-2] < 0)
10628                         board[p][BOARD_WIDTH-2] = 0;
10629         } else {                  /* black drop */
10630              p -= (int)BlackPawn;
10631                  p = PieceToNumber((ChessSquare)p);
10632              if(p >= gameInfo.holdingsSize) p = 0;
10633              if(--board[handSize-1-p][1] <= 0)
10634                   board[handSize-1-p][0] = EmptySquare;
10635              if((int)board[handSize-1-p][1] < 0)
10636                         board[handSize-1-p][1] = 0;
10637         }
10638       }
10639       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10640           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10641         /* [HGM] holdings: Add to holdings, if holdings exist */
10642         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10643                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10644                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10645         }
10646         p = (int) captured;
10647         if (p >= (int) BlackPawn) {
10648           p -= (int)BlackPawn;
10649           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10650                   /* Restore shogi-promoted piece to its original  first */
10651                   captured = (ChessSquare) (DEMOTED(captured));
10652                   p = DEMOTED(p);
10653           }
10654           p = PieceToNumber((ChessSquare)p);
10655           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10656           board[p][BOARD_WIDTH-2]++;
10657           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10658         } else {
10659           p -= (int)WhitePawn;
10660           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10661                   captured = (ChessSquare) (DEMOTED(captured));
10662                   p = DEMOTED(p);
10663           }
10664           p = PieceToNumber((ChessSquare)p);
10665           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10666           board[handSize-1-p][1]++;
10667           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10668         }
10669       }
10670     } else if (gameInfo.variant == VariantAtomic) {
10671       if (captured != EmptySquare) {
10672         int y, x;
10673         for (y = toY-1; y <= toY+1; y++) {
10674           for (x = toX-1; x <= toX+1; x++) {
10675             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10676                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10677               board[y][x] = EmptySquare;
10678             }
10679           }
10680         }
10681         board[toY][toX] = EmptySquare;
10682       }
10683     }
10684
10685     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10686         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10687     } else
10688     if(promoChar == '+') {
10689         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10690         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10691         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10692           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10693     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10694         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10695         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10696            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10697         board[toY][toX] = newPiece;
10698     }
10699     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10700                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10701         // [HGM] superchess: take promotion piece out of holdings
10702         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10703         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10704             if(!--board[k][BOARD_WIDTH-2])
10705                 board[k][BOARD_WIDTH-1] = EmptySquare;
10706         } else {
10707             if(!--board[handSize-1-k][1])
10708                 board[handSize-1-k][0] = EmptySquare;
10709         }
10710     }
10711 }
10712
10713 /* Updates forwardMostMove */
10714 void
10715 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10716 {
10717     int x = toX, y = toY, mask;
10718     char *s = parseList[forwardMostMove];
10719     ChessSquare p = boards[forwardMostMove][toY][toX];
10720 //    forwardMostMove++; // [HGM] bare: moved downstream
10721
10722     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10723     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10724     (void) CoordsToAlgebraic(boards[forwardMostMove],
10725                              PosFlags(forwardMostMove),
10726                              fromY, fromX, y, x, (killX < 0)*promoChar,
10727                              s);
10728     if(kill2X >= 0 && kill2Y >= 0)
10729         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10730     if(killX >= 0 && killY >= 0)
10731         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10732                                            toX + AAA, toY + ONE - '0', promoChar);
10733
10734     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10735         int timeLeft; static int lastLoadFlag=0; int king, piece;
10736         piece = boards[forwardMostMove][fromY][fromX];
10737         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10738         if(gameInfo.variant == VariantKnightmate)
10739             king += (int) WhiteUnicorn - (int) WhiteKing;
10740         if(forwardMostMove == 0) {
10741             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10742                 fprintf(serverMoves, "%s;", UserName());
10743             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10744                 fprintf(serverMoves, "%s;", second.tidy);
10745             fprintf(serverMoves, "%s;", first.tidy);
10746             if(gameMode == MachinePlaysWhite)
10747                 fprintf(serverMoves, "%s;", UserName());
10748             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10749                 fprintf(serverMoves, "%s;", second.tidy);
10750         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10751         lastLoadFlag = loadFlag;
10752         // print base move
10753         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10754         // print castling suffix
10755         if( toY == fromY && piece == king ) {
10756             if(toX-fromX > 1)
10757                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10758             if(fromX-toX >1)
10759                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10760         }
10761         // e.p. suffix
10762         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10763              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10764              boards[forwardMostMove][toY][toX] == EmptySquare
10765              && fromX != toX && fromY != toY)
10766                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10767         // promotion suffix
10768         if(promoChar != NULLCHAR) {
10769             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10770                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10771                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10772             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10773         }
10774         if(!loadFlag) {
10775                 char buf[MOVE_LEN*2], *p; int len;
10776             fprintf(serverMoves, "/%d/%d",
10777                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10778             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10779             else                      timeLeft = blackTimeRemaining/1000;
10780             fprintf(serverMoves, "/%d", timeLeft);
10781                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10782                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10783                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10784                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10785             fprintf(serverMoves, "/%s", buf);
10786         }
10787         fflush(serverMoves);
10788     }
10789
10790     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10791         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10792       return;
10793     }
10794     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10795     if (commentList[forwardMostMove+1] != NULL) {
10796         free(commentList[forwardMostMove+1]);
10797         commentList[forwardMostMove+1] = NULL;
10798     }
10799     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10800     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10801     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10802     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10803     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10804     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10805     adjustedClock = FALSE;
10806     gameInfo.result = GameUnfinished;
10807     if (gameInfo.resultDetails != NULL) {
10808         free(gameInfo.resultDetails);
10809         gameInfo.resultDetails = NULL;
10810     }
10811     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10812                               moveList[forwardMostMove - 1]);
10813     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10814     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10815       case MT_NONE:
10816       case MT_STALEMATE:
10817       default:
10818         break;
10819       case MT_CHECK:
10820         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10821         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10822             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10823             break;
10824         }
10825       case MT_CHECKMATE:
10826       case MT_STAINMATE:
10827         strcat(parseList[forwardMostMove - 1], "#");
10828         break;
10829     }
10830 }
10831
10832 /* Updates currentMove if not pausing */
10833 void
10834 ShowMove (int fromX, int fromY, int toX, int toY)
10835 {
10836     int instant = (gameMode == PlayFromGameFile) ?
10837         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10838     if(appData.noGUI) return;
10839     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10840         if (!instant) {
10841             if (forwardMostMove == currentMove + 1) {
10842                 AnimateMove(boards[forwardMostMove - 1],
10843                             fromX, fromY, toX, toY);
10844             }
10845         }
10846         currentMove = forwardMostMove;
10847     }
10848
10849     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10850
10851     if (instant) return;
10852
10853     DisplayMove(currentMove - 1);
10854     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10855             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10856                 SetHighlights(fromX, fromY, toX, toY);
10857             }
10858     }
10859     DrawPosition(FALSE, boards[currentMove]);
10860     DisplayBothClocks();
10861     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10862 }
10863
10864 void
10865 SendEgtPath (ChessProgramState *cps)
10866 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10867         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10868
10869         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10870
10871         while(*p) {
10872             char c, *q = name+1, *r, *s;
10873
10874             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10875             while(*p && *p != ',') *q++ = *p++;
10876             *q++ = ':'; *q = 0;
10877             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10878                 strcmp(name, ",nalimov:") == 0 ) {
10879                 // take nalimov path from the menu-changeable option first, if it is defined
10880               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10881                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10882             } else
10883             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10884                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10885                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10886                 s = r = StrStr(s, ":") + 1; // beginning of path info
10887                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10888                 c = *r; *r = 0;             // temporarily null-terminate path info
10889                     *--q = 0;               // strip of trailig ':' from name
10890                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10891                 *r = c;
10892                 SendToProgram(buf,cps);     // send egtbpath command for this format
10893             }
10894             if(*p == ',') p++; // read away comma to position for next format name
10895         }
10896 }
10897
10898 static int
10899 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10900 {
10901       int width = 8, height = 8, holdings = 0;             // most common sizes
10902       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10903       // correct the deviations default for each variant
10904       if( v == VariantXiangqi ) width = 9,  height = 10;
10905       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10906       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10907       if( v == VariantCapablanca || v == VariantCapaRandom ||
10908           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10909                                 width = 10;
10910       if( v == VariantCourier ) width = 12;
10911       if( v == VariantSuper )                            holdings = 8;
10912       if( v == VariantGreat )   width = 10,              holdings = 8;
10913       if( v == VariantSChess )                           holdings = 7;
10914       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10915       if( v == VariantChuChess) width = 10, height = 10;
10916       if( v == VariantChu )     width = 12, height = 12;
10917       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10918              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10919              holdingsSize >= 0 && holdingsSize != holdings;
10920 }
10921
10922 char variantError[MSG_SIZ];
10923
10924 char *
10925 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10926 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10927       char *p, *variant = VariantName(v);
10928       static char b[MSG_SIZ];
10929       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10930            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10931                                                holdingsSize, variant); // cook up sized variant name
10932            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10933            if(StrStr(list, b) == NULL) {
10934                // specific sized variant not known, check if general sizing allowed
10935                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10936                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10937                             boardWidth, boardHeight, holdingsSize, engine);
10938                    return NULL;
10939                }
10940                /* [HGM] here we really should compare with the maximum supported board size */
10941            }
10942       } else snprintf(b, MSG_SIZ,"%s", variant);
10943       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10944       p = StrStr(list, b);
10945       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10946       if(p == NULL) {
10947           // occurs not at all in list, or only as sub-string
10948           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10949           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10950               int l = strlen(variantError);
10951               char *q;
10952               while(p != list && p[-1] != ',') p--;
10953               q = strchr(p, ',');
10954               if(q) *q = NULLCHAR;
10955               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10956               if(q) *q= ',';
10957           }
10958           return NULL;
10959       }
10960       return b;
10961 }
10962
10963 void
10964 InitChessProgram (ChessProgramState *cps, int setup)
10965 /* setup needed to setup FRC opening position */
10966 {
10967     char buf[MSG_SIZ], *b;
10968     if (appData.noChessProgram) return;
10969     hintRequested = FALSE;
10970     bookRequested = FALSE;
10971
10972     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10973     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10974     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10975     if(cps->memSize) { /* [HGM] memory */
10976       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10977         SendToProgram(buf, cps);
10978     }
10979     SendEgtPath(cps); /* [HGM] EGT */
10980     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10981       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10982         SendToProgram(buf, cps);
10983     }
10984
10985     setboardSpoiledMachineBlack = FALSE;
10986     SendToProgram(cps->initString, cps);
10987     if (gameInfo.variant != VariantNormal &&
10988         gameInfo.variant != VariantLoadable
10989         /* [HGM] also send variant if board size non-standard */
10990         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10991
10992       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10993                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10994
10995       if (b == NULL) {
10996         VariantClass v;
10997         char c, *q = cps->variants, *p = strchr(q, ',');
10998         if(p) *p = NULLCHAR;
10999         v = StringToVariant(q);
11000         DisplayError(variantError, 0);
11001         if(v != VariantUnknown && cps == &first) {
11002             int w, h, s;
11003             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
11004                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
11005             ASSIGN(appData.variant, q);
11006             Reset(TRUE, FALSE);
11007         }
11008         if(p) *p = ',';
11009         return;
11010       }
11011
11012       snprintf(buf, MSG_SIZ, "variant %s\n", b);
11013       SendToProgram(buf, cps);
11014     }
11015     currentlyInitializedVariant = gameInfo.variant;
11016
11017     /* [HGM] send opening position in FRC to first engine */
11018     if(setup) {
11019           SendToProgram("force\n", cps);
11020           SendBoard(cps, 0);
11021           /* engine is now in force mode! Set flag to wake it up after first move. */
11022           setboardSpoiledMachineBlack = 1;
11023     }
11024
11025     if (cps->sendICS) {
11026       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11027       SendToProgram(buf, cps);
11028     }
11029     cps->maybeThinking = FALSE;
11030     cps->offeredDraw = 0;
11031     if (!appData.icsActive) {
11032         SendTimeControl(cps, movesPerSession, timeControl,
11033                         timeIncrement, appData.searchDepth,
11034                         searchTime);
11035     }
11036     if (appData.showThinking
11037         // [HGM] thinking: four options require thinking output to be sent
11038         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11039                                 ) {
11040         SendToProgram("post\n", cps);
11041     }
11042     SendToProgram("hard\n", cps);
11043     if (!appData.ponderNextMove) {
11044         /* Warning: "easy" is a toggle in GNU Chess, so don't send
11045            it without being sure what state we are in first.  "hard"
11046            is not a toggle, so that one is OK.
11047          */
11048         SendToProgram("easy\n", cps);
11049     }
11050     if (cps->usePing) {
11051       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11052       SendToProgram(buf, cps);
11053     }
11054     cps->initDone = TRUE;
11055     ClearEngineOutputPane(cps == &second);
11056 }
11057
11058
11059 char *
11060 ResendOptions (ChessProgramState *cps, int toEngine)
11061 { // send the stored value of the options
11062   int i;
11063   static char buf2[MSG_SIZ*10];
11064   char buf[MSG_SIZ], *p = buf2;
11065   Option *opt = cps->option;
11066   *p = NULLCHAR;
11067   for(i=0; i<cps->nrOptions; i++, opt++) {
11068       *buf = NULLCHAR;
11069       switch(opt->type) {
11070         case Spin:
11071         case Slider:
11072         case CheckBox:
11073             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11074             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11075           break;
11076         case ComboBox:
11077             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11078             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11079           break;
11080         default:
11081             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11082             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11083           break;
11084         case Button:
11085         case SaveButton:
11086           continue;
11087       }
11088       if(*buf) {
11089         if(toEngine) {
11090           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11091           SendToProgram(buf2, cps);
11092         } else {
11093           if(p != buf2) *p++ = ',';
11094           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11095           while(*p) p++;
11096         }
11097       }
11098   }
11099   return buf2;
11100 }
11101
11102 void
11103 StartChessProgram (ChessProgramState *cps)
11104 {
11105     char buf[MSG_SIZ];
11106     int err;
11107
11108     if (appData.noChessProgram) return;
11109     cps->initDone = FALSE;
11110
11111     if (strcmp(cps->host, "localhost") == 0) {
11112         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11113     } else if (*appData.remoteShell == NULLCHAR) {
11114         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11115     } else {
11116         if (*appData.remoteUser == NULLCHAR) {
11117           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11118                     cps->program);
11119         } else {
11120           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11121                     cps->host, appData.remoteUser, cps->program);
11122         }
11123         err = StartChildProcess(buf, "", &cps->pr);
11124     }
11125
11126     if (err != 0) {
11127       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11128         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11129         if(cps != &first) return;
11130         appData.noChessProgram = TRUE;
11131         ThawUI();
11132         SetNCPMode();
11133 //      DisplayFatalError(buf, err, 1);
11134 //      cps->pr = NoProc;
11135 //      cps->isr = NULL;
11136         return;
11137     }
11138
11139     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11140     if (cps->protocolVersion > 1) {
11141       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11142       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11143         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11144         cps->comboCnt = 0;  //                and values of combo boxes
11145       }
11146       SendToProgram(buf, cps);
11147       if(cps->reload) ResendOptions(cps, TRUE);
11148     } else {
11149       SendToProgram("xboard\n", cps);
11150     }
11151 }
11152
11153 void
11154 TwoMachinesEventIfReady P((void))
11155 {
11156   static int curMess = 0;
11157   if (first.lastPing != first.lastPong) {
11158     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11159     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11160     return;
11161   }
11162   if (second.lastPing != second.lastPong) {
11163     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11164     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11165     return;
11166   }
11167   DisplayMessage("", ""); curMess = 0;
11168   TwoMachinesEvent();
11169 }
11170
11171 char *
11172 MakeName (char *template)
11173 {
11174     time_t clock;
11175     struct tm *tm;
11176     static char buf[MSG_SIZ];
11177     char *p = buf;
11178     int i;
11179
11180     clock = time((time_t *)NULL);
11181     tm = localtime(&clock);
11182
11183     while(*p++ = *template++) if(p[-1] == '%') {
11184         switch(*template++) {
11185           case 0:   *p = 0; return buf;
11186           case 'Y': i = tm->tm_year+1900; break;
11187           case 'y': i = tm->tm_year-100; break;
11188           case 'M': i = tm->tm_mon+1; break;
11189           case 'd': i = tm->tm_mday; break;
11190           case 'h': i = tm->tm_hour; break;
11191           case 'm': i = tm->tm_min; break;
11192           case 's': i = tm->tm_sec; break;
11193           default:  i = 0;
11194         }
11195         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11196     }
11197     return buf;
11198 }
11199
11200 int
11201 CountPlayers (char *p)
11202 {
11203     int n = 0;
11204     while(p = strchr(p, '\n')) p++, n++; // count participants
11205     return n;
11206 }
11207
11208 FILE *
11209 WriteTourneyFile (char *results, FILE *f)
11210 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11211     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11212     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11213         // create a file with tournament description
11214         fprintf(f, "-participants {%s}\n", appData.participants);
11215         fprintf(f, "-seedBase %d\n", appData.seedBase);
11216         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11217         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11218         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11219         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11220         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11221         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11222         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11223         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11224         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11225         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11226         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11227         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11228         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11229         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11230         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11231         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11232         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11233         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11234         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11235         fprintf(f, "-smpCores %d\n", appData.smpCores);
11236         if(searchTime > 0)
11237                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11238         else {
11239                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11240                 fprintf(f, "-tc %s\n", appData.timeControl);
11241                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11242         }
11243         fprintf(f, "-results \"%s\"\n", results);
11244     }
11245     return f;
11246 }
11247
11248 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11249
11250 void
11251 Substitute (char *participants, int expunge)
11252 {
11253     int i, changed, changes=0, nPlayers=0;
11254     char *p, *q, *r, buf[MSG_SIZ];
11255     if(participants == NULL) return;
11256     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11257     r = p = participants; q = appData.participants;
11258     while(*p && *p == *q) {
11259         if(*p == '\n') r = p+1, nPlayers++;
11260         p++; q++;
11261     }
11262     if(*p) { // difference
11263         while(*p && *p++ != '\n')
11264                                  ;
11265         while(*q && *q++ != '\n')
11266                                  ;
11267       changed = nPlayers;
11268         changes = 1 + (strcmp(p, q) != 0);
11269     }
11270     if(changes == 1) { // a single engine mnemonic was changed
11271         q = r; while(*q) nPlayers += (*q++ == '\n');
11272         p = buf; while(*r && (*p = *r++) != '\n') p++;
11273         *p = NULLCHAR;
11274         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11275         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11276         if(mnemonic[i]) { // The substitute is valid
11277             FILE *f;
11278             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11279                 flock(fileno(f), LOCK_EX);
11280                 ParseArgsFromFile(f);
11281                 fseek(f, 0, SEEK_SET);
11282                 FREE(appData.participants); appData.participants = participants;
11283                 if(expunge) { // erase results of replaced engine
11284                     int len = strlen(appData.results), w, b, dummy;
11285                     for(i=0; i<len; i++) {
11286                         Pairing(i, nPlayers, &w, &b, &dummy);
11287                         if((w == changed || b == changed) && appData.results[i] == '*') {
11288                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11289                             fclose(f);
11290                             return;
11291                         }
11292                     }
11293                     for(i=0; i<len; i++) {
11294                         Pairing(i, nPlayers, &w, &b, &dummy);
11295                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11296                     }
11297                 }
11298                 WriteTourneyFile(appData.results, f);
11299                 fclose(f); // release lock
11300                 return;
11301             }
11302         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11303     }
11304     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11305     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11306     free(participants);
11307     return;
11308 }
11309
11310 int
11311 CheckPlayers (char *participants)
11312 {
11313         int i;
11314         char buf[MSG_SIZ], *p;
11315         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11316         while(p = strchr(participants, '\n')) {
11317             *p = NULLCHAR;
11318             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11319             if(!mnemonic[i]) {
11320                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11321                 *p = '\n';
11322                 DisplayError(buf, 0);
11323                 return 1;
11324             }
11325             *p = '\n';
11326             participants = p + 1;
11327         }
11328         return 0;
11329 }
11330
11331 int
11332 CreateTourney (char *name)
11333 {
11334         FILE *f;
11335         if(matchMode && strcmp(name, appData.tourneyFile)) {
11336              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11337         }
11338         if(name[0] == NULLCHAR) {
11339             if(appData.participants[0])
11340                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11341             return 0;
11342         }
11343         f = fopen(name, "r");
11344         if(f) { // file exists
11345             ASSIGN(appData.tourneyFile, name);
11346             ParseArgsFromFile(f); // parse it
11347         } else {
11348             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11349             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11350                 DisplayError(_("Not enough participants"), 0);
11351                 return 0;
11352             }
11353             if(CheckPlayers(appData.participants)) return 0;
11354             ASSIGN(appData.tourneyFile, name);
11355             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11356             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11357         }
11358         fclose(f);
11359         appData.noChessProgram = FALSE;
11360         appData.clockMode = TRUE;
11361         SetGNUMode();
11362         return 1;
11363 }
11364
11365 int
11366 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11367 {
11368     char buf[2*MSG_SIZ], *p, *q;
11369     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11370     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11371     skip = !all && group[0]; // if group requested, we start in skip mode
11372     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11373         p = names; q = buf; header = 0;
11374         while(*p && *p != '\n') *q++ = *p++;
11375         *q = 0;
11376         if(*p == '\n') p++;
11377         if(buf[0] == '#') {
11378             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11379             depth++; // we must be entering a new group
11380             if(all) continue; // suppress printing group headers when complete list requested
11381             header = 1;
11382             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11383         }
11384         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11385         if(engineList[i]) free(engineList[i]);
11386         engineList[i] = strdup(buf);
11387         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11388         if(engineMnemonic[i]) free(engineMnemonic[i]);
11389         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11390             strcat(buf, " (");
11391             sscanf(q + 8, "%s", buf + strlen(buf));
11392             strcat(buf, ")");
11393         }
11394         engineMnemonic[i] = strdup(buf);
11395         i++;
11396     }
11397     engineList[i] = engineMnemonic[i] = NULL;
11398     return i;
11399 }
11400
11401 void
11402 SaveEngineSettings (int n)
11403 {
11404     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11405     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11406     if(*engineListFile) ParseSettingsFile(engineListFile, &engineListFile); // update engine list
11407     p = strstr(firstChessProgramNames, currentEngine[n]);
11408     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11409     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11410     len = strlen(currentEngine[n]);
11411     q = p + len; *p = 0; // cut list into head and tail piece
11412     s = strstr(currentEngine[n], "firstOptions");
11413     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11414         char *r = s + 14;
11415         while(*r && *r != s[13]) r++;
11416         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11417         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11418     } else if(*optionSettings) {
11419         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11420     }
11421     ASSIGN(currentEngine[n], buf); // updated engine line
11422     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11423     s = malloc(len);
11424     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11425     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11426     if(*engineListFile) SaveEngineList();
11427 }
11428
11429 // following implemented as macro to avoid type limitations
11430 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11431
11432 void
11433 SwapEngines (int n)
11434 {   // swap settings for first engine and other engine (so far only some selected options)
11435     int h;
11436     char *p;
11437     if(n == 0) return;
11438     SWAP(directory, p)
11439     SWAP(chessProgram, p)
11440     SWAP(isUCI, h)
11441     SWAP(hasOwnBookUCI, h)
11442     SWAP(protocolVersion, h)
11443     SWAP(reuse, h)
11444     SWAP(scoreIsAbsolute, h)
11445     SWAP(timeOdds, h)
11446     SWAP(logo, p)
11447     SWAP(pgnName, p)
11448     SWAP(pvSAN, h)
11449     SWAP(engOptions, p)
11450     SWAP(engInitString, p)
11451     SWAP(computerString, p)
11452     SWAP(features, p)
11453     SWAP(fenOverride, p)
11454     SWAP(NPS, h)
11455     SWAP(accumulateTC, h)
11456     SWAP(drawDepth, h)
11457     SWAP(host, p)
11458     SWAP(pseudo, h)
11459 }
11460
11461 int
11462 GetEngineLine (char *s, int n)
11463 {
11464     int i;
11465     char buf[MSG_SIZ];
11466     extern char *icsNames;
11467     if(!s || !*s) return 0;
11468     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11469     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11470     if(!mnemonic[i]) return 0;
11471     if(n == 11) return 1; // just testing if there was a match
11472     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11473     if(n == 1) SwapEngines(n);
11474     ParseArgsFromString(buf);
11475     if(n == 1) SwapEngines(n);
11476     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11477     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11478         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11479         ParseArgsFromString(buf);
11480     }
11481     return 1;
11482 }
11483
11484 int
11485 SetPlayer (int player, char *p)
11486 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11487     int i;
11488     char buf[MSG_SIZ], *engineName;
11489     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11490     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11491     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11492     if(mnemonic[i]) {
11493         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11494         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11495         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11496         ParseArgsFromString(buf);
11497     } else { // no engine with this nickname is installed!
11498         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11499         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11500         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11501         ModeHighlight();
11502         DisplayError(buf, 0);
11503         return 0;
11504     }
11505     free(engineName);
11506     return i;
11507 }
11508
11509 char *recentEngines;
11510
11511 void
11512 RecentEngineEvent (int nr)
11513 {
11514     int n;
11515 //    SwapEngines(1); // bump first to second
11516 //    ReplaceEngine(&second, 1); // and load it there
11517     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11518     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11519     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11520         ReplaceEngine(&first, 0);
11521         FloatToFront(&appData.recentEngineList, command[n]);
11522         ASSIGN(currentEngine[0], command[n]);
11523     }
11524 }
11525
11526 int
11527 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11528 {   // determine players from game number
11529     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11530
11531     if(appData.tourneyType == 0) {
11532         roundsPerCycle = (nPlayers - 1) | 1;
11533         pairingsPerRound = nPlayers / 2;
11534     } else if(appData.tourneyType > 0) {
11535         roundsPerCycle = nPlayers - appData.tourneyType;
11536         pairingsPerRound = appData.tourneyType;
11537     }
11538     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11539     gamesPerCycle = gamesPerRound * roundsPerCycle;
11540     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11541     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11542     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11543     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11544     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11545     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11546
11547     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11548     if(appData.roundSync) *syncInterval = gamesPerRound;
11549
11550     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11551
11552     if(appData.tourneyType == 0) {
11553         if(curPairing == (nPlayers-1)/2 ) {
11554             *whitePlayer = curRound;
11555             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11556         } else {
11557             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11558             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11559             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11560             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11561         }
11562     } else if(appData.tourneyType > 1) {
11563         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11564         *whitePlayer = curRound + appData.tourneyType;
11565     } else if(appData.tourneyType > 0) {
11566         *whitePlayer = curPairing;
11567         *blackPlayer = curRound + appData.tourneyType;
11568     }
11569
11570     // take care of white/black alternation per round.
11571     // For cycles and games this is already taken care of by default, derived from matchGame!
11572     return curRound & 1;
11573 }
11574
11575 int
11576 NextTourneyGame (int nr, int *swapColors)
11577 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11578     char *p, *q;
11579     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11580     FILE *tf;
11581     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11582     tf = fopen(appData.tourneyFile, "r");
11583     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11584     ParseArgsFromFile(tf); fclose(tf);
11585     InitTimeControls(); // TC might be altered from tourney file
11586
11587     nPlayers = CountPlayers(appData.participants); // count participants
11588     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11589     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11590
11591     if(syncInterval) {
11592         p = q = appData.results;
11593         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11594         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11595             DisplayMessage(_("Waiting for other game(s)"),"");
11596             waitingForGame = TRUE;
11597             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11598             return 0;
11599         }
11600         waitingForGame = FALSE;
11601     }
11602
11603     if(appData.tourneyType < 0) {
11604         if(nr>=0 && !pairingReceived) {
11605             char buf[1<<16];
11606             if(pairing.pr == NoProc) {
11607                 if(!appData.pairingEngine[0]) {
11608                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11609                     return 0;
11610                 }
11611                 StartChessProgram(&pairing); // starts the pairing engine
11612             }
11613             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11614             SendToProgram(buf, &pairing);
11615             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11616             SendToProgram(buf, &pairing);
11617             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11618         }
11619         pairingReceived = 0;                              // ... so we continue here
11620         *swapColors = 0;
11621         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11622         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11623         matchGame = 1; roundNr = nr / syncInterval + 1;
11624     }
11625
11626     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11627
11628     // redefine engines, engine dir, etc.
11629     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11630     if(first.pr == NoProc) {
11631       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11632       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11633     }
11634     if(second.pr == NoProc) {
11635       SwapEngines(1);
11636       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11637       SwapEngines(1);         // and make that valid for second engine by swapping
11638       InitEngine(&second, 1);
11639     }
11640     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11641     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11642     return OK;
11643 }
11644
11645 void
11646 NextMatchGame ()
11647 {   // performs game initialization that does not invoke engines, and then tries to start the game
11648     int res, firstWhite, swapColors = 0;
11649     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11650     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
11651         char buf[MSG_SIZ];
11652         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11653         if(strcmp(buf, currentDebugFile)) { // name has changed
11654             FILE *f = fopen(buf, "w");
11655             if(f) { // if opening the new file failed, just keep using the old one
11656                 ASSIGN(currentDebugFile, buf);
11657                 fclose(debugFP);
11658                 debugFP = f;
11659             }
11660             if(appData.serverFileName) {
11661                 if(serverFP) fclose(serverFP);
11662                 serverFP = fopen(appData.serverFileName, "w");
11663                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11664                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11665             }
11666         }
11667     }
11668     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11669     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11670     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11671     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11672     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11673     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11674     Reset(FALSE, first.pr != NoProc);
11675     res = LoadGameOrPosition(matchGame); // setup game
11676     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11677     if(!res) return; // abort when bad game/pos file
11678     if(appData.epd) {// in EPD mode we make sure first engine is to move
11679         firstWhite = !(forwardMostMove & 1);
11680         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11681         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11682     }
11683     TwoMachinesEvent();
11684 }
11685
11686 void
11687 UserAdjudicationEvent (int result)
11688 {
11689     ChessMove gameResult = GameIsDrawn;
11690
11691     if( result > 0 ) {
11692         gameResult = WhiteWins;
11693     }
11694     else if( result < 0 ) {
11695         gameResult = BlackWins;
11696     }
11697
11698     if( gameMode == TwoMachinesPlay ) {
11699         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11700     }
11701 }
11702
11703
11704 // [HGM] save: calculate checksum of game to make games easily identifiable
11705 int
11706 StringCheckSum (char *s)
11707 {
11708         int i = 0;
11709         if(s==NULL) return 0;
11710         while(*s) i = i*259 + *s++;
11711         return i;
11712 }
11713
11714 int
11715 GameCheckSum ()
11716 {
11717         int i, sum=0;
11718         for(i=backwardMostMove; i<forwardMostMove; i++) {
11719                 sum += pvInfoList[i].depth;
11720                 sum += StringCheckSum(parseList[i]);
11721                 sum += StringCheckSum(commentList[i]);
11722                 sum *= 261;
11723         }
11724         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11725         return sum + StringCheckSum(commentList[i]);
11726 } // end of save patch
11727
11728 void
11729 GameEnds (ChessMove result, char *resultDetails, int whosays)
11730 {
11731     GameMode nextGameMode;
11732     int isIcsGame;
11733     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11734
11735     if(endingGame) return; /* [HGM] crash: forbid recursion */
11736     endingGame = 1;
11737     if(twoBoards) { // [HGM] dual: switch back to one board
11738         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11739         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11740     }
11741     if (appData.debugMode) {
11742       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11743               result, resultDetails ? resultDetails : "(null)", whosays);
11744     }
11745
11746     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11747
11748     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11749
11750     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11751         /* If we are playing on ICS, the server decides when the
11752            game is over, but the engine can offer to draw, claim
11753            a draw, or resign.
11754          */
11755 #if ZIPPY
11756         if (appData.zippyPlay && first.initDone) {
11757             if (result == GameIsDrawn) {
11758                 /* In case draw still needs to be claimed */
11759                 SendToICS(ics_prefix);
11760                 SendToICS("draw\n");
11761             } else if (StrCaseStr(resultDetails, "resign")) {
11762                 SendToICS(ics_prefix);
11763                 SendToICS("resign\n");
11764             }
11765         }
11766 #endif
11767         endingGame = 0; /* [HGM] crash */
11768         return;
11769     }
11770
11771     /* If we're loading the game from a file, stop */
11772     if (whosays == GE_FILE) {
11773       (void) StopLoadGameTimer();
11774       gameFileFP = NULL;
11775     }
11776
11777     /* Cancel draw offers */
11778     first.offeredDraw = second.offeredDraw = 0;
11779
11780     /* If this is an ICS game, only ICS can really say it's done;
11781        if not, anyone can. */
11782     isIcsGame = (gameMode == IcsPlayingWhite ||
11783                  gameMode == IcsPlayingBlack ||
11784                  gameMode == IcsObserving    ||
11785                  gameMode == IcsExamining);
11786
11787     if (!isIcsGame || whosays == GE_ICS) {
11788         /* OK -- not an ICS game, or ICS said it was done */
11789         StopClocks();
11790         if (!isIcsGame && !appData.noChessProgram)
11791           SetUserThinkingEnables();
11792
11793         /* [HGM] if a machine claims the game end we verify this claim */
11794         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11795             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11796                 char claimer;
11797                 ChessMove trueResult = (ChessMove) -1;
11798
11799                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11800                                             first.twoMachinesColor[0] :
11801                                             second.twoMachinesColor[0] ;
11802
11803                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11804                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11805                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11806                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11807                 } else
11808                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11809                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11810                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11811                 } else
11812                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11813                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11814                 }
11815
11816                 // now verify win claims, but not in drop games, as we don't understand those yet
11817                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11818                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11819                     (result == WhiteWins && claimer == 'w' ||
11820                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11821                       if (appData.debugMode) {
11822                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11823                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11824                       }
11825                       if(result != trueResult) {
11826                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11827                               result = claimer == 'w' ? BlackWins : WhiteWins;
11828                               resultDetails = buf;
11829                       }
11830                 } else
11831                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11832                     && (forwardMostMove <= backwardMostMove ||
11833                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11834                         (claimer=='b')==(forwardMostMove&1))
11835                                                                                   ) {
11836                       /* [HGM] verify: draws that were not flagged are false claims */
11837                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11838                       result = claimer == 'w' ? BlackWins : WhiteWins;
11839                       resultDetails = buf;
11840                 }
11841                 /* (Claiming a loss is accepted no questions asked!) */
11842             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11843                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11844                 result = GameUnfinished;
11845                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11846             }
11847             /* [HGM] bare: don't allow bare King to win */
11848             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11849                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11850                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11851                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11852                && result != GameIsDrawn)
11853             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11854                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11855                         int p = (int)boards[forwardMostMove][i][j] - color;
11856                         if(p >= 0 && p <= (int)WhiteKing) k++;
11857                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11858                 }
11859                 if (appData.debugMode) {
11860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11862                 }
11863                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11864                         result = GameIsDrawn;
11865                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11866                         resultDetails = buf;
11867                 }
11868             }
11869         }
11870
11871
11872         if(serverMoves != NULL && !loadFlag) { char c = '=';
11873             if(result==WhiteWins) c = '+';
11874             if(result==BlackWins) c = '-';
11875             if(resultDetails != NULL)
11876                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11877         }
11878         if (resultDetails != NULL) {
11879             gameInfo.result = result;
11880             gameInfo.resultDetails = StrSave(resultDetails);
11881
11882             /* display last move only if game was not loaded from file */
11883             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11884                 DisplayMove(currentMove - 1);
11885
11886             if (forwardMostMove != 0) {
11887                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11888                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11889                                                                 ) {
11890                     if (*appData.saveGameFile != NULLCHAR) {
11891                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11892                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11893                         else
11894                         SaveGameToFile(appData.saveGameFile, TRUE);
11895                     } else if (appData.autoSaveGames) {
11896                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11897                     }
11898                     if (*appData.savePositionFile != NULLCHAR) {
11899                         SavePositionToFile(appData.savePositionFile);
11900                     }
11901                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11902                 }
11903             }
11904
11905             /* Tell program how game ended in case it is learning */
11906             /* [HGM] Moved this to after saving the PGN, just in case */
11907             /* engine died and we got here through time loss. In that */
11908             /* case we will get a fatal error writing the pipe, which */
11909             /* would otherwise lose us the PGN.                       */
11910             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11911             /* output during GameEnds should never be fatal anymore   */
11912             if (gameMode == MachinePlaysWhite ||
11913                 gameMode == MachinePlaysBlack ||
11914                 gameMode == TwoMachinesPlay ||
11915                 gameMode == IcsPlayingWhite ||
11916                 gameMode == IcsPlayingBlack ||
11917                 gameMode == BeginningOfGame) {
11918                 char buf[MSG_SIZ];
11919                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11920                         resultDetails);
11921                 if (first.pr != NoProc) {
11922                     SendToProgram(buf, &first);
11923                 }
11924                 if (second.pr != NoProc &&
11925                     gameMode == TwoMachinesPlay) {
11926                     SendToProgram(buf, &second);
11927                 }
11928             }
11929         }
11930
11931         if (appData.icsActive) {
11932             if (appData.quietPlay &&
11933                 (gameMode == IcsPlayingWhite ||
11934                  gameMode == IcsPlayingBlack)) {
11935                 SendToICS(ics_prefix);
11936                 SendToICS("set shout 1\n");
11937             }
11938             nextGameMode = IcsIdle;
11939             ics_user_moved = FALSE;
11940             /* clean up premove.  It's ugly when the game has ended and the
11941              * premove highlights are still on the board.
11942              */
11943             if (gotPremove) {
11944               gotPremove = FALSE;
11945               ClearPremoveHighlights();
11946               DrawPosition(FALSE, boards[currentMove]);
11947             }
11948             if (whosays == GE_ICS) {
11949                 switch (result) {
11950                 case WhiteWins:
11951                     if (gameMode == IcsPlayingWhite)
11952                         PlayIcsWinSound();
11953                     else if(gameMode == IcsPlayingBlack)
11954                         PlayIcsLossSound();
11955                     break;
11956                 case BlackWins:
11957                     if (gameMode == IcsPlayingBlack)
11958                         PlayIcsWinSound();
11959                     else if(gameMode == IcsPlayingWhite)
11960                         PlayIcsLossSound();
11961                     break;
11962                 case GameIsDrawn:
11963                     PlayIcsDrawSound();
11964                     break;
11965                 default:
11966                     PlayIcsUnfinishedSound();
11967                 }
11968             }
11969             if(appData.quitNext) { ExitEvent(0); return; }
11970         } else if (gameMode == EditGame ||
11971                    gameMode == PlayFromGameFile ||
11972                    gameMode == AnalyzeMode ||
11973                    gameMode == AnalyzeFile) {
11974             nextGameMode = gameMode;
11975         } else {
11976             nextGameMode = EndOfGame;
11977         }
11978         pausing = FALSE;
11979         ModeHighlight();
11980     } else {
11981         nextGameMode = gameMode;
11982     }
11983
11984     if (appData.noChessProgram) {
11985         gameMode = nextGameMode;
11986         ModeHighlight();
11987         endingGame = 0; /* [HGM] crash */
11988         return;
11989     }
11990
11991     if (first.reuse) {
11992         /* Put first chess program into idle state */
11993         if (first.pr != NoProc &&
11994             (gameMode == MachinePlaysWhite ||
11995              gameMode == MachinePlaysBlack ||
11996              gameMode == TwoMachinesPlay ||
11997              gameMode == IcsPlayingWhite ||
11998              gameMode == IcsPlayingBlack ||
11999              gameMode == BeginningOfGame)) {
12000             SendToProgram("force\n", &first);
12001             if (first.usePing) {
12002               char buf[MSG_SIZ];
12003               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
12004               SendToProgram(buf, &first);
12005             }
12006         }
12007     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12008         /* Kill off first chess program */
12009         if (first.isr != NULL)
12010           RemoveInputSource(first.isr);
12011         first.isr = NULL;
12012
12013         if (first.pr != NoProc) {
12014             ExitAnalyzeMode();
12015             DoSleep( appData.delayBeforeQuit );
12016             SendToProgram("quit\n", &first);
12017             DestroyChildProcess(first.pr, 4 + first.useSigterm);
12018             first.reload = TRUE;
12019         }
12020         first.pr = NoProc;
12021     }
12022     if (second.reuse) {
12023         /* Put second chess program into idle state */
12024         if (second.pr != NoProc &&
12025             gameMode == TwoMachinesPlay) {
12026             SendToProgram("force\n", &second);
12027             if (second.usePing) {
12028               char buf[MSG_SIZ];
12029               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12030               SendToProgram(buf, &second);
12031             }
12032         }
12033     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12034         /* Kill off second chess program */
12035         if (second.isr != NULL)
12036           RemoveInputSource(second.isr);
12037         second.isr = NULL;
12038
12039         if (second.pr != NoProc) {
12040             DoSleep( appData.delayBeforeQuit );
12041             SendToProgram("quit\n", &second);
12042             DestroyChildProcess(second.pr, 4 + second.useSigterm);
12043             second.reload = TRUE;
12044         }
12045         second.pr = NoProc;
12046     }
12047
12048     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12049         char resChar = '=';
12050         switch (result) {
12051         case WhiteWins:
12052           resChar = '+';
12053           if (first.twoMachinesColor[0] == 'w') {
12054             first.matchWins++;
12055           } else {
12056             second.matchWins++;
12057           }
12058           break;
12059         case BlackWins:
12060           resChar = '-';
12061           if (first.twoMachinesColor[0] == 'b') {
12062             first.matchWins++;
12063           } else {
12064             second.matchWins++;
12065           }
12066           break;
12067         case GameUnfinished:
12068           resChar = ' ';
12069         default:
12070           break;
12071         }
12072
12073         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12074         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12075             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12076             ReserveGame(nextGame, resChar); // sets nextGame
12077             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12078             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12079         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12080
12081         if (nextGame <= appData.matchGames && !abortMatch) {
12082             gameMode = nextGameMode;
12083             matchGame = nextGame; // this will be overruled in tourney mode!
12084             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12085             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12086             endingGame = 0; /* [HGM] crash */
12087             return;
12088         } else {
12089             gameMode = nextGameMode;
12090             if(appData.epd) {
12091                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12092                 OutputKibitz(2, buf);
12093                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12094                 OutputKibitz(2, buf);
12095                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12096                 if(second.matchWins) OutputKibitz(2, buf);
12097                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12098                 OutputKibitz(2, buf);
12099             }
12100             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12101                      first.tidy, second.tidy,
12102                      first.matchWins, second.matchWins,
12103                      appData.matchGames - (first.matchWins + second.matchWins));
12104             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12105             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12106             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12107             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12108                 first.twoMachinesColor = "black\n";
12109                 second.twoMachinesColor = "white\n";
12110             } else {
12111                 first.twoMachinesColor = "white\n";
12112                 second.twoMachinesColor = "black\n";
12113             }
12114         }
12115     }
12116     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12117         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12118       ExitAnalyzeMode();
12119     gameMode = nextGameMode;
12120     ModeHighlight();
12121     endingGame = 0;  /* [HGM] crash */
12122     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12123         if(matchMode == TRUE) { // match through command line: exit with or without popup
12124             if(ranking) {
12125                 ToNrEvent(forwardMostMove);
12126                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12127                 else ExitEvent(0);
12128             } else DisplayFatalError(buf, 0, 0);
12129         } else { // match through menu; just stop, with or without popup
12130             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12131             ModeHighlight();
12132             if(ranking){
12133                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12134             } else DisplayNote(buf);
12135       }
12136       if(ranking) free(ranking);
12137     }
12138 }
12139
12140 /* Assumes program was just initialized (initString sent).
12141    Leaves program in force mode. */
12142 void
12143 FeedMovesToProgram (ChessProgramState *cps, int upto)
12144 {
12145     int i;
12146
12147     if (appData.debugMode)
12148       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12149               startedFromSetupPosition ? "position and " : "",
12150               backwardMostMove, upto, cps->which);
12151     if(currentlyInitializedVariant != gameInfo.variant) {
12152       char buf[MSG_SIZ];
12153         // [HGM] variantswitch: make engine aware of new variant
12154         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12155                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12156                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12157         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12158         SendToProgram(buf, cps);
12159         currentlyInitializedVariant = gameInfo.variant;
12160     }
12161     SendToProgram("force\n", cps);
12162     if (startedFromSetupPosition) {
12163         SendBoard(cps, backwardMostMove);
12164     if (appData.debugMode) {
12165         fprintf(debugFP, "feedMoves\n");
12166     }
12167     }
12168     for (i = backwardMostMove; i < upto; i++) {
12169         SendMoveToProgram(i, cps);
12170     }
12171 }
12172
12173
12174 int
12175 ResurrectChessProgram ()
12176 {
12177      /* The chess program may have exited.
12178         If so, restart it and feed it all the moves made so far. */
12179     static int doInit = 0;
12180
12181     if (appData.noChessProgram) return 1;
12182
12183     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12184         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12185         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12186         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12187     } else {
12188         if (first.pr != NoProc) return 1;
12189         StartChessProgram(&first);
12190     }
12191     InitChessProgram(&first, FALSE);
12192     FeedMovesToProgram(&first, currentMove);
12193
12194     if (!first.sendTime) {
12195         /* can't tell gnuchess what its clock should read,
12196            so we bow to its notion. */
12197         ResetClocks();
12198         timeRemaining[0][currentMove] = whiteTimeRemaining;
12199         timeRemaining[1][currentMove] = blackTimeRemaining;
12200     }
12201
12202     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12203                 appData.icsEngineAnalyze) && first.analysisSupport) {
12204       SendToProgram("analyze\n", &first);
12205       first.analyzing = TRUE;
12206     }
12207     return 1;
12208 }
12209
12210 /*
12211  * Button procedures
12212  */
12213 void
12214 Reset (int redraw, int init)
12215 {
12216     int i;
12217
12218     if (appData.debugMode) {
12219         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12220                 redraw, init, gameMode);
12221     }
12222     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12223     deadRanks = 0; // assume entire board is used
12224     handSize = 0;
12225     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12226     CleanupTail(); // [HGM] vari: delete any stored variations
12227     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12228     pausing = pauseExamInvalid = FALSE;
12229     startedFromSetupPosition = blackPlaysFirst = FALSE;
12230     firstMove = TRUE;
12231     whiteFlag = blackFlag = FALSE;
12232     userOfferedDraw = FALSE;
12233     hintRequested = bookRequested = FALSE;
12234     first.maybeThinking = FALSE;
12235     second.maybeThinking = FALSE;
12236     first.bookSuspend = FALSE; // [HGM] book
12237     second.bookSuspend = FALSE;
12238     thinkOutput[0] = NULLCHAR;
12239     lastHint[0] = NULLCHAR;
12240     ClearGameInfo(&gameInfo);
12241     gameInfo.variant = StringToVariant(appData.variant);
12242     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12243         gameInfo.variant = VariantUnknown;
12244         strncpy(engineVariant, appData.variant, MSG_SIZ);
12245     }
12246     ics_user_moved = ics_clock_paused = FALSE;
12247     ics_getting_history = H_FALSE;
12248     ics_gamenum = -1;
12249     white_holding[0] = black_holding[0] = NULLCHAR;
12250     ClearProgramStats();
12251     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12252
12253     ResetFrontEnd();
12254     ClearHighlights();
12255     flipView = appData.flipView;
12256     ClearPremoveHighlights();
12257     gotPremove = FALSE;
12258     alarmSounded = FALSE;
12259     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12260
12261     GameEnds(EndOfFile, NULL, GE_PLAYER);
12262     if(appData.serverMovesName != NULL) {
12263         /* [HGM] prepare to make moves file for broadcasting */
12264         clock_t t = clock();
12265         if(serverMoves != NULL) fclose(serverMoves);
12266         serverMoves = fopen(appData.serverMovesName, "r");
12267         if(serverMoves != NULL) {
12268             fclose(serverMoves);
12269             /* delay 15 sec before overwriting, so all clients can see end */
12270             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12271         }
12272         serverMoves = fopen(appData.serverMovesName, "w");
12273     }
12274
12275     ExitAnalyzeMode();
12276     gameMode = BeginningOfGame;
12277     ModeHighlight();
12278     if(appData.icsActive) gameInfo.variant = VariantNormal;
12279     currentMove = forwardMostMove = backwardMostMove = 0;
12280     MarkTargetSquares(1);
12281     InitPosition(redraw);
12282     for (i = 0; i < MAX_MOVES; i++) {
12283         if (commentList[i] != NULL) {
12284             free(commentList[i]);
12285             commentList[i] = NULL;
12286         }
12287     }
12288     ResetClocks();
12289     timeRemaining[0][0] = whiteTimeRemaining;
12290     timeRemaining[1][0] = blackTimeRemaining;
12291
12292     if (first.pr == NoProc) {
12293         StartChessProgram(&first);
12294     }
12295     if (init) {
12296             InitChessProgram(&first, startedFromSetupPosition);
12297     }
12298     DisplayTitle("");
12299     DisplayMessage("", "");
12300     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12301     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12302     ClearMap();        // [HGM] exclude: invalidate map
12303 }
12304
12305 void
12306 AutoPlayGameLoop ()
12307 {
12308     for (;;) {
12309         if (!AutoPlayOneMove())
12310           return;
12311         if (matchMode || appData.timeDelay == 0)
12312           continue;
12313         if (appData.timeDelay < 0)
12314           return;
12315         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12316         break;
12317     }
12318 }
12319
12320 void
12321 AnalyzeNextGame()
12322 {
12323     ReloadGame(1); // next game
12324 }
12325
12326 int
12327 AutoPlayOneMove ()
12328 {
12329     int fromX, fromY, toX, toY;
12330
12331     if (appData.debugMode) {
12332       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12333     }
12334
12335     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12336       return FALSE;
12337
12338     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12339       pvInfoList[currentMove].depth = programStats.depth;
12340       pvInfoList[currentMove].score = programStats.score;
12341       pvInfoList[currentMove].time  = 0;
12342       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12343       else { // append analysis of final position as comment
12344         char buf[MSG_SIZ];
12345         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12346         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12347       }
12348       programStats.depth = 0;
12349     }
12350
12351     if (currentMove >= forwardMostMove) {
12352       if(gameMode == AnalyzeFile) {
12353           if(appData.loadGameIndex == -1) {
12354             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12355           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12356           } else {
12357           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12358         }
12359       }
12360 //      gameMode = EndOfGame;
12361 //      ModeHighlight();
12362
12363       /* [AS] Clear current move marker at the end of a game */
12364       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12365
12366       return FALSE;
12367     }
12368
12369     toX = moveList[currentMove][2] - AAA;
12370     toY = moveList[currentMove][3] - ONE;
12371
12372     if (moveList[currentMove][1] == '@') {
12373         if (appData.highlightLastMove) {
12374             SetHighlights(-1, -1, toX, toY);
12375         }
12376     } else {
12377         fromX = moveList[currentMove][0] - AAA;
12378         fromY = moveList[currentMove][1] - ONE;
12379
12380         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12381
12382         if(moveList[currentMove][4] == ';') { // multi-leg
12383             killX = moveList[currentMove][5] - AAA;
12384             killY = moveList[currentMove][6] - ONE;
12385         }
12386         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12387         killX = killY = -1;
12388
12389         if (appData.highlightLastMove) {
12390             SetHighlights(fromX, fromY, toX, toY);
12391         }
12392     }
12393     DisplayMove(currentMove);
12394     SendMoveToProgram(currentMove++, &first);
12395     DisplayBothClocks();
12396     DrawPosition(FALSE, boards[currentMove]);
12397     // [HGM] PV info: always display, routine tests if empty
12398     DisplayComment(currentMove - 1, commentList[currentMove]);
12399     return TRUE;
12400 }
12401
12402
12403 int
12404 LoadGameOneMove (ChessMove readAhead)
12405 {
12406     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12407     char promoChar = NULLCHAR;
12408     ChessMove moveType;
12409     char move[MSG_SIZ];
12410     char *p, *q;
12411
12412     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12413         gameMode != AnalyzeMode && gameMode != Training) {
12414         gameFileFP = NULL;
12415         return FALSE;
12416     }
12417
12418     yyboardindex = forwardMostMove;
12419     if (readAhead != EndOfFile) {
12420       moveType = readAhead;
12421     } else {
12422       if (gameFileFP == NULL)
12423           return FALSE;
12424       moveType = (ChessMove) Myylex();
12425     }
12426
12427     done = FALSE;
12428     switch (moveType) {
12429       case Comment:
12430         if (appData.debugMode)
12431           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12432         p = yy_text;
12433
12434         /* append the comment but don't display it */
12435         AppendComment(currentMove, p, FALSE);
12436         return TRUE;
12437
12438       case WhiteCapturesEnPassant:
12439       case BlackCapturesEnPassant:
12440       case WhitePromotion:
12441       case BlackPromotion:
12442       case WhiteNonPromotion:
12443       case BlackNonPromotion:
12444       case NormalMove:
12445       case FirstLeg:
12446       case WhiteKingSideCastle:
12447       case WhiteQueenSideCastle:
12448       case BlackKingSideCastle:
12449       case BlackQueenSideCastle:
12450       case WhiteKingSideCastleWild:
12451       case WhiteQueenSideCastleWild:
12452       case BlackKingSideCastleWild:
12453       case BlackQueenSideCastleWild:
12454       /* PUSH Fabien */
12455       case WhiteHSideCastleFR:
12456       case WhiteASideCastleFR:
12457       case BlackHSideCastleFR:
12458       case BlackASideCastleFR:
12459       /* POP Fabien */
12460         if (appData.debugMode)
12461           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12462         fromX = currentMoveString[0] - AAA;
12463         fromY = currentMoveString[1] - ONE;
12464         toX = currentMoveString[2] - AAA;
12465         toY = currentMoveString[3] - ONE;
12466         promoChar = currentMoveString[4];
12467         if(promoChar == ';') promoChar = currentMoveString[7];
12468         break;
12469
12470       case WhiteDrop:
12471       case BlackDrop:
12472         if (appData.debugMode)
12473           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12474         fromX = moveType == WhiteDrop ?
12475           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12476         (int) CharToPiece(ToLower(currentMoveString[0]));
12477         fromY = DROP_RANK;
12478         toX = currentMoveString[2] - AAA;
12479         toY = currentMoveString[3] - ONE;
12480         break;
12481
12482       case WhiteWins:
12483       case BlackWins:
12484       case GameIsDrawn:
12485       case GameUnfinished:
12486         if (appData.debugMode)
12487           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12488         p = strchr(yy_text, '{');
12489         if (p == NULL) p = strchr(yy_text, '(');
12490         if (p == NULL) {
12491             p = yy_text;
12492             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12493         } else {
12494             q = strchr(p, *p == '{' ? '}' : ')');
12495             if (q != NULL) *q = NULLCHAR;
12496             p++;
12497         }
12498         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12499         GameEnds(moveType, p, GE_FILE);
12500         done = TRUE;
12501         if (cmailMsgLoaded) {
12502             ClearHighlights();
12503             flipView = WhiteOnMove(currentMove);
12504             if (moveType == GameUnfinished) flipView = !flipView;
12505             if (appData.debugMode)
12506               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12507         }
12508         break;
12509
12510       case EndOfFile:
12511         if (appData.debugMode)
12512           fprintf(debugFP, "Parser hit end of file\n");
12513         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12514           case MT_NONE:
12515           case MT_CHECK:
12516             break;
12517           case MT_CHECKMATE:
12518           case MT_STAINMATE:
12519             if (WhiteOnMove(currentMove)) {
12520                 GameEnds(BlackWins, "Black mates", GE_FILE);
12521             } else {
12522                 GameEnds(WhiteWins, "White mates", GE_FILE);
12523             }
12524             break;
12525           case MT_STALEMATE:
12526             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12527             break;
12528         }
12529         done = TRUE;
12530         break;
12531
12532       case MoveNumberOne:
12533         if (lastLoadGameStart == GNUChessGame) {
12534             /* GNUChessGames have numbers, but they aren't move numbers */
12535             if (appData.debugMode)
12536               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12537                       yy_text, (int) moveType);
12538             return LoadGameOneMove(EndOfFile); /* tail recursion */
12539         }
12540         /* else fall thru */
12541
12542       case XBoardGame:
12543       case GNUChessGame:
12544       case PGNTag:
12545         /* Reached start of next game in file */
12546         if (appData.debugMode)
12547           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12548         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12549           case MT_NONE:
12550           case MT_CHECK:
12551             break;
12552           case MT_CHECKMATE:
12553           case MT_STAINMATE:
12554             if (WhiteOnMove(currentMove)) {
12555                 GameEnds(BlackWins, "Black mates", GE_FILE);
12556             } else {
12557                 GameEnds(WhiteWins, "White mates", GE_FILE);
12558             }
12559             break;
12560           case MT_STALEMATE:
12561             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12562             break;
12563         }
12564         done = TRUE;
12565         break;
12566
12567       case PositionDiagram:     /* should not happen; ignore */
12568       case ElapsedTime:         /* ignore */
12569       case NAG:                 /* ignore */
12570         if (appData.debugMode)
12571           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12572                   yy_text, (int) moveType);
12573         return LoadGameOneMove(EndOfFile); /* tail recursion */
12574
12575       case IllegalMove:
12576         if (appData.testLegality) {
12577             if (appData.debugMode)
12578               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12579             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12580                     (forwardMostMove / 2) + 1,
12581                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12582             DisplayError(move, 0);
12583             done = TRUE;
12584         } else {
12585             if (appData.debugMode)
12586               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12587                       yy_text, currentMoveString);
12588             if(currentMoveString[1] == '@') {
12589                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12590                 fromY = DROP_RANK;
12591             } else {
12592                 fromX = currentMoveString[0] - AAA;
12593                 fromY = currentMoveString[1] - ONE;
12594             }
12595             toX = currentMoveString[2] - AAA;
12596             toY = currentMoveString[3] - ONE;
12597             promoChar = currentMoveString[4];
12598         }
12599         break;
12600
12601       case AmbiguousMove:
12602         if (appData.debugMode)
12603           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12604         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12605                 (forwardMostMove / 2) + 1,
12606                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12607         DisplayError(move, 0);
12608         done = TRUE;
12609         break;
12610
12611       default:
12612       case ImpossibleMove:
12613         if (appData.debugMode)
12614           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12615         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12616                 (forwardMostMove / 2) + 1,
12617                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12618         DisplayError(move, 0);
12619         done = TRUE;
12620         break;
12621     }
12622
12623     if (done) {
12624         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12625             DrawPosition(FALSE, boards[currentMove]);
12626             DisplayBothClocks();
12627             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12628               DisplayComment(currentMove - 1, commentList[currentMove]);
12629         }
12630         (void) StopLoadGameTimer();
12631         gameFileFP = NULL;
12632         cmailOldMove = forwardMostMove;
12633         return FALSE;
12634     } else {
12635         /* currentMoveString is set as a side-effect of yylex */
12636
12637         thinkOutput[0] = NULLCHAR;
12638         MakeMove(fromX, fromY, toX, toY, promoChar);
12639         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12640         currentMove = forwardMostMove;
12641         return TRUE;
12642     }
12643 }
12644
12645 /* Load the nth game from the given file */
12646 int
12647 LoadGameFromFile (char *filename, int n, char *title, int useList)
12648 {
12649     FILE *f;
12650     char buf[MSG_SIZ];
12651
12652     if (strcmp(filename, "-") == 0) {
12653         f = stdin;
12654         title = "stdin";
12655     } else {
12656         f = fopen(filename, "rb");
12657         if (f == NULL) {
12658           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12659             DisplayError(buf, errno);
12660             return FALSE;
12661         }
12662     }
12663     if (fseek(f, 0, 0) == -1) {
12664         /* f is not seekable; probably a pipe */
12665         useList = FALSE;
12666     }
12667     if (useList && n == 0) {
12668         int error = GameListBuild(f);
12669         if (error) {
12670             DisplayError(_("Cannot build game list"), error);
12671         } else if (!ListEmpty(&gameList) &&
12672                    ((ListGame *) gameList.tailPred)->number > 1) {
12673             GameListPopUp(f, title);
12674             return TRUE;
12675         }
12676         GameListDestroy();
12677         n = 1;
12678     }
12679     if (n == 0) n = 1;
12680     return LoadGame(f, n, title, FALSE);
12681 }
12682
12683
12684 void
12685 MakeRegisteredMove ()
12686 {
12687     int fromX, fromY, toX, toY;
12688     char promoChar;
12689     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12691           case CMAIL_MOVE:
12692           case CMAIL_DRAW:
12693             if (appData.debugMode)
12694               fprintf(debugFP, "Restoring %s for game %d\n",
12695                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12696
12697             thinkOutput[0] = NULLCHAR;
12698             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12699             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12700             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12701             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12702             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12703             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12704             MakeMove(fromX, fromY, toX, toY, promoChar);
12705             ShowMove(fromX, fromY, toX, toY);
12706
12707             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12708               case MT_NONE:
12709               case MT_CHECK:
12710                 break;
12711
12712               case MT_CHECKMATE:
12713               case MT_STAINMATE:
12714                 if (WhiteOnMove(currentMove)) {
12715                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12716                 } else {
12717                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12718                 }
12719                 break;
12720
12721               case MT_STALEMATE:
12722                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12723                 break;
12724             }
12725
12726             break;
12727
12728           case CMAIL_RESIGN:
12729             if (WhiteOnMove(currentMove)) {
12730                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12731             } else {
12732                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12733             }
12734             break;
12735
12736           case CMAIL_ACCEPT:
12737             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12738             break;
12739
12740           default:
12741             break;
12742         }
12743     }
12744
12745     return;
12746 }
12747
12748 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12749 int
12750 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12751 {
12752     int retVal;
12753
12754     if (gameNumber > nCmailGames) {
12755         DisplayError(_("No more games in this message"), 0);
12756         return FALSE;
12757     }
12758     if (f == lastLoadGameFP) {
12759         int offset = gameNumber - lastLoadGameNumber;
12760         if (offset == 0) {
12761             cmailMsg[0] = NULLCHAR;
12762             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12763                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12764                 nCmailMovesRegistered--;
12765             }
12766             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12767             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12768                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12769             }
12770         } else {
12771             if (! RegisterMove()) return FALSE;
12772         }
12773     }
12774
12775     retVal = LoadGame(f, gameNumber, title, useList);
12776
12777     /* Make move registered during previous look at this game, if any */
12778     MakeRegisteredMove();
12779
12780     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12781         commentList[currentMove]
12782           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12783         DisplayComment(currentMove - 1, commentList[currentMove]);
12784     }
12785
12786     return retVal;
12787 }
12788
12789 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12790 int
12791 ReloadGame (int offset)
12792 {
12793     int gameNumber = lastLoadGameNumber + offset;
12794     if (lastLoadGameFP == NULL) {
12795         DisplayError(_("No game has been loaded yet"), 0);
12796         return FALSE;
12797     }
12798     if (gameNumber <= 0) {
12799         DisplayError(_("Can't back up any further"), 0);
12800         return FALSE;
12801     }
12802     if (cmailMsgLoaded) {
12803         return CmailLoadGame(lastLoadGameFP, gameNumber,
12804                              lastLoadGameTitle, lastLoadGameUseList);
12805     } else {
12806         return LoadGame(lastLoadGameFP, gameNumber,
12807                         lastLoadGameTitle, lastLoadGameUseList);
12808     }
12809 }
12810
12811 int keys[EmptySquare+1];
12812
12813 int
12814 PositionMatches (Board b1, Board b2)
12815 {
12816     int r, f, sum=0;
12817     switch(appData.searchMode) {
12818         case 1: return CompareWithRights(b1, b2);
12819         case 2:
12820             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12821                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12822             }
12823             return TRUE;
12824         case 3:
12825             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12826               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12827                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12828             }
12829             return sum==0;
12830         case 4:
12831             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12832                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12833             }
12834             return sum==0;
12835     }
12836     return TRUE;
12837 }
12838
12839 #define Q_PROMO  4
12840 #define Q_EP     3
12841 #define Q_BCASTL 2
12842 #define Q_WCASTL 1
12843
12844 int pieceList[256], quickBoard[256];
12845 ChessSquare pieceType[256] = { EmptySquare };
12846 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12847 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12848 int soughtTotal, turn;
12849 Boolean epOK, flipSearch;
12850
12851 typedef struct {
12852     unsigned char piece, to;
12853 } Move;
12854
12855 #define DSIZE (250000)
12856
12857 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12858 Move *moveDatabase = initialSpace;
12859 unsigned int movePtr, dataSize = DSIZE;
12860
12861 int
12862 MakePieceList (Board board, int *counts)
12863 {
12864     int r, f, n=Q_PROMO, total=0;
12865     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12866     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12867         int sq = f + (r<<4);
12868         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12869             quickBoard[sq] = ++n;
12870             pieceList[n] = sq;
12871             pieceType[n] = board[r][f];
12872             counts[board[r][f]]++;
12873             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12874             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12875             total++;
12876         }
12877     }
12878     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12879     return total;
12880 }
12881
12882 void
12883 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12884 {
12885     int sq = fromX + (fromY<<4);
12886     int piece = quickBoard[sq], rook;
12887     quickBoard[sq] = 0;
12888     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12889     if(piece == pieceList[1] && fromY == toY) {
12890       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12891         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12892         moveDatabase[movePtr++].piece = Q_WCASTL;
12893         quickBoard[sq] = piece;
12894         piece = quickBoard[from]; quickBoard[from] = 0;
12895         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12896       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12897         quickBoard[sq] = 0; // remove Rook
12898         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12899         moveDatabase[movePtr++].piece = Q_WCASTL;
12900         quickBoard[sq] = pieceList[1]; // put King
12901         piece = rook;
12902         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12903       }
12904     } else
12905     if(piece == pieceList[2] && fromY == toY) {
12906       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12907         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12908         moveDatabase[movePtr++].piece = Q_BCASTL;
12909         quickBoard[sq] = piece;
12910         piece = quickBoard[from]; quickBoard[from] = 0;
12911         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12912       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12913         quickBoard[sq] = 0; // remove Rook
12914         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12915         moveDatabase[movePtr++].piece = Q_BCASTL;
12916         quickBoard[sq] = pieceList[2]; // put King
12917         piece = rook;
12918         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12919       }
12920     } else
12921     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12922         quickBoard[(fromY<<4)+toX] = 0;
12923         moveDatabase[movePtr].piece = Q_EP;
12924         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12925         moveDatabase[movePtr].to = sq;
12926     } else
12927     if(promoPiece != pieceType[piece]) {
12928         moveDatabase[movePtr++].piece = Q_PROMO;
12929         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12930     }
12931     moveDatabase[movePtr].piece = piece;
12932     quickBoard[sq] = piece;
12933     movePtr++;
12934 }
12935
12936 int
12937 PackGame (Board board)
12938 {
12939     Move *newSpace = NULL;
12940     moveDatabase[movePtr].piece = 0; // terminate previous game
12941     if(movePtr > dataSize) {
12942         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12943         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12944         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12945         if(newSpace) {
12946             int i;
12947             Move *p = moveDatabase, *q = newSpace;
12948             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12949             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12950             moveDatabase = newSpace;
12951         } else { // calloc failed, we must be out of memory. Too bad...
12952             dataSize = 0; // prevent calloc events for all subsequent games
12953             return 0;     // and signal this one isn't cached
12954         }
12955     }
12956     movePtr++;
12957     MakePieceList(board, counts);
12958     return movePtr;
12959 }
12960
12961 int
12962 QuickCompare (Board board, int *minCounts, int *maxCounts)
12963 {   // compare according to search mode
12964     int r, f;
12965     switch(appData.searchMode)
12966     {
12967       case 1: // exact position match
12968         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12969         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12970             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12971         }
12972         break;
12973       case 2: // can have extra material on empty squares
12974         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12975             if(board[r][f] == EmptySquare) continue;
12976             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12977         }
12978         break;
12979       case 3: // material with exact Pawn structure
12980         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12981             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12982             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12983         } // fall through to material comparison
12984       case 4: // exact material
12985         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12986         break;
12987       case 6: // material range with given imbalance
12988         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12989         // fall through to range comparison
12990       case 5: // material range
12991         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12992     }
12993     return TRUE;
12994 }
12995
12996 int
12997 QuickScan (Board board, Move *move)
12998 {   // reconstruct game,and compare all positions in it
12999     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
13000     do {
13001         int piece = move->piece;
13002         int to = move->to, from = pieceList[piece];
13003         if(found < 0) { // if already found just scan to game end for final piece count
13004           if(QuickCompare(soughtBoard, minSought, maxSought) ||
13005            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
13006            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
13007                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
13008             ) {
13009             static int lastCounts[EmptySquare+1];
13010             int i;
13011             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
13012             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
13013           } else stretch = 0;
13014           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13015           if(found >= 0 && !appData.minPieces) return found;
13016         }
13017         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13018           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13019           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13020             piece = (++move)->piece;
13021             from = pieceList[piece];
13022             counts[pieceType[piece]]--;
13023             pieceType[piece] = (ChessSquare) move->to;
13024             counts[move->to]++;
13025           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13026             counts[pieceType[quickBoard[to]]]--;
13027             quickBoard[to] = 0; total--;
13028             move++;
13029             continue;
13030           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13031             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13032             from  = pieceList[piece]; // so this must be King
13033             quickBoard[from] = 0;
13034             pieceList[piece] = to;
13035             from = pieceList[(++move)->piece]; // for FRC this has to be done here
13036             quickBoard[from] = 0; // rook
13037             quickBoard[to] = piece;
13038             to = move->to; piece = move->piece;
13039             goto aftercastle;
13040           }
13041         }
13042         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13043         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13044         quickBoard[from] = 0;
13045       aftercastle:
13046         quickBoard[to] = piece;
13047         pieceList[piece] = to;
13048         cnt++; turn ^= 3;
13049         move++;
13050     } while(1);
13051 }
13052
13053 void
13054 InitSearch ()
13055 {
13056     int r, f;
13057     flipSearch = FALSE;
13058     CopyBoard(soughtBoard, boards[currentMove]);
13059     soughtTotal = MakePieceList(soughtBoard, maxSought);
13060     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13061     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13062     CopyBoard(reverseBoard, boards[currentMove]);
13063     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13064         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13065         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13066         reverseBoard[r][f] = piece;
13067     }
13068     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13069     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13070     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13071                  || (boards[currentMove][CASTLING][2] == NoRights ||
13072                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13073                  && (boards[currentMove][CASTLING][5] == NoRights ||
13074                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13075       ) {
13076         flipSearch = TRUE;
13077         CopyBoard(flipBoard, soughtBoard);
13078         CopyBoard(rotateBoard, reverseBoard);
13079         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13080             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13081             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13082         }
13083     }
13084     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13085     if(appData.searchMode >= 5) {
13086         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13087         MakePieceList(soughtBoard, minSought);
13088         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13089     }
13090     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13091         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13092 }
13093
13094 GameInfo dummyInfo;
13095 static int creatingBook;
13096
13097 int
13098 GameContainsPosition (FILE *f, ListGame *lg)
13099 {
13100     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13101     int fromX, fromY, toX, toY;
13102     char promoChar;
13103     static int initDone=FALSE;
13104
13105     // weed out games based on numerical tag comparison
13106     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13107     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13108     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13109     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13110     if(!initDone) {
13111         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13112         initDone = TRUE;
13113     }
13114     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13115     else CopyBoard(boards[scratch], initialPosition); // default start position
13116     if(lg->moves) {
13117         turn = btm + 1;
13118         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13119         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13120     }
13121     if(btm) plyNr++;
13122     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13123     fseek(f, lg->offset, 0);
13124     yynewfile(f);
13125     while(1) {
13126         yyboardindex = scratch;
13127         quickFlag = plyNr+1;
13128         next = Myylex();
13129         quickFlag = 0;
13130         switch(next) {
13131             case PGNTag:
13132                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13133             default:
13134                 continue;
13135
13136             case XBoardGame:
13137             case GNUChessGame:
13138                 if(plyNr) return -1; // after we have seen moves, this is for new game
13139               continue;
13140
13141             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13142             case ImpossibleMove:
13143             case WhiteWins: // game ends here with these four
13144             case BlackWins:
13145             case GameIsDrawn:
13146             case GameUnfinished:
13147                 return -1;
13148
13149             case IllegalMove:
13150                 if(appData.testLegality) return -1;
13151             case WhiteCapturesEnPassant:
13152             case BlackCapturesEnPassant:
13153             case WhitePromotion:
13154             case BlackPromotion:
13155             case WhiteNonPromotion:
13156             case BlackNonPromotion:
13157             case NormalMove:
13158             case FirstLeg:
13159             case WhiteKingSideCastle:
13160             case WhiteQueenSideCastle:
13161             case BlackKingSideCastle:
13162             case BlackQueenSideCastle:
13163             case WhiteKingSideCastleWild:
13164             case WhiteQueenSideCastleWild:
13165             case BlackKingSideCastleWild:
13166             case BlackQueenSideCastleWild:
13167             case WhiteHSideCastleFR:
13168             case WhiteASideCastleFR:
13169             case BlackHSideCastleFR:
13170             case BlackASideCastleFR:
13171                 fromX = currentMoveString[0] - AAA;
13172                 fromY = currentMoveString[1] - ONE;
13173                 toX = currentMoveString[2] - AAA;
13174                 toY = currentMoveString[3] - ONE;
13175                 promoChar = currentMoveString[4];
13176                 break;
13177             case WhiteDrop:
13178             case BlackDrop:
13179                 fromX = next == WhiteDrop ?
13180                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13181                   (int) CharToPiece(ToLower(currentMoveString[0]));
13182                 fromY = DROP_RANK;
13183                 toX = currentMoveString[2] - AAA;
13184                 toY = currentMoveString[3] - ONE;
13185                 promoChar = 0;
13186                 break;
13187         }
13188         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13189         plyNr++;
13190         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13191         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13192         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13193         if(appData.findMirror) {
13194             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13195             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13196         }
13197     }
13198 }
13199
13200 /* Load the nth game from open file f */
13201 int
13202 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13203 {
13204     ChessMove cm;
13205     char buf[MSG_SIZ];
13206     int gn = gameNumber;
13207     ListGame *lg = NULL;
13208     int numPGNTags = 0, i;
13209     int err, pos = -1;
13210     GameMode oldGameMode;
13211     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13212     char oldName[MSG_SIZ];
13213
13214     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13215
13216     if (appData.debugMode)
13217         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13218
13219     if (gameMode == Training )
13220         SetTrainingModeOff();
13221
13222     oldGameMode = gameMode;
13223     if (gameMode != BeginningOfGame) {
13224       Reset(FALSE, TRUE);
13225     }
13226     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13227
13228     gameFileFP = f;
13229     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13230         fclose(lastLoadGameFP);
13231     }
13232
13233     if (useList) {
13234         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13235
13236         if (lg) {
13237             fseek(f, lg->offset, 0);
13238             GameListHighlight(gameNumber);
13239             pos = lg->position;
13240             gn = 1;
13241         }
13242         else {
13243             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13244               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13245             else
13246             DisplayError(_("Game number out of range"), 0);
13247             return FALSE;
13248         }
13249     } else {
13250         GameListDestroy();
13251         if (fseek(f, 0, 0) == -1) {
13252             if (f == lastLoadGameFP ?
13253                 gameNumber == lastLoadGameNumber + 1 :
13254                 gameNumber == 1) {
13255                 gn = 1;
13256             } else {
13257                 DisplayError(_("Can't seek on game file"), 0);
13258                 return FALSE;
13259             }
13260         }
13261     }
13262     lastLoadGameFP = f;
13263     lastLoadGameNumber = gameNumber;
13264     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13265     lastLoadGameUseList = useList;
13266
13267     yynewfile(f);
13268
13269     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13270       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13271                 lg->gameInfo.black);
13272             DisplayTitle(buf);
13273     } else if (*title != NULLCHAR) {
13274         if (gameNumber > 1) {
13275           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13276             DisplayTitle(buf);
13277         } else {
13278             DisplayTitle(title);
13279         }
13280     }
13281
13282     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13283         gameMode = PlayFromGameFile;
13284         ModeHighlight();
13285     }
13286
13287     currentMove = forwardMostMove = backwardMostMove = 0;
13288     CopyBoard(boards[0], initialPosition);
13289     StopClocks();
13290
13291     /*
13292      * Skip the first gn-1 games in the file.
13293      * Also skip over anything that precedes an identifiable
13294      * start of game marker, to avoid being confused by
13295      * garbage at the start of the file.  Currently
13296      * recognized start of game markers are the move number "1",
13297      * the pattern "gnuchess .* game", the pattern
13298      * "^[#;%] [^ ]* game file", and a PGN tag block.
13299      * A game that starts with one of the latter two patterns
13300      * will also have a move number 1, possibly
13301      * following a position diagram.
13302      * 5-4-02: Let's try being more lenient and allowing a game to
13303      * start with an unnumbered move.  Does that break anything?
13304      */
13305     cm = lastLoadGameStart = EndOfFile;
13306     while (gn > 0) {
13307         yyboardindex = forwardMostMove;
13308         cm = (ChessMove) Myylex();
13309         switch (cm) {
13310           case EndOfFile:
13311             if (cmailMsgLoaded) {
13312                 nCmailGames = CMAIL_MAX_GAMES - gn;
13313             } else {
13314                 Reset(TRUE, TRUE);
13315                 DisplayError(_("Game not found in file"), 0);
13316             }
13317             return FALSE;
13318
13319           case GNUChessGame:
13320           case XBoardGame:
13321             gn--;
13322             lastLoadGameStart = cm;
13323             break;
13324
13325           case MoveNumberOne:
13326             switch (lastLoadGameStart) {
13327               case GNUChessGame:
13328               case XBoardGame:
13329               case PGNTag:
13330                 break;
13331               case MoveNumberOne:
13332               case EndOfFile:
13333                 gn--;           /* count this game */
13334                 lastLoadGameStart = cm;
13335                 break;
13336               default:
13337                 /* impossible */
13338                 break;
13339             }
13340             break;
13341
13342           case PGNTag:
13343             switch (lastLoadGameStart) {
13344               case GNUChessGame:
13345               case PGNTag:
13346               case MoveNumberOne:
13347               case EndOfFile:
13348                 gn--;           /* count this game */
13349                 lastLoadGameStart = cm;
13350                 break;
13351               case XBoardGame:
13352                 lastLoadGameStart = cm; /* game counted already */
13353                 break;
13354               default:
13355                 /* impossible */
13356                 break;
13357             }
13358             if (gn > 0) {
13359                 do {
13360                     yyboardindex = forwardMostMove;
13361                     cm = (ChessMove) Myylex();
13362                 } while (cm == PGNTag || cm == Comment);
13363             }
13364             break;
13365
13366           case WhiteWins:
13367           case BlackWins:
13368           case GameIsDrawn:
13369             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13370                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13371                     != CMAIL_OLD_RESULT) {
13372                     nCmailResults ++ ;
13373                     cmailResult[  CMAIL_MAX_GAMES
13374                                 - gn - 1] = CMAIL_OLD_RESULT;
13375                 }
13376             }
13377             break;
13378
13379           case NormalMove:
13380           case FirstLeg:
13381             /* Only a NormalMove can be at the start of a game
13382              * without a position diagram. */
13383             if (lastLoadGameStart == EndOfFile ) {
13384               gn--;
13385               lastLoadGameStart = MoveNumberOne;
13386             }
13387             break;
13388
13389           default:
13390             break;
13391         }
13392     }
13393
13394     if (appData.debugMode)
13395       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13396
13397     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13398
13399     if (cm == XBoardGame) {
13400         /* Skip any header junk before position diagram and/or move 1 */
13401         for (;;) {
13402             yyboardindex = forwardMostMove;
13403             cm = (ChessMove) Myylex();
13404
13405             if (cm == EndOfFile ||
13406                 cm == GNUChessGame || cm == XBoardGame) {
13407                 /* Empty game; pretend end-of-file and handle later */
13408                 cm = EndOfFile;
13409                 break;
13410             }
13411
13412             if (cm == MoveNumberOne || cm == PositionDiagram ||
13413                 cm == PGNTag || cm == Comment)
13414               break;
13415         }
13416     } else if (cm == GNUChessGame) {
13417         if (gameInfo.event != NULL) {
13418             free(gameInfo.event);
13419         }
13420         gameInfo.event = StrSave(yy_text);
13421     }
13422
13423     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13424     while (cm == PGNTag) {
13425         if (appData.debugMode)
13426           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13427         err = ParsePGNTag(yy_text, &gameInfo);
13428         if (!err) numPGNTags++;
13429
13430         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13431         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13432             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13433             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13434             InitPosition(TRUE);
13435             oldVariant = gameInfo.variant;
13436             if (appData.debugMode)
13437               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13438         }
13439
13440
13441         if (gameInfo.fen != NULL) {
13442           Board initial_position;
13443           startedFromSetupPosition = TRUE;
13444           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13445             Reset(TRUE, TRUE);
13446             DisplayError(_("Bad FEN position in file"), 0);
13447             return FALSE;
13448           }
13449           CopyBoard(boards[0], initial_position);
13450           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13451             CopyBoard(initialPosition, initial_position);
13452           if (blackPlaysFirst) {
13453             currentMove = forwardMostMove = backwardMostMove = 1;
13454             CopyBoard(boards[1], initial_position);
13455             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13456             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13457             timeRemaining[0][1] = whiteTimeRemaining;
13458             timeRemaining[1][1] = blackTimeRemaining;
13459             if (commentList[0] != NULL) {
13460               commentList[1] = commentList[0];
13461               commentList[0] = NULL;
13462             }
13463           } else {
13464             currentMove = forwardMostMove = backwardMostMove = 0;
13465           }
13466           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13467           {   int i;
13468               initialRulePlies = FENrulePlies;
13469               for( i=0; i< nrCastlingRights; i++ )
13470                   initialRights[i] = initial_position[CASTLING][i];
13471           }
13472           yyboardindex = forwardMostMove;
13473           free(gameInfo.fen);
13474           gameInfo.fen = NULL;
13475         }
13476
13477         yyboardindex = forwardMostMove;
13478         cm = (ChessMove) Myylex();
13479
13480         /* Handle comments interspersed among the tags */
13481         while (cm == Comment) {
13482             char *p;
13483             if (appData.debugMode)
13484               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13485             p = yy_text;
13486             AppendComment(currentMove, p, FALSE);
13487             yyboardindex = forwardMostMove;
13488             cm = (ChessMove) Myylex();
13489         }
13490     }
13491
13492     /* don't rely on existence of Event tag since if game was
13493      * pasted from clipboard the Event tag may not exist
13494      */
13495     if (numPGNTags > 0){
13496         char *tags;
13497         if (gameInfo.variant == VariantNormal) {
13498           VariantClass v = StringToVariant(gameInfo.event);
13499           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13500           if(v < VariantShogi) gameInfo.variant = v;
13501         }
13502         if (!matchMode) {
13503           if( appData.autoDisplayTags ) {
13504             tags = PGNTags(&gameInfo);
13505             TagsPopUp(tags, CmailMsg());
13506             free(tags);
13507           }
13508         }
13509     } else {
13510         /* Make something up, but don't display it now */
13511         SetGameInfo();
13512         TagsPopDown();
13513     }
13514
13515     if (cm == PositionDiagram) {
13516         int i, j;
13517         char *p;
13518         Board initial_position;
13519
13520         if (appData.debugMode)
13521           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13522
13523         if (!startedFromSetupPosition) {
13524             p = yy_text;
13525             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13526               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13527                 switch (*p) {
13528                   case '{':
13529                   case '[':
13530                   case '-':
13531                   case ' ':
13532                   case '\t':
13533                   case '\n':
13534                   case '\r':
13535                     break;
13536                   default:
13537                     initial_position[i][j++] = CharToPiece(*p);
13538                     break;
13539                 }
13540             while (*p == ' ' || *p == '\t' ||
13541                    *p == '\n' || *p == '\r') p++;
13542
13543             if (strncmp(p, "black", strlen("black"))==0)
13544               blackPlaysFirst = TRUE;
13545             else
13546               blackPlaysFirst = FALSE;
13547             startedFromSetupPosition = TRUE;
13548
13549             CopyBoard(boards[0], initial_position);
13550             if (blackPlaysFirst) {
13551                 currentMove = forwardMostMove = backwardMostMove = 1;
13552                 CopyBoard(boards[1], initial_position);
13553                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13554                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13555                 timeRemaining[0][1] = whiteTimeRemaining;
13556                 timeRemaining[1][1] = blackTimeRemaining;
13557                 if (commentList[0] != NULL) {
13558                     commentList[1] = commentList[0];
13559                     commentList[0] = NULL;
13560                 }
13561             } else {
13562                 currentMove = forwardMostMove = backwardMostMove = 0;
13563             }
13564         }
13565         yyboardindex = forwardMostMove;
13566         cm = (ChessMove) Myylex();
13567     }
13568
13569   if(!creatingBook) {
13570     if (first.pr == NoProc) {
13571         StartChessProgram(&first);
13572     }
13573     InitChessProgram(&first, FALSE);
13574     if(gameInfo.variant == VariantUnknown && *oldName) {
13575         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13576         gameInfo.variant = v;
13577     }
13578     SendToProgram("force\n", &first);
13579     if (startedFromSetupPosition) {
13580         SendBoard(&first, forwardMostMove);
13581     if (appData.debugMode) {
13582         fprintf(debugFP, "Load Game\n");
13583     }
13584         DisplayBothClocks();
13585     }
13586   }
13587
13588     /* [HGM] server: flag to write setup moves in broadcast file as one */
13589     loadFlag = appData.suppressLoadMoves;
13590
13591     while (cm == Comment) {
13592         char *p;
13593         if (appData.debugMode)
13594           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13595         p = yy_text;
13596         AppendComment(currentMove, p, FALSE);
13597         yyboardindex = forwardMostMove;
13598         cm = (ChessMove) Myylex();
13599     }
13600
13601     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13602         cm == WhiteWins || cm == BlackWins ||
13603         cm == GameIsDrawn || cm == GameUnfinished) {
13604         DisplayMessage("", _("No moves in game"));
13605         if (cmailMsgLoaded) {
13606             if (appData.debugMode)
13607               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13608             ClearHighlights();
13609             flipView = FALSE;
13610         }
13611         DrawPosition(FALSE, boards[currentMove]);
13612         DisplayBothClocks();
13613         gameMode = EditGame;
13614         ModeHighlight();
13615         gameFileFP = NULL;
13616         cmailOldMove = 0;
13617         return TRUE;
13618     }
13619
13620     // [HGM] PV info: routine tests if comment empty
13621     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13622         DisplayComment(currentMove - 1, commentList[currentMove]);
13623     }
13624     if (!matchMode && appData.timeDelay != 0)
13625       DrawPosition(FALSE, boards[currentMove]);
13626
13627     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13628       programStats.ok_to_send = 1;
13629     }
13630
13631     /* if the first token after the PGN tags is a move
13632      * and not move number 1, retrieve it from the parser
13633      */
13634     if (cm != MoveNumberOne)
13635         LoadGameOneMove(cm);
13636
13637     /* load the remaining moves from the file */
13638     while (LoadGameOneMove(EndOfFile)) {
13639       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13640       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13641     }
13642
13643     /* rewind to the start of the game */
13644     currentMove = backwardMostMove;
13645
13646     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13647
13648     if (oldGameMode == AnalyzeFile) {
13649       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13650       AnalyzeFileEvent();
13651     } else
13652     if (oldGameMode == AnalyzeMode) {
13653       AnalyzeFileEvent();
13654     }
13655
13656     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13657         long int w, b; // [HGM] adjourn: restore saved clock times
13658         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13659         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13660             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13661             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13662         }
13663     }
13664
13665     if(creatingBook) return TRUE;
13666     if (!matchMode && pos > 0) {
13667         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13668     } else
13669     if (matchMode || appData.timeDelay == 0) {
13670       ToEndEvent();
13671     } else if (appData.timeDelay > 0) {
13672       AutoPlayGameLoop();
13673     }
13674
13675     if (appData.debugMode)
13676         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13677
13678     loadFlag = 0; /* [HGM] true game starts */
13679     return TRUE;
13680 }
13681
13682 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13683 int
13684 ReloadPosition (int offset)
13685 {
13686     int positionNumber = lastLoadPositionNumber + offset;
13687     if (lastLoadPositionFP == NULL) {
13688         DisplayError(_("No position has been loaded yet"), 0);
13689         return FALSE;
13690     }
13691     if (positionNumber <= 0) {
13692         DisplayError(_("Can't back up any further"), 0);
13693         return FALSE;
13694     }
13695     return LoadPosition(lastLoadPositionFP, positionNumber,
13696                         lastLoadPositionTitle);
13697 }
13698
13699 /* Load the nth position from the given file */
13700 int
13701 LoadPositionFromFile (char *filename, int n, char *title)
13702 {
13703     FILE *f;
13704     char buf[MSG_SIZ];
13705
13706     if (strcmp(filename, "-") == 0) {
13707         return LoadPosition(stdin, n, "stdin");
13708     } else {
13709         f = fopen(filename, "rb");
13710         if (f == NULL) {
13711             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13712             DisplayError(buf, errno);
13713             return FALSE;
13714         } else {
13715             return LoadPosition(f, n, title);
13716         }
13717     }
13718 }
13719
13720 /* Load the nth position from the given open file, and close it */
13721 int
13722 LoadPosition (FILE *f, int positionNumber, char *title)
13723 {
13724     char *p, line[MSG_SIZ];
13725     Board initial_position;
13726     int i, j, fenMode, pn;
13727
13728     if (gameMode == Training )
13729         SetTrainingModeOff();
13730
13731     if (gameMode != BeginningOfGame) {
13732         Reset(FALSE, TRUE);
13733     }
13734     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13735         fclose(lastLoadPositionFP);
13736     }
13737     if (positionNumber == 0) positionNumber = 1;
13738     lastLoadPositionFP = f;
13739     lastLoadPositionNumber = positionNumber;
13740     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13741     if (first.pr == NoProc && !appData.noChessProgram) {
13742       StartChessProgram(&first);
13743       InitChessProgram(&first, FALSE);
13744     }
13745     pn = positionNumber;
13746     if (positionNumber < 0) {
13747         /* Negative position number means to seek to that byte offset */
13748         if (fseek(f, -positionNumber, 0) == -1) {
13749             DisplayError(_("Can't seek on position file"), 0);
13750             return FALSE;
13751         };
13752         pn = 1;
13753     } else {
13754         if (fseek(f, 0, 0) == -1) {
13755             if (f == lastLoadPositionFP ?
13756                 positionNumber == lastLoadPositionNumber + 1 :
13757                 positionNumber == 1) {
13758                 pn = 1;
13759             } else {
13760                 DisplayError(_("Can't seek on position file"), 0);
13761                 return FALSE;
13762             }
13763         }
13764     }
13765     /* See if this file is FEN or old-style xboard */
13766     if (fgets(line, MSG_SIZ, f) == NULL) {
13767         DisplayError(_("Position not found in file"), 0);
13768         return FALSE;
13769     }
13770     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13771     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13772
13773     if (pn >= 2) {
13774         if (fenMode || line[0] == '#') pn--;
13775         while (pn > 0) {
13776             /* skip positions before number pn */
13777             if (fgets(line, MSG_SIZ, f) == NULL) {
13778                 Reset(TRUE, TRUE);
13779                 DisplayError(_("Position not found in file"), 0);
13780                 return FALSE;
13781             }
13782             if (fenMode || line[0] == '#') pn--;
13783         }
13784     }
13785
13786     if (fenMode) {
13787         char *p;
13788         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13789             DisplayError(_("Bad FEN position in file"), 0);
13790             return FALSE;
13791         }
13792         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13793             sscanf(p+4, "%[^;]", bestMove);
13794         } else *bestMove = NULLCHAR;
13795         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13796             sscanf(p+4, "%[^;]", avoidMove);
13797         } else *avoidMove = NULLCHAR;
13798     } else {
13799         (void) fgets(line, MSG_SIZ, f);
13800         (void) fgets(line, MSG_SIZ, f);
13801
13802         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13803             (void) fgets(line, MSG_SIZ, f);
13804             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13805                 if (*p == ' ')
13806                   continue;
13807                 initial_position[i][j++] = CharToPiece(*p);
13808             }
13809         }
13810
13811         blackPlaysFirst = FALSE;
13812         if (!feof(f)) {
13813             (void) fgets(line, MSG_SIZ, f);
13814             if (strncmp(line, "black", strlen("black"))==0)
13815               blackPlaysFirst = TRUE;
13816         }
13817     }
13818     startedFromSetupPosition = TRUE;
13819
13820     CopyBoard(boards[0], initial_position);
13821     if (blackPlaysFirst) {
13822         currentMove = forwardMostMove = backwardMostMove = 1;
13823         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13824         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13825         CopyBoard(boards[1], initial_position);
13826         DisplayMessage("", _("Black to play"));
13827     } else {
13828         currentMove = forwardMostMove = backwardMostMove = 0;
13829         DisplayMessage("", _("White to play"));
13830     }
13831     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13832     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13833         SendToProgram("force\n", &first);
13834         SendBoard(&first, forwardMostMove);
13835     }
13836     if (appData.debugMode) {
13837 int i, j;
13838   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13839   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13840         fprintf(debugFP, "Load Position\n");
13841     }
13842
13843     if (positionNumber > 1) {
13844       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13845         DisplayTitle(line);
13846     } else {
13847         DisplayTitle(title);
13848     }
13849     gameMode = EditGame;
13850     ModeHighlight();
13851     ResetClocks();
13852     timeRemaining[0][1] = whiteTimeRemaining;
13853     timeRemaining[1][1] = blackTimeRemaining;
13854     DrawPosition(FALSE, boards[currentMove]);
13855
13856     return TRUE;
13857 }
13858
13859
13860 void
13861 CopyPlayerNameIntoFileName (char **dest, char *src)
13862 {
13863     while (*src != NULLCHAR && *src != ',') {
13864         if (*src == ' ') {
13865             *(*dest)++ = '_';
13866             src++;
13867         } else {
13868             *(*dest)++ = *src++;
13869         }
13870     }
13871 }
13872
13873 char *
13874 DefaultFileName (char *ext)
13875 {
13876     static char def[MSG_SIZ];
13877     char *p;
13878
13879     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13880         p = def;
13881         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13882         *p++ = '-';
13883         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13884         *p++ = '.';
13885         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13886     } else {
13887         def[0] = NULLCHAR;
13888     }
13889     return def;
13890 }
13891
13892 /* Save the current game to the given file */
13893 int
13894 SaveGameToFile (char *filename, int append)
13895 {
13896     FILE *f;
13897     char buf[MSG_SIZ];
13898     int result, i, t,tot=0;
13899
13900     if (strcmp(filename, "-") == 0) {
13901         return SaveGame(stdout, 0, NULL);
13902     } else {
13903         for(i=0; i<10; i++) { // upto 10 tries
13904              f = fopen(filename, append ? "a" : "w");
13905              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13906              if(f || errno != 13) break;
13907              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13908              tot += t;
13909         }
13910         if (f == NULL) {
13911             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13912             DisplayError(buf, errno);
13913             return FALSE;
13914         } else {
13915             safeStrCpy(buf, lastMsg, MSG_SIZ);
13916             DisplayMessage(_("Waiting for access to save file"), "");
13917             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13918             DisplayMessage(_("Saving game"), "");
13919             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13920             result = SaveGame(f, 0, NULL);
13921             DisplayMessage(buf, "");
13922             return result;
13923         }
13924     }
13925 }
13926
13927 char *
13928 SavePart (char *str)
13929 {
13930     static char buf[MSG_SIZ];
13931     char *p;
13932
13933     p = strchr(str, ' ');
13934     if (p == NULL) return str;
13935     strncpy(buf, str, p - str);
13936     buf[p - str] = NULLCHAR;
13937     return buf;
13938 }
13939
13940 #define PGN_MAX_LINE 75
13941
13942 #define PGN_SIDE_WHITE  0
13943 #define PGN_SIDE_BLACK  1
13944
13945 static int
13946 FindFirstMoveOutOfBook (int side)
13947 {
13948     int result = -1;
13949
13950     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13951         int index = backwardMostMove;
13952         int has_book_hit = 0;
13953
13954         if( (index % 2) != side ) {
13955             index++;
13956         }
13957
13958         while( index < forwardMostMove ) {
13959             /* Check to see if engine is in book */
13960             int depth = pvInfoList[index].depth;
13961             int score = pvInfoList[index].score;
13962             int in_book = 0;
13963
13964             if( depth <= 2 ) {
13965                 in_book = 1;
13966             }
13967             else if( score == 0 && depth == 63 ) {
13968                 in_book = 1; /* Zappa */
13969             }
13970             else if( score == 2 && depth == 99 ) {
13971                 in_book = 1; /* Abrok */
13972             }
13973
13974             has_book_hit += in_book;
13975
13976             if( ! in_book ) {
13977                 result = index;
13978
13979                 break;
13980             }
13981
13982             index += 2;
13983         }
13984     }
13985
13986     return result;
13987 }
13988
13989 void
13990 GetOutOfBookInfo (char * buf)
13991 {
13992     int oob[2];
13993     int i;
13994     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13995
13996     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13997     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13998
13999     *buf = '\0';
14000
14001     if( oob[0] >= 0 || oob[1] >= 0 ) {
14002         for( i=0; i<2; i++ ) {
14003             int idx = oob[i];
14004
14005             if( idx >= 0 ) {
14006                 if( i > 0 && oob[0] >= 0 ) {
14007                     strcat( buf, "   " );
14008                 }
14009
14010                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
14011                 sprintf( buf+strlen(buf), "%s%.2f",
14012                     pvInfoList[idx].score >= 0 ? "+" : "",
14013                     pvInfoList[idx].score / 100.0 );
14014             }
14015         }
14016     }
14017 }
14018
14019 /* Save game in PGN style */
14020 static void
14021 SaveGamePGN2 (FILE *f)
14022 {
14023     int i, offset, linelen, newblock;
14024 //    char *movetext;
14025     char numtext[32];
14026     int movelen, numlen, blank;
14027     char move_buffer[100]; /* [AS] Buffer for move+PV info */
14028
14029     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14030
14031     PrintPGNTags(f, &gameInfo);
14032
14033     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14034
14035     if (backwardMostMove > 0 || startedFromSetupPosition) {
14036         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14037         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14038         fprintf(f, "\n{--------------\n");
14039         PrintPosition(f, backwardMostMove);
14040         fprintf(f, "--------------}\n");
14041         free(fen);
14042     }
14043     else {
14044         /* [AS] Out of book annotation */
14045         if( appData.saveOutOfBookInfo ) {
14046             char buf[64];
14047
14048             GetOutOfBookInfo( buf );
14049
14050             if( buf[0] != '\0' ) {
14051                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14052             }
14053         }
14054
14055         fprintf(f, "\n");
14056     }
14057
14058     i = backwardMostMove;
14059     linelen = 0;
14060     newblock = TRUE;
14061
14062     while (i < forwardMostMove) {
14063         /* Print comments preceding this move */
14064         if (commentList[i] != NULL) {
14065             if (linelen > 0) fprintf(f, "\n");
14066             fprintf(f, "%s", commentList[i]);
14067             linelen = 0;
14068             newblock = TRUE;
14069         }
14070
14071         /* Format move number */
14072         if ((i % 2) == 0)
14073           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14074         else
14075           if (newblock)
14076             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14077           else
14078             numtext[0] = NULLCHAR;
14079
14080         numlen = strlen(numtext);
14081         newblock = FALSE;
14082
14083         /* Print move number */
14084         blank = linelen > 0 && numlen > 0;
14085         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14086             fprintf(f, "\n");
14087             linelen = 0;
14088             blank = 0;
14089         }
14090         if (blank) {
14091             fprintf(f, " ");
14092             linelen++;
14093         }
14094         fprintf(f, "%s", numtext);
14095         linelen += numlen;
14096
14097         /* Get move */
14098         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14099         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14100
14101         /* Print move */
14102         blank = linelen > 0 && movelen > 0;
14103         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14104             fprintf(f, "\n");
14105             linelen = 0;
14106             blank = 0;
14107         }
14108         if (blank) {
14109             fprintf(f, " ");
14110             linelen++;
14111         }
14112         fprintf(f, "%s", move_buffer);
14113         linelen += movelen;
14114
14115         /* [AS] Add PV info if present */
14116         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14117             /* [HGM] add time */
14118             char buf[MSG_SIZ]; int seconds;
14119
14120             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14121
14122             if( seconds <= 0)
14123               buf[0] = 0;
14124             else
14125               if( seconds < 30 )
14126                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14127               else
14128                 {
14129                   seconds = (seconds + 4)/10; // round to full seconds
14130                   if( seconds < 60 )
14131                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14132                   else
14133                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14134                 }
14135
14136             if(appData.cumulativeTimePGN) {
14137                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14138             }
14139
14140             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14141                       pvInfoList[i].score >= 0 ? "+" : "",
14142                       pvInfoList[i].score / 100.0,
14143                       pvInfoList[i].depth,
14144                       buf );
14145
14146             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14147
14148             /* Print score/depth */
14149             blank = linelen > 0 && movelen > 0;
14150             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14151                 fprintf(f, "\n");
14152                 linelen = 0;
14153                 blank = 0;
14154             }
14155             if (blank) {
14156                 fprintf(f, " ");
14157                 linelen++;
14158             }
14159             fprintf(f, "%s", move_buffer);
14160             linelen += movelen;
14161         }
14162
14163         i++;
14164     }
14165
14166     /* Start a new line */
14167     if (linelen > 0) fprintf(f, "\n");
14168
14169     /* Print comments after last move */
14170     if (commentList[i] != NULL) {
14171         fprintf(f, "%s\n", commentList[i]);
14172     }
14173
14174     /* Print result */
14175     if (gameInfo.resultDetails != NULL &&
14176         gameInfo.resultDetails[0] != NULLCHAR) {
14177         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14178         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14179            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14180             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14181         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14182     } else {
14183         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14184     }
14185 }
14186
14187 /* Save game in PGN style and close the file */
14188 int
14189 SaveGamePGN (FILE *f)
14190 {
14191     SaveGamePGN2(f);
14192     fclose(f);
14193     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14194     return TRUE;
14195 }
14196
14197 /* Save game in old style and close the file */
14198 int
14199 SaveGameOldStyle (FILE *f)
14200 {
14201     int i, offset;
14202     time_t tm;
14203
14204     tm = time((time_t *) NULL);
14205
14206     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14207     PrintOpponents(f);
14208
14209     if (backwardMostMove > 0 || startedFromSetupPosition) {
14210         fprintf(f, "\n[--------------\n");
14211         PrintPosition(f, backwardMostMove);
14212         fprintf(f, "--------------]\n");
14213     } else {
14214         fprintf(f, "\n");
14215     }
14216
14217     i = backwardMostMove;
14218     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14219
14220     while (i < forwardMostMove) {
14221         if (commentList[i] != NULL) {
14222             fprintf(f, "[%s]\n", commentList[i]);
14223         }
14224
14225         if ((i % 2) == 1) {
14226             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14227             i++;
14228         } else {
14229             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14230             i++;
14231             if (commentList[i] != NULL) {
14232                 fprintf(f, "\n");
14233                 continue;
14234             }
14235             if (i >= forwardMostMove) {
14236                 fprintf(f, "\n");
14237                 break;
14238             }
14239             fprintf(f, "%s\n", parseList[i]);
14240             i++;
14241         }
14242     }
14243
14244     if (commentList[i] != NULL) {
14245         fprintf(f, "[%s]\n", commentList[i]);
14246     }
14247
14248     /* This isn't really the old style, but it's close enough */
14249     if (gameInfo.resultDetails != NULL &&
14250         gameInfo.resultDetails[0] != NULLCHAR) {
14251         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14252                 gameInfo.resultDetails);
14253     } else {
14254         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14255     }
14256
14257     fclose(f);
14258     return TRUE;
14259 }
14260
14261 /* Save the current game to open file f and close the file */
14262 int
14263 SaveGame (FILE *f, int dummy, char *dummy2)
14264 {
14265     if (gameMode == EditPosition) EditPositionDone(TRUE);
14266     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14267     if (appData.oldSaveStyle)
14268       return SaveGameOldStyle(f);
14269     else
14270       return SaveGamePGN(f);
14271 }
14272
14273 /* Save the current position to the given file */
14274 int
14275 SavePositionToFile (char *filename)
14276 {
14277     FILE *f;
14278     char buf[MSG_SIZ];
14279
14280     if (strcmp(filename, "-") == 0) {
14281         return SavePosition(stdout, 0, NULL);
14282     } else {
14283         f = fopen(filename, "a");
14284         if (f == NULL) {
14285             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14286             DisplayError(buf, errno);
14287             return FALSE;
14288         } else {
14289             safeStrCpy(buf, lastMsg, MSG_SIZ);
14290             DisplayMessage(_("Waiting for access to save file"), "");
14291             flock(fileno(f), LOCK_EX); // [HGM] lock
14292             DisplayMessage(_("Saving position"), "");
14293             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14294             SavePosition(f, 0, NULL);
14295             DisplayMessage(buf, "");
14296             return TRUE;
14297         }
14298     }
14299 }
14300
14301 /* Save the current position to the given open file and close the file */
14302 int
14303 SavePosition (FILE *f, int dummy, char *dummy2)
14304 {
14305     time_t tm;
14306     char *fen;
14307
14308     if (gameMode == EditPosition) EditPositionDone(TRUE);
14309     if (appData.oldSaveStyle) {
14310         tm = time((time_t *) NULL);
14311
14312         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14313         PrintOpponents(f);
14314         fprintf(f, "[--------------\n");
14315         PrintPosition(f, currentMove);
14316         fprintf(f, "--------------]\n");
14317     } else {
14318         fen = PositionToFEN(currentMove, NULL, 1);
14319         fprintf(f, "%s\n", fen);
14320         free(fen);
14321     }
14322     fclose(f);
14323     return TRUE;
14324 }
14325
14326 void
14327 ReloadCmailMsgEvent (int unregister)
14328 {
14329 #if !WIN32
14330     static char *inFilename = NULL;
14331     static char *outFilename;
14332     int i;
14333     struct stat inbuf, outbuf;
14334     int status;
14335
14336     /* Any registered moves are unregistered if unregister is set, */
14337     /* i.e. invoked by the signal handler */
14338     if (unregister) {
14339         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14340             cmailMoveRegistered[i] = FALSE;
14341             if (cmailCommentList[i] != NULL) {
14342                 free(cmailCommentList[i]);
14343                 cmailCommentList[i] = NULL;
14344             }
14345         }
14346         nCmailMovesRegistered = 0;
14347     }
14348
14349     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14350         cmailResult[i] = CMAIL_NOT_RESULT;
14351     }
14352     nCmailResults = 0;
14353
14354     if (inFilename == NULL) {
14355         /* Because the filenames are static they only get malloced once  */
14356         /* and they never get freed                                      */
14357         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14358         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14359
14360         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14361         sprintf(outFilename, "%s.out", appData.cmailGameName);
14362     }
14363
14364     status = stat(outFilename, &outbuf);
14365     if (status < 0) {
14366         cmailMailedMove = FALSE;
14367     } else {
14368         status = stat(inFilename, &inbuf);
14369         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14370     }
14371
14372     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14373        counts the games, notes how each one terminated, etc.
14374
14375        It would be nice to remove this kludge and instead gather all
14376        the information while building the game list.  (And to keep it
14377        in the game list nodes instead of having a bunch of fixed-size
14378        parallel arrays.)  Note this will require getting each game's
14379        termination from the PGN tags, as the game list builder does
14380        not process the game moves.  --mann
14381        */
14382     cmailMsgLoaded = TRUE;
14383     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14384
14385     /* Load first game in the file or popup game menu */
14386     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14387
14388 #endif /* !WIN32 */
14389     return;
14390 }
14391
14392 int
14393 RegisterMove ()
14394 {
14395     FILE *f;
14396     char string[MSG_SIZ];
14397
14398     if (   cmailMailedMove
14399         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14400         return TRUE;            /* Allow free viewing  */
14401     }
14402
14403     /* Unregister move to ensure that we don't leave RegisterMove        */
14404     /* with the move registered when the conditions for registering no   */
14405     /* longer hold                                                       */
14406     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14407         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14408         nCmailMovesRegistered --;
14409
14410         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14411           {
14412               free(cmailCommentList[lastLoadGameNumber - 1]);
14413               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14414           }
14415     }
14416
14417     if (cmailOldMove == -1) {
14418         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14419         return FALSE;
14420     }
14421
14422     if (currentMove > cmailOldMove + 1) {
14423         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14424         return FALSE;
14425     }
14426
14427     if (currentMove < cmailOldMove) {
14428         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14429         return FALSE;
14430     }
14431
14432     if (forwardMostMove > currentMove) {
14433         /* Silently truncate extra moves */
14434         TruncateGame();
14435     }
14436
14437     if (   (currentMove == cmailOldMove + 1)
14438         || (   (currentMove == cmailOldMove)
14439             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14440                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14441         if (gameInfo.result != GameUnfinished) {
14442             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14443         }
14444
14445         if (commentList[currentMove] != NULL) {
14446             cmailCommentList[lastLoadGameNumber - 1]
14447               = StrSave(commentList[currentMove]);
14448         }
14449         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14450
14451         if (appData.debugMode)
14452           fprintf(debugFP, "Saving %s for game %d\n",
14453                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14454
14455         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14456
14457         f = fopen(string, "w");
14458         if (appData.oldSaveStyle) {
14459             SaveGameOldStyle(f); /* also closes the file */
14460
14461             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14462             f = fopen(string, "w");
14463             SavePosition(f, 0, NULL); /* also closes the file */
14464         } else {
14465             fprintf(f, "{--------------\n");
14466             PrintPosition(f, currentMove);
14467             fprintf(f, "--------------}\n\n");
14468
14469             SaveGame(f, 0, NULL); /* also closes the file*/
14470         }
14471
14472         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14473         nCmailMovesRegistered ++;
14474     } else if (nCmailGames == 1) {
14475         DisplayError(_("You have not made a move yet"), 0);
14476         return FALSE;
14477     }
14478
14479     return TRUE;
14480 }
14481
14482 void
14483 MailMoveEvent ()
14484 {
14485 #if !WIN32
14486     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14487     FILE *commandOutput;
14488     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14489     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14490     int nBuffers;
14491     int i;
14492     int archived;
14493     char *arcDir;
14494
14495     if (! cmailMsgLoaded) {
14496         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14497         return;
14498     }
14499
14500     if (nCmailGames == nCmailResults) {
14501         DisplayError(_("No unfinished games"), 0);
14502         return;
14503     }
14504
14505 #if CMAIL_PROHIBIT_REMAIL
14506     if (cmailMailedMove) {
14507       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);
14508         DisplayError(msg, 0);
14509         return;
14510     }
14511 #endif
14512
14513     if (! (cmailMailedMove || RegisterMove())) return;
14514
14515     if (   cmailMailedMove
14516         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14517       snprintf(string, MSG_SIZ, partCommandString,
14518                appData.debugMode ? " -v" : "", appData.cmailGameName);
14519         commandOutput = popen(string, "r");
14520
14521         if (commandOutput == NULL) {
14522             DisplayError(_("Failed to invoke cmail"), 0);
14523         } else {
14524             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14525                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14526             }
14527             if (nBuffers > 1) {
14528                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14529                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14530                 nBytes = MSG_SIZ - 1;
14531             } else {
14532                 (void) memcpy(msg, buffer, nBytes);
14533             }
14534             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14535
14536             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14537                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14538
14539                 archived = TRUE;
14540                 for (i = 0; i < nCmailGames; i ++) {
14541                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14542                         archived = FALSE;
14543                     }
14544                 }
14545                 if (   archived
14546                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14547                         != NULL)) {
14548                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14549                            arcDir,
14550                            appData.cmailGameName,
14551                            gameInfo.date);
14552                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14553                     cmailMsgLoaded = FALSE;
14554                 }
14555             }
14556
14557             DisplayInformation(msg);
14558             pclose(commandOutput);
14559         }
14560     } else {
14561         if ((*cmailMsg) != '\0') {
14562             DisplayInformation(cmailMsg);
14563         }
14564     }
14565
14566     return;
14567 #endif /* !WIN32 */
14568 }
14569
14570 char *
14571 CmailMsg ()
14572 {
14573 #if WIN32
14574     return NULL;
14575 #else
14576     int  prependComma = 0;
14577     char number[5];
14578     char string[MSG_SIZ];       /* Space for game-list */
14579     int  i;
14580
14581     if (!cmailMsgLoaded) return "";
14582
14583     if (cmailMailedMove) {
14584       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14585     } else {
14586         /* Create a list of games left */
14587       snprintf(string, MSG_SIZ, "[");
14588         for (i = 0; i < nCmailGames; i ++) {
14589             if (! (   cmailMoveRegistered[i]
14590                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14591                 if (prependComma) {
14592                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14593                 } else {
14594                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14595                     prependComma = 1;
14596                 }
14597
14598                 strcat(string, number);
14599             }
14600         }
14601         strcat(string, "]");
14602
14603         if (nCmailMovesRegistered + nCmailResults == 0) {
14604             switch (nCmailGames) {
14605               case 1:
14606                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14607                 break;
14608
14609               case 2:
14610                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14611                 break;
14612
14613               default:
14614                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14615                          nCmailGames);
14616                 break;
14617             }
14618         } else {
14619             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14620               case 1:
14621                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14622                          string);
14623                 break;
14624
14625               case 0:
14626                 if (nCmailResults == nCmailGames) {
14627                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14628                 } else {
14629                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14630                 }
14631                 break;
14632
14633               default:
14634                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14635                          string);
14636             }
14637         }
14638     }
14639     return cmailMsg;
14640 #endif /* WIN32 */
14641 }
14642
14643 void
14644 ResetGameEvent ()
14645 {
14646     if (gameMode == Training)
14647       SetTrainingModeOff();
14648
14649     Reset(TRUE, TRUE);
14650     cmailMsgLoaded = FALSE;
14651     if (appData.icsActive) {
14652       SendToICS(ics_prefix);
14653       SendToICS("refresh\n");
14654     }
14655 }
14656
14657 void
14658 ExitEvent (int status)
14659 {
14660     exiting++;
14661     if (exiting > 2) {
14662       /* Give up on clean exit */
14663       exit(status);
14664     }
14665     if (exiting > 1) {
14666       /* Keep trying for clean exit */
14667       return;
14668     }
14669
14670     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14671     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14672
14673     if (telnetISR != NULL) {
14674       RemoveInputSource(telnetISR);
14675     }
14676     if (icsPR != NoProc) {
14677       DestroyChildProcess(icsPR, TRUE);
14678     }
14679
14680     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14681     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14682
14683     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14684     /* make sure this other one finishes before killing it!                  */
14685     if(endingGame) { int count = 0;
14686         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14687         while(endingGame && count++ < 10) DoSleep(1);
14688         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14689     }
14690
14691     /* Kill off chess programs */
14692     if (first.pr != NoProc) {
14693         ExitAnalyzeMode();
14694
14695         DoSleep( appData.delayBeforeQuit );
14696         SendToProgram("quit\n", &first);
14697         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14698     }
14699     if (second.pr != NoProc) {
14700         DoSleep( appData.delayBeforeQuit );
14701         SendToProgram("quit\n", &second);
14702         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14703     }
14704     if (first.isr != NULL) {
14705         RemoveInputSource(first.isr);
14706     }
14707     if (second.isr != NULL) {
14708         RemoveInputSource(second.isr);
14709     }
14710
14711     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14712     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14713
14714     ShutDownFrontEnd();
14715     exit(status);
14716 }
14717
14718 void
14719 PauseEngine (ChessProgramState *cps)
14720 {
14721     SendToProgram("pause\n", cps);
14722     cps->pause = 2;
14723 }
14724
14725 void
14726 UnPauseEngine (ChessProgramState *cps)
14727 {
14728     SendToProgram("resume\n", cps);
14729     cps->pause = 1;
14730 }
14731
14732 void
14733 PauseEvent ()
14734 {
14735     if (appData.debugMode)
14736         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14737     if (pausing) {
14738         pausing = FALSE;
14739         ModeHighlight();
14740         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14741             StartClocks();
14742             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14743                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14744                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14745             }
14746             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14747             HandleMachineMove(stashedInputMove, stalledEngine);
14748             stalledEngine = NULL;
14749             return;
14750         }
14751         if (gameMode == MachinePlaysWhite ||
14752             gameMode == TwoMachinesPlay   ||
14753             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14754             if(first.pause)  UnPauseEngine(&first);
14755             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14756             if(second.pause) UnPauseEngine(&second);
14757             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14758             StartClocks();
14759         } else {
14760             DisplayBothClocks();
14761         }
14762         if (gameMode == PlayFromGameFile) {
14763             if (appData.timeDelay >= 0)
14764                 AutoPlayGameLoop();
14765         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14766             Reset(FALSE, TRUE);
14767             SendToICS(ics_prefix);
14768             SendToICS("refresh\n");
14769         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14770             ForwardInner(forwardMostMove);
14771         }
14772         pauseExamInvalid = FALSE;
14773     } else {
14774         switch (gameMode) {
14775           default:
14776             return;
14777           case IcsExamining:
14778             pauseExamForwardMostMove = forwardMostMove;
14779             pauseExamInvalid = FALSE;
14780             /* fall through */
14781           case IcsObserving:
14782           case IcsPlayingWhite:
14783           case IcsPlayingBlack:
14784             pausing = TRUE;
14785             ModeHighlight();
14786             return;
14787           case PlayFromGameFile:
14788             (void) StopLoadGameTimer();
14789             pausing = TRUE;
14790             ModeHighlight();
14791             break;
14792           case BeginningOfGame:
14793             if (appData.icsActive) return;
14794             /* else fall through */
14795           case MachinePlaysWhite:
14796           case MachinePlaysBlack:
14797           case TwoMachinesPlay:
14798             if (forwardMostMove == 0)
14799               return;           /* don't pause if no one has moved */
14800             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14801                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14802                 if(onMove->pause) {           // thinking engine can be paused
14803                     PauseEngine(onMove);      // do it
14804                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14805                         PauseEngine(onMove->other);
14806                     else
14807                         SendToProgram("easy\n", onMove->other);
14808                     StopClocks();
14809                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14810             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14811                 if(first.pause) {
14812                     PauseEngine(&first);
14813                     StopClocks();
14814                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14815             } else { // human on move, pause pondering by either method
14816                 if(first.pause)
14817                     PauseEngine(&first);
14818                 else if(appData.ponderNextMove)
14819                     SendToProgram("easy\n", &first);
14820                 StopClocks();
14821             }
14822             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14823           case AnalyzeMode:
14824             pausing = TRUE;
14825             ModeHighlight();
14826             break;
14827         }
14828     }
14829 }
14830
14831 void
14832 EditCommentEvent ()
14833 {
14834     char title[MSG_SIZ];
14835
14836     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14837       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14838     } else {
14839       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14840                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14841                parseList[currentMove - 1]);
14842     }
14843
14844     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14845 }
14846
14847
14848 void
14849 EditTagsEvent ()
14850 {
14851     char *tags = PGNTags(&gameInfo);
14852     bookUp = FALSE;
14853     EditTagsPopUp(tags, NULL);
14854     free(tags);
14855 }
14856
14857 void
14858 StartSecond ()
14859 {
14860     if(WaitForEngine(&second, StartSecond)) return;
14861     InitChessProgram(&second, FALSE);
14862     FeedMovesToProgram(&second, currentMove);
14863
14864     SendToProgram("analyze\n", &second);
14865     second.analyzing = TRUE;
14866     ThawUI();
14867 }
14868
14869 void
14870 ToggleSecond ()
14871 {
14872   if(second.analyzing) {
14873     SendToProgram("exit\n", &second);
14874     second.analyzing = FALSE;
14875   } else {
14876     StartSecond();
14877   }
14878 }
14879
14880 /* Toggle ShowThinking */
14881 void
14882 ToggleShowThinking()
14883 {
14884   appData.showThinking = !appData.showThinking;
14885   ShowThinkingEvent();
14886 }
14887
14888 int
14889 AnalyzeModeEvent ()
14890 {
14891     char buf[MSG_SIZ];
14892
14893     if (!first.analysisSupport) {
14894       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14895       DisplayError(buf, 0);
14896       return 0;
14897     }
14898     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14899     if (appData.icsActive) {
14900         if (gameMode != IcsObserving) {
14901           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14902             DisplayError(buf, 0);
14903             /* secure check */
14904             if (appData.icsEngineAnalyze) {
14905                 if (appData.debugMode)
14906                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14907                 ExitAnalyzeMode();
14908                 ModeHighlight();
14909             }
14910             return 0;
14911         }
14912         /* if enable, user wants to disable icsEngineAnalyze */
14913         if (appData.icsEngineAnalyze) {
14914                 ExitAnalyzeMode();
14915                 ModeHighlight();
14916                 return 0;
14917         }
14918         appData.icsEngineAnalyze = TRUE;
14919         if (appData.debugMode)
14920             fprintf(debugFP, "ICS engine analyze starting... \n");
14921     }
14922
14923     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14924     if (appData.noChessProgram || gameMode == AnalyzeMode)
14925       return 0;
14926
14927     if (gameMode != AnalyzeFile) {
14928         if (!appData.icsEngineAnalyze) {
14929                EditGameEvent();
14930                if (gameMode != EditGame) return 0;
14931         }
14932         if (!appData.showThinking) ToggleShowThinking();
14933         ResurrectChessProgram();
14934         SendToProgram("analyze\n", &first);
14935         first.analyzing = TRUE;
14936         /*first.maybeThinking = TRUE;*/
14937         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14938         EngineOutputPopUp();
14939     }
14940     if (!appData.icsEngineAnalyze) {
14941         gameMode = AnalyzeMode;
14942         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14943     }
14944     pausing = FALSE;
14945     ModeHighlight();
14946     SetGameInfo();
14947
14948     StartAnalysisClock();
14949     GetTimeMark(&lastNodeCountTime);
14950     lastNodeCount = 0;
14951     return 1;
14952 }
14953
14954 void
14955 AnalyzeFileEvent ()
14956 {
14957     if (appData.noChessProgram || gameMode == AnalyzeFile)
14958       return;
14959
14960     if (!first.analysisSupport) {
14961       char buf[MSG_SIZ];
14962       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14963       DisplayError(buf, 0);
14964       return;
14965     }
14966
14967     if (gameMode != AnalyzeMode) {
14968         keepInfo = 1; // mere annotating should not alter PGN tags
14969         EditGameEvent();
14970         keepInfo = 0;
14971         if (gameMode != EditGame) return;
14972         if (!appData.showThinking) ToggleShowThinking();
14973         ResurrectChessProgram();
14974         SendToProgram("analyze\n", &first);
14975         first.analyzing = TRUE;
14976         /*first.maybeThinking = TRUE;*/
14977         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14978         EngineOutputPopUp();
14979     }
14980     gameMode = AnalyzeFile;
14981     pausing = FALSE;
14982     ModeHighlight();
14983
14984     StartAnalysisClock();
14985     GetTimeMark(&lastNodeCountTime);
14986     lastNodeCount = 0;
14987     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14988     AnalysisPeriodicEvent(1);
14989 }
14990
14991 void
14992 MachineWhiteEvent ()
14993 {
14994     char buf[MSG_SIZ];
14995     char *bookHit = NULL;
14996
14997     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14998       return;
14999
15000
15001     if (gameMode == PlayFromGameFile ||
15002         gameMode == TwoMachinesPlay  ||
15003         gameMode == Training         ||
15004         gameMode == AnalyzeMode      ||
15005         gameMode == EndOfGame)
15006         EditGameEvent();
15007
15008     if (gameMode == EditPosition)
15009         EditPositionDone(TRUE);
15010
15011     if (!WhiteOnMove(currentMove)) {
15012         DisplayError(_("It is not White's turn"), 0);
15013         return;
15014     }
15015
15016     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15017       ExitAnalyzeMode();
15018
15019     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15020         gameMode == AnalyzeFile)
15021         TruncateGame();
15022
15023     ResurrectChessProgram();    /* in case it isn't running */
15024     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15025         gameMode = MachinePlaysWhite;
15026         ResetClocks();
15027     } else
15028     gameMode = MachinePlaysWhite;
15029     pausing = FALSE;
15030     ModeHighlight();
15031     SetGameInfo();
15032     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15033     DisplayTitle(buf);
15034     if (first.sendName) {
15035       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15036       SendToProgram(buf, &first);
15037     }
15038     if (first.sendTime) {
15039       if (first.useColors) {
15040         SendToProgram("black\n", &first); /*gnu kludge*/
15041       }
15042       SendTimeRemaining(&first, TRUE);
15043     }
15044     if (first.useColors) {
15045       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15046     }
15047     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15048     SetMachineThinkingEnables();
15049     first.maybeThinking = TRUE;
15050     StartClocks();
15051     firstMove = FALSE;
15052
15053     if (appData.autoFlipView && !flipView) {
15054       flipView = !flipView;
15055       DrawPosition(FALSE, NULL);
15056       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15057     }
15058
15059     if(bookHit) { // [HGM] book: simulate book reply
15060         static char bookMove[MSG_SIZ]; // a bit generous?
15061
15062         programStats.nodes = programStats.depth = programStats.time =
15063         programStats.score = programStats.got_only_move = 0;
15064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15065
15066         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15067         strcat(bookMove, bookHit);
15068         savedMessage = bookMove; // args for deferred call
15069         savedState = &first;
15070         ScheduleDelayedEvent(DeferredBookMove, 1);
15071     }
15072 }
15073
15074 void
15075 MachineBlackEvent ()
15076 {
15077   char buf[MSG_SIZ];
15078   char *bookHit = NULL;
15079
15080     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15081         return;
15082
15083
15084     if (gameMode == PlayFromGameFile ||
15085         gameMode == TwoMachinesPlay  ||
15086         gameMode == Training         ||
15087         gameMode == AnalyzeMode      ||
15088         gameMode == EndOfGame)
15089         EditGameEvent();
15090
15091     if (gameMode == EditPosition)
15092         EditPositionDone(TRUE);
15093
15094     if (WhiteOnMove(currentMove)) {
15095         DisplayError(_("It is not Black's turn"), 0);
15096         return;
15097     }
15098
15099     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15100       ExitAnalyzeMode();
15101
15102     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15103         gameMode == AnalyzeFile)
15104         TruncateGame();
15105
15106     ResurrectChessProgram();    /* in case it isn't running */
15107     gameMode = MachinePlaysBlack;
15108     pausing = FALSE;
15109     ModeHighlight();
15110     SetGameInfo();
15111     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15112     DisplayTitle(buf);
15113     if (first.sendName) {
15114       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15115       SendToProgram(buf, &first);
15116     }
15117     if (first.sendTime) {
15118       if (first.useColors) {
15119         SendToProgram("white\n", &first); /*gnu kludge*/
15120       }
15121       SendTimeRemaining(&first, FALSE);
15122     }
15123     if (first.useColors) {
15124       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15125     }
15126     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15127     SetMachineThinkingEnables();
15128     first.maybeThinking = TRUE;
15129     StartClocks();
15130
15131     if (appData.autoFlipView && flipView) {
15132       flipView = !flipView;
15133       DrawPosition(FALSE, NULL);
15134       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15135     }
15136     if(bookHit) { // [HGM] book: simulate book reply
15137         static char bookMove[MSG_SIZ]; // a bit generous?
15138
15139         programStats.nodes = programStats.depth = programStats.time =
15140         programStats.score = programStats.got_only_move = 0;
15141         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15142
15143         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15144         strcat(bookMove, bookHit);
15145         savedMessage = bookMove; // args for deferred call
15146         savedState = &first;
15147         ScheduleDelayedEvent(DeferredBookMove, 1);
15148     }
15149 }
15150
15151
15152 void
15153 DisplayTwoMachinesTitle ()
15154 {
15155     char buf[MSG_SIZ];
15156     if (appData.matchGames > 0) {
15157         if(appData.tourneyFile[0]) {
15158           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15159                    gameInfo.white, _("vs."), gameInfo.black,
15160                    nextGame+1, appData.matchGames+1,
15161                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15162         } else
15163         if (first.twoMachinesColor[0] == 'w') {
15164           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15165                    gameInfo.white, _("vs."),  gameInfo.black,
15166                    first.matchWins, second.matchWins,
15167                    matchGame - 1 - (first.matchWins + second.matchWins));
15168         } else {
15169           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15170                    gameInfo.white, _("vs."), gameInfo.black,
15171                    second.matchWins, first.matchWins,
15172                    matchGame - 1 - (first.matchWins + second.matchWins));
15173         }
15174     } else {
15175       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15176     }
15177     DisplayTitle(buf);
15178 }
15179
15180 void
15181 SettingsMenuIfReady ()
15182 {
15183   if (second.lastPing != second.lastPong) {
15184     DisplayMessage("", _("Waiting for second chess program"));
15185     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15186     return;
15187   }
15188   ThawUI();
15189   DisplayMessage("", "");
15190   SettingsPopUp(&second);
15191 }
15192
15193 int
15194 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15195 {
15196     char buf[MSG_SIZ];
15197     if (cps->pr == NoProc) {
15198         StartChessProgram(cps);
15199         if (cps->protocolVersion == 1) {
15200           retry();
15201           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15202         } else {
15203           /* kludge: allow timeout for initial "feature" command */
15204           if(retry != TwoMachinesEventIfReady) FreezeUI();
15205           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15206           DisplayMessage("", buf);
15207           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15208         }
15209         return 1;
15210     }
15211     return 0;
15212 }
15213
15214 void
15215 TwoMachinesEvent P((void))
15216 {
15217     int i, move = forwardMostMove;
15218     char buf[MSG_SIZ];
15219     ChessProgramState *onmove;
15220     char *bookHit = NULL;
15221     static int stalling = 0;
15222     TimeMark now;
15223     long wait;
15224
15225     if (appData.noChessProgram) return;
15226
15227     switch (gameMode) {
15228       case TwoMachinesPlay:
15229         return;
15230       case MachinePlaysWhite:
15231       case MachinePlaysBlack:
15232         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15233             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15234             return;
15235         }
15236         /* fall through */
15237       case BeginningOfGame:
15238       case PlayFromGameFile:
15239       case EndOfGame:
15240         EditGameEvent();
15241         if (gameMode != EditGame) return;
15242         break;
15243       case EditPosition:
15244         EditPositionDone(TRUE);
15245         break;
15246       case AnalyzeMode:
15247       case AnalyzeFile:
15248         ExitAnalyzeMode();
15249         break;
15250       case EditGame:
15251       default:
15252         break;
15253     }
15254
15255 //    forwardMostMove = currentMove;
15256     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15257     startingEngine = TRUE;
15258
15259     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15260
15261     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15262     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15263       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15264       return;
15265     }
15266   if(!appData.epd) {
15267     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15268
15269     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15270                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15271         startingEngine = matchMode = FALSE;
15272         DisplayError("second engine does not play this", 0);
15273         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15274         EditGameEvent(); // switch back to EditGame mode
15275         return;
15276     }
15277
15278     if(!stalling) {
15279       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15280       SendToProgram("force\n", &second);
15281       stalling = 1;
15282       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15283       return;
15284     }
15285   }
15286     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15287     if(appData.matchPause>10000 || appData.matchPause<10)
15288                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15289     wait = SubtractTimeMarks(&now, &pauseStart);
15290     if(wait < appData.matchPause) {
15291         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15292         return;
15293     }
15294     // we are now committed to starting the game
15295     stalling = 0;
15296     DisplayMessage("", "");
15297   if(!appData.epd) {
15298     if (startedFromSetupPosition) {
15299         SendBoard(&second, backwardMostMove);
15300     if (appData.debugMode) {
15301         fprintf(debugFP, "Two Machines\n");
15302     }
15303     }
15304     for (i = backwardMostMove; i < forwardMostMove; i++) {
15305         SendMoveToProgram(i, &second);
15306     }
15307   }
15308
15309     gameMode = TwoMachinesPlay;
15310     pausing = startingEngine = FALSE;
15311     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15312     SetGameInfo();
15313     DisplayTwoMachinesTitle();
15314     firstMove = TRUE;
15315     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15316         onmove = &first;
15317     } else {
15318         onmove = &second;
15319     }
15320     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15321     SendToProgram(first.computerString, &first);
15322     if (first.sendName) {
15323       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15324       SendToProgram(buf, &first);
15325     }
15326   if(!appData.epd) {
15327     SendToProgram(second.computerString, &second);
15328     if (second.sendName) {
15329       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15330       SendToProgram(buf, &second);
15331     }
15332   }
15333
15334     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15335         ResetClocks();
15336         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15337         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15338     }
15339     if (onmove->sendTime) {
15340       if (onmove->useColors) {
15341         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15342       }
15343       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15344     }
15345     if (onmove->useColors) {
15346       SendToProgram(onmove->twoMachinesColor, onmove);
15347     }
15348     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15349 //    SendToProgram("go\n", onmove);
15350     onmove->maybeThinking = TRUE;
15351     SetMachineThinkingEnables();
15352
15353     StartClocks();
15354
15355     if(bookHit) { // [HGM] book: simulate book reply
15356         static char bookMove[MSG_SIZ]; // a bit generous?
15357
15358         programStats.nodes = programStats.depth = programStats.time =
15359         programStats.score = programStats.got_only_move = 0;
15360         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15361
15362         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15363         strcat(bookMove, bookHit);
15364         savedMessage = bookMove; // args for deferred call
15365         savedState = onmove;
15366         ScheduleDelayedEvent(DeferredBookMove, 1);
15367     }
15368 }
15369
15370 void
15371 TrainingEvent ()
15372 {
15373     if (gameMode == Training) {
15374       SetTrainingModeOff();
15375       gameMode = PlayFromGameFile;
15376       DisplayMessage("", _("Training mode off"));
15377     } else {
15378       gameMode = Training;
15379       animateTraining = appData.animate;
15380
15381       /* make sure we are not already at the end of the game */
15382       if (currentMove < forwardMostMove) {
15383         SetTrainingModeOn();
15384         DisplayMessage("", _("Training mode on"));
15385       } else {
15386         gameMode = PlayFromGameFile;
15387         DisplayError(_("Already at end of game"), 0);
15388       }
15389     }
15390     ModeHighlight();
15391 }
15392
15393 void
15394 IcsClientEvent ()
15395 {
15396     if (!appData.icsActive) return;
15397     switch (gameMode) {
15398       case IcsPlayingWhite:
15399       case IcsPlayingBlack:
15400       case IcsObserving:
15401       case IcsIdle:
15402       case BeginningOfGame:
15403       case IcsExamining:
15404         return;
15405
15406       case EditGame:
15407         break;
15408
15409       case EditPosition:
15410         EditPositionDone(TRUE);
15411         break;
15412
15413       case AnalyzeMode:
15414       case AnalyzeFile:
15415         ExitAnalyzeMode();
15416         break;
15417
15418       default:
15419         EditGameEvent();
15420         break;
15421     }
15422
15423     gameMode = IcsIdle;
15424     ModeHighlight();
15425     return;
15426 }
15427
15428 void
15429 EditGameEvent ()
15430 {
15431     int i;
15432
15433     switch (gameMode) {
15434       case Training:
15435         SetTrainingModeOff();
15436         break;
15437       case MachinePlaysWhite:
15438       case MachinePlaysBlack:
15439       case BeginningOfGame:
15440         SendToProgram("force\n", &first);
15441         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15442             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15443                 char buf[MSG_SIZ];
15444                 abortEngineThink = TRUE;
15445                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15446                 SendToProgram(buf, &first);
15447                 DisplayMessage("Aborting engine think", "");
15448                 FreezeUI();
15449             }
15450         }
15451         SetUserThinkingEnables();
15452         break;
15453       case PlayFromGameFile:
15454         (void) StopLoadGameTimer();
15455         if (gameFileFP != NULL) {
15456             gameFileFP = NULL;
15457         }
15458         break;
15459       case EditPosition:
15460         EditPositionDone(TRUE);
15461         break;
15462       case AnalyzeMode:
15463       case AnalyzeFile:
15464         ExitAnalyzeMode();
15465         SendToProgram("force\n", &first);
15466         break;
15467       case TwoMachinesPlay:
15468         GameEnds(EndOfFile, NULL, GE_PLAYER);
15469         ResurrectChessProgram();
15470         SetUserThinkingEnables();
15471         break;
15472       case EndOfGame:
15473         ResurrectChessProgram();
15474         break;
15475       case IcsPlayingBlack:
15476       case IcsPlayingWhite:
15477         DisplayError(_("Warning: You are still playing a game"), 0);
15478         break;
15479       case IcsObserving:
15480         DisplayError(_("Warning: You are still observing a game"), 0);
15481         break;
15482       case IcsExamining:
15483         DisplayError(_("Warning: You are still examining a game"), 0);
15484         break;
15485       case IcsIdle:
15486         break;
15487       case EditGame:
15488       default:
15489         return;
15490     }
15491
15492     pausing = FALSE;
15493     StopClocks();
15494     first.offeredDraw = second.offeredDraw = 0;
15495
15496     if (gameMode == PlayFromGameFile) {
15497         whiteTimeRemaining = timeRemaining[0][currentMove];
15498         blackTimeRemaining = timeRemaining[1][currentMove];
15499         DisplayTitle("");
15500     }
15501
15502     if (gameMode == MachinePlaysWhite ||
15503         gameMode == MachinePlaysBlack ||
15504         gameMode == TwoMachinesPlay ||
15505         gameMode == EndOfGame) {
15506         i = forwardMostMove;
15507         while (i > currentMove) {
15508             SendToProgram("undo\n", &first);
15509             i--;
15510         }
15511         if(!adjustedClock) {
15512         whiteTimeRemaining = timeRemaining[0][currentMove];
15513         blackTimeRemaining = timeRemaining[1][currentMove];
15514         DisplayBothClocks();
15515         }
15516         if (whiteFlag || blackFlag) {
15517             whiteFlag = blackFlag = 0;
15518         }
15519         DisplayTitle("");
15520     }
15521
15522     gameMode = EditGame;
15523     ModeHighlight();
15524     SetGameInfo();
15525 }
15526
15527 void
15528 EditPositionEvent ()
15529 {
15530     int i;
15531     if (gameMode == EditPosition) {
15532         EditGameEvent();
15533         return;
15534     }
15535
15536     EditGameEvent();
15537     if (gameMode != EditGame) return;
15538
15539     gameMode = EditPosition;
15540     ModeHighlight();
15541     SetGameInfo();
15542     CopyBoard(rightsBoard, nullBoard);
15543     if (currentMove > 0)
15544       CopyBoard(boards[0], boards[currentMove]);
15545     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15546       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15547
15548     blackPlaysFirst = !WhiteOnMove(currentMove);
15549     ResetClocks();
15550     currentMove = forwardMostMove = backwardMostMove = 0;
15551     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15552     DisplayMove(-1);
15553     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15554 }
15555
15556 void
15557 ExitAnalyzeMode ()
15558 {
15559     /* [DM] icsEngineAnalyze - possible call from other functions */
15560     if (appData.icsEngineAnalyze) {
15561         appData.icsEngineAnalyze = FALSE;
15562
15563         DisplayMessage("",_("Close ICS engine analyze..."));
15564     }
15565     if (first.analysisSupport && first.analyzing) {
15566       SendToBoth("exit\n");
15567       first.analyzing = second.analyzing = FALSE;
15568     }
15569     thinkOutput[0] = NULLCHAR;
15570 }
15571
15572 void
15573 EditPositionDone (Boolean fakeRights)
15574 {
15575     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15576
15577     startedFromSetupPosition = TRUE;
15578     InitChessProgram(&first, FALSE);
15579     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15580       int r, f;
15581       boards[0][EP_STATUS] = EP_NONE;
15582       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15583       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15584         if(rightsBoard[r][f]) {
15585           ChessSquare p = boards[0][r][f];
15586           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15587           else if(p == king) boards[0][CASTLING][2] = f;
15588           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15589           else rightsBoard[r][f] = 2; // mark for second pass
15590         }
15591       }
15592       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15593         if(rightsBoard[r][f] == 2) {
15594           ChessSquare p = boards[0][r][f];
15595           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15596           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15597         }
15598       }
15599     }
15600     SendToProgram("force\n", &first);
15601     if (blackPlaysFirst) {
15602         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15603         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15604         currentMove = forwardMostMove = backwardMostMove = 1;
15605         CopyBoard(boards[1], boards[0]);
15606     } else {
15607         currentMove = forwardMostMove = backwardMostMove = 0;
15608     }
15609     SendBoard(&first, forwardMostMove);
15610     if (appData.debugMode) {
15611         fprintf(debugFP, "EditPosDone\n");
15612     }
15613     DisplayTitle("");
15614     DisplayMessage("", "");
15615     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15616     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15617     gameMode = EditGame;
15618     ModeHighlight();
15619     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15620     ClearHighlights(); /* [AS] */
15621 }
15622
15623 /* Pause for `ms' milliseconds */
15624 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15625 void
15626 TimeDelay (long ms)
15627 {
15628     TimeMark m1, m2;
15629
15630     GetTimeMark(&m1);
15631     do {
15632         GetTimeMark(&m2);
15633     } while (SubtractTimeMarks(&m2, &m1) < ms);
15634 }
15635
15636 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15637 void
15638 SendMultiLineToICS (char *buf)
15639 {
15640     char temp[MSG_SIZ+1], *p;
15641     int len;
15642
15643     len = strlen(buf);
15644     if (len > MSG_SIZ)
15645       len = MSG_SIZ;
15646
15647     strncpy(temp, buf, len);
15648     temp[len] = 0;
15649
15650     p = temp;
15651     while (*p) {
15652         if (*p == '\n' || *p == '\r')
15653           *p = ' ';
15654         ++p;
15655     }
15656
15657     strcat(temp, "\n");
15658     SendToICS(temp);
15659     SendToPlayer(temp, strlen(temp));
15660 }
15661
15662 void
15663 SetWhiteToPlayEvent ()
15664 {
15665     if (gameMode == EditPosition) {
15666         blackPlaysFirst = FALSE;
15667         DisplayBothClocks();    /* works because currentMove is 0 */
15668     } else if (gameMode == IcsExamining) {
15669         SendToICS(ics_prefix);
15670         SendToICS("tomove white\n");
15671     }
15672 }
15673
15674 void
15675 SetBlackToPlayEvent ()
15676 {
15677     if (gameMode == EditPosition) {
15678         blackPlaysFirst = TRUE;
15679         currentMove = 1;        /* kludge */
15680         DisplayBothClocks();
15681         currentMove = 0;
15682     } else if (gameMode == IcsExamining) {
15683         SendToICS(ics_prefix);
15684         SendToICS("tomove black\n");
15685     }
15686 }
15687
15688 void
15689 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15690 {
15691     char buf[MSG_SIZ];
15692     ChessSquare piece = boards[0][y][x];
15693     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15694     static int lastVariant;
15695     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15696
15697     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15698
15699     switch (selection) {
15700       case ClearBoard:
15701         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15702         MarkTargetSquares(1);
15703         CopyBoard(currentBoard, boards[0]);
15704         CopyBoard(menuBoard, initialPosition);
15705         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15706             SendToICS(ics_prefix);
15707             SendToICS("bsetup clear\n");
15708         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15709             SendToICS(ics_prefix);
15710             SendToICS("clearboard\n");
15711         } else {
15712             int nonEmpty = 0;
15713             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15714                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15715                 for (y = 0; y < BOARD_HEIGHT; y++) {
15716                     if (gameMode == IcsExamining) {
15717                         if (boards[currentMove][y][x] != EmptySquare) {
15718                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15719                                     AAA + x, ONE + y);
15720                             SendToICS(buf);
15721                         }
15722                     } else if(boards[0][y][x] != DarkSquare) {
15723                         if(boards[0][y][x] != p) nonEmpty++;
15724                         boards[0][y][x] = p;
15725                     }
15726                 }
15727             }
15728             CopyBoard(rightsBoard, nullBoard);
15729             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15730                 int r, i;
15731                 for(r = 0; r < BOARD_HEIGHT; r++) {
15732                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15733                     ChessSquare p = menuBoard[r][x];
15734                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15735                   }
15736                 }
15737                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15738                 DisplayMessage("Clicking clock again restores position", "");
15739                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15740                 if(!nonEmpty) { // asked to clear an empty board
15741                     CopyBoard(boards[0], menuBoard);
15742                 } else
15743                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15744                     CopyBoard(boards[0], initialPosition);
15745                 } else
15746                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15747                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15748                     CopyBoard(boards[0], erasedBoard);
15749                 } else
15750                     CopyBoard(erasedBoard, currentBoard);
15751
15752                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15753                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15754             }
15755         }
15756         if (gameMode == EditPosition) {
15757             DrawPosition(FALSE, boards[0]);
15758         }
15759         break;
15760
15761       case WhitePlay:
15762         SetWhiteToPlayEvent();
15763         break;
15764
15765       case BlackPlay:
15766         SetBlackToPlayEvent();
15767         break;
15768
15769       case EmptySquare:
15770         if (gameMode == IcsExamining) {
15771             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15772             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15773             SendToICS(buf);
15774         } else {
15775             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15776                 if(x == BOARD_LEFT-2) {
15777                     if(y < handSize-1-gameInfo.holdingsSize) break;
15778                     boards[0][y][1] = 0;
15779                 } else
15780                 if(x == BOARD_RGHT+1) {
15781                     if(y >= gameInfo.holdingsSize) break;
15782                     boards[0][y][BOARD_WIDTH-2] = 0;
15783                 } else break;
15784             }
15785             boards[0][y][x] = EmptySquare;
15786             DrawPosition(FALSE, boards[0]);
15787         }
15788         break;
15789
15790       case PromotePiece:
15791         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15792            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15793             selection = (ChessSquare) (PROMOTED(piece));
15794         } else if(piece == EmptySquare) selection = WhiteSilver;
15795         else selection = (ChessSquare)((int)piece - 1);
15796         goto defaultlabel;
15797
15798       case DemotePiece:
15799         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15800            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15801             selection = (ChessSquare) (DEMOTED(piece));
15802         } else if(piece == EmptySquare) selection = BlackSilver;
15803         else selection = (ChessSquare)((int)piece + 1);
15804         goto defaultlabel;
15805
15806       case WhiteQueen:
15807       case BlackQueen:
15808         if(gameInfo.variant == VariantShatranj ||
15809            gameInfo.variant == VariantXiangqi  ||
15810            gameInfo.variant == VariantCourier  ||
15811            gameInfo.variant == VariantASEAN    ||
15812            gameInfo.variant == VariantMakruk     )
15813             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15814         goto defaultlabel;
15815
15816       case WhiteRook:
15817         baseRank = 0;
15818       case BlackRook:
15819         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15820         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15821         goto defaultlabel;
15822
15823       case WhiteKing:
15824         baseRank = 0;
15825       case BlackKing:
15826         if(gameInfo.variant == VariantXiangqi)
15827             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15828         if(gameInfo.variant == VariantKnightmate)
15829             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15830         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15831       default:
15832         defaultlabel:
15833         if (gameMode == IcsExamining) {
15834             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15835             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15836                      PieceToChar(selection), AAA + x, ONE + y);
15837             SendToICS(buf);
15838         } else {
15839             rightsBoard[y][x] = hasRights;
15840             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15841                 int n;
15842                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15843                     n = PieceToNumber(selection - BlackPawn);
15844                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15845                     boards[0][handSize-1-n][0] = selection;
15846                     boards[0][handSize-1-n][1]++;
15847                 } else
15848                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15849                     n = PieceToNumber(selection);
15850                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15851                     boards[0][n][BOARD_WIDTH-1] = selection;
15852                     boards[0][n][BOARD_WIDTH-2]++;
15853                 }
15854             } else
15855             boards[0][y][x] = selection;
15856             DrawPosition(TRUE, boards[0]);
15857             ClearHighlights();
15858             fromX = fromY = -1;
15859         }
15860         break;
15861     }
15862 }
15863
15864
15865 void
15866 DropMenuEvent (ChessSquare selection, int x, int y)
15867 {
15868     ChessMove moveType;
15869
15870     switch (gameMode) {
15871       case IcsPlayingWhite:
15872       case MachinePlaysBlack:
15873         if (!WhiteOnMove(currentMove)) {
15874             DisplayMoveError(_("It is Black's turn"));
15875             return;
15876         }
15877         moveType = WhiteDrop;
15878         break;
15879       case IcsPlayingBlack:
15880       case MachinePlaysWhite:
15881         if (WhiteOnMove(currentMove)) {
15882             DisplayMoveError(_("It is White's turn"));
15883             return;
15884         }
15885         moveType = BlackDrop;
15886         break;
15887       case EditGame:
15888         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15889         break;
15890       default:
15891         return;
15892     }
15893
15894     if (moveType == BlackDrop && selection < BlackPawn) {
15895       selection = (ChessSquare) ((int) selection
15896                                  + (int) BlackPawn - (int) WhitePawn);
15897     }
15898     if (boards[currentMove][y][x] != EmptySquare) {
15899         DisplayMoveError(_("That square is occupied"));
15900         return;
15901     }
15902
15903     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15904 }
15905
15906 void
15907 AcceptEvent ()
15908 {
15909     /* Accept a pending offer of any kind from opponent */
15910
15911     if (appData.icsActive) {
15912         SendToICS(ics_prefix);
15913         SendToICS("accept\n");
15914     } else if (cmailMsgLoaded) {
15915         if (currentMove == cmailOldMove &&
15916             commentList[cmailOldMove] != NULL &&
15917             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15918                    "Black offers a draw" : "White offers a draw")) {
15919             TruncateGame();
15920             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15921             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15922         } else {
15923             DisplayError(_("There is no pending offer on this move"), 0);
15924             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15925         }
15926     } else {
15927         /* Not used for offers from chess program */
15928     }
15929 }
15930
15931 void
15932 DeclineEvent ()
15933 {
15934     /* Decline a pending offer of any kind from opponent */
15935
15936     if (appData.icsActive) {
15937         SendToICS(ics_prefix);
15938         SendToICS("decline\n");
15939     } else if (cmailMsgLoaded) {
15940         if (currentMove == cmailOldMove &&
15941             commentList[cmailOldMove] != NULL &&
15942             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15943                    "Black offers a draw" : "White offers a draw")) {
15944 #ifdef NOTDEF
15945             AppendComment(cmailOldMove, "Draw declined", TRUE);
15946             DisplayComment(cmailOldMove - 1, "Draw declined");
15947 #endif /*NOTDEF*/
15948         } else {
15949             DisplayError(_("There is no pending offer on this move"), 0);
15950         }
15951     } else {
15952         /* Not used for offers from chess program */
15953     }
15954 }
15955
15956 void
15957 RematchEvent ()
15958 {
15959     /* Issue ICS rematch command */
15960     if (appData.icsActive) {
15961         SendToICS(ics_prefix);
15962         SendToICS("rematch\n");
15963     }
15964 }
15965
15966 void
15967 CallFlagEvent ()
15968 {
15969     /* Call your opponent's flag (claim a win on time) */
15970     if (appData.icsActive) {
15971         SendToICS(ics_prefix);
15972         SendToICS("flag\n");
15973     } else {
15974         switch (gameMode) {
15975           default:
15976             return;
15977           case MachinePlaysWhite:
15978             if (whiteFlag) {
15979                 if (blackFlag)
15980                   GameEnds(GameIsDrawn, "Both players ran out of time",
15981                            GE_PLAYER);
15982                 else
15983                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15984             } else {
15985                 DisplayError(_("Your opponent is not out of time"), 0);
15986             }
15987             break;
15988           case MachinePlaysBlack:
15989             if (blackFlag) {
15990                 if (whiteFlag)
15991                   GameEnds(GameIsDrawn, "Both players ran out of time",
15992                            GE_PLAYER);
15993                 else
15994                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15995             } else {
15996                 DisplayError(_("Your opponent is not out of time"), 0);
15997             }
15998             break;
15999         }
16000     }
16001 }
16002
16003 void
16004 ClockClick (int which)
16005 {       // [HGM] code moved to back-end from winboard.c
16006         if(which) { // black clock
16007           if (gameMode == EditPosition || gameMode == IcsExamining) {
16008             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16009             SetBlackToPlayEvent();
16010           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16011                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
16012           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
16013           } else if (shiftKey) {
16014             AdjustClock(which, -1);
16015           } else if (gameMode == IcsPlayingWhite ||
16016                      gameMode == MachinePlaysBlack) {
16017             CallFlagEvent();
16018           }
16019         } else { // white clock
16020           if (gameMode == EditPosition || gameMode == IcsExamining) {
16021             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16022             SetWhiteToPlayEvent();
16023           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16024                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16025           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16026           } else if (shiftKey) {
16027             AdjustClock(which, -1);
16028           } else if (gameMode == IcsPlayingBlack ||
16029                    gameMode == MachinePlaysWhite) {
16030             CallFlagEvent();
16031           }
16032         }
16033 }
16034
16035 void
16036 DrawEvent ()
16037 {
16038     /* Offer draw or accept pending draw offer from opponent */
16039
16040     if (appData.icsActive) {
16041         /* Note: tournament rules require draw offers to be
16042            made after you make your move but before you punch
16043            your clock.  Currently ICS doesn't let you do that;
16044            instead, you immediately punch your clock after making
16045            a move, but you can offer a draw at any time. */
16046
16047         SendToICS(ics_prefix);
16048         SendToICS("draw\n");
16049         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16050     } else if (cmailMsgLoaded) {
16051         if (currentMove == cmailOldMove &&
16052             commentList[cmailOldMove] != NULL &&
16053             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16054                    "Black offers a draw" : "White offers a draw")) {
16055             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16056             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16057         } else if (currentMove == cmailOldMove + 1) {
16058             char *offer = WhiteOnMove(cmailOldMove) ?
16059               "White offers a draw" : "Black offers a draw";
16060             AppendComment(currentMove, offer, TRUE);
16061             DisplayComment(currentMove - 1, offer);
16062             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16063         } else {
16064             DisplayError(_("You must make your move before offering a draw"), 0);
16065             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16066         }
16067     } else if (first.offeredDraw) {
16068         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16069     } else {
16070         if (first.sendDrawOffers) {
16071             SendToProgram("draw\n", &first);
16072             userOfferedDraw = TRUE;
16073         }
16074     }
16075 }
16076
16077 void
16078 AdjournEvent ()
16079 {
16080     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16081
16082     if (appData.icsActive) {
16083         SendToICS(ics_prefix);
16084         SendToICS("adjourn\n");
16085     } else {
16086         /* Currently GNU Chess doesn't offer or accept Adjourns */
16087     }
16088 }
16089
16090
16091 void
16092 AbortEvent ()
16093 {
16094     /* Offer Abort or accept pending Abort offer from opponent */
16095
16096     if (appData.icsActive) {
16097         SendToICS(ics_prefix);
16098         SendToICS("abort\n");
16099     } else {
16100         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16101     }
16102 }
16103
16104 void
16105 ResignEvent ()
16106 {
16107     /* Resign.  You can do this even if it's not your turn. */
16108
16109     if (appData.icsActive) {
16110         SendToICS(ics_prefix);
16111         SendToICS("resign\n");
16112     } else {
16113         switch (gameMode) {
16114           case MachinePlaysWhite:
16115             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16116             break;
16117           case MachinePlaysBlack:
16118             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16119             break;
16120           case EditGame:
16121             if (cmailMsgLoaded) {
16122                 TruncateGame();
16123                 if (WhiteOnMove(cmailOldMove)) {
16124                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16125                 } else {
16126                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16127                 }
16128                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16129             }
16130             break;
16131           default:
16132             break;
16133         }
16134     }
16135 }
16136
16137
16138 void
16139 StopObservingEvent ()
16140 {
16141     /* Stop observing current games */
16142     SendToICS(ics_prefix);
16143     SendToICS("unobserve\n");
16144 }
16145
16146 void
16147 StopExaminingEvent ()
16148 {
16149     /* Stop observing current game */
16150     SendToICS(ics_prefix);
16151     SendToICS("unexamine\n");
16152 }
16153
16154 void
16155 ForwardInner (int target)
16156 {
16157     int limit; int oldSeekGraphUp = seekGraphUp;
16158
16159     if (appData.debugMode)
16160         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16161                 target, currentMove, forwardMostMove);
16162
16163     if (gameMode == EditPosition)
16164       return;
16165
16166     seekGraphUp = FALSE;
16167     MarkTargetSquares(1);
16168     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16169
16170     if (gameMode == PlayFromGameFile && !pausing)
16171       PauseEvent();
16172
16173     if (gameMode == IcsExamining && pausing)
16174       limit = pauseExamForwardMostMove;
16175     else
16176       limit = forwardMostMove;
16177
16178     if (target > limit) target = limit;
16179
16180     if (target > 0 && moveList[target - 1][0]) {
16181         int fromX, fromY, toX, toY;
16182         toX = moveList[target - 1][2] - AAA;
16183         toY = moveList[target - 1][3] - ONE;
16184         if (moveList[target - 1][1] == '@') {
16185             if (appData.highlightLastMove) {
16186                 SetHighlights(-1, -1, toX, toY);
16187             }
16188         } else {
16189             fromX = moveList[target - 1][0] - AAA;
16190             fromY = moveList[target - 1][1] - ONE;
16191             if (target == currentMove + 1) {
16192                 if(moveList[target - 1][4] == ';') { // multi-leg
16193                     killX = moveList[target - 1][5] - AAA;
16194                     killY = moveList[target - 1][6] - ONE;
16195                 }
16196                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16197                 killX = killY = -1;
16198             }
16199             if (appData.highlightLastMove) {
16200                 SetHighlights(fromX, fromY, toX, toY);
16201             }
16202         }
16203     }
16204     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16205         gameMode == Training || gameMode == PlayFromGameFile ||
16206         gameMode == AnalyzeFile) {
16207         while (currentMove < target) {
16208             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16209             SendMoveToProgram(currentMove++, &first);
16210         }
16211     } else {
16212         currentMove = target;
16213     }
16214
16215     if (gameMode == EditGame || gameMode == EndOfGame) {
16216         whiteTimeRemaining = timeRemaining[0][currentMove];
16217         blackTimeRemaining = timeRemaining[1][currentMove];
16218     }
16219     DisplayBothClocks();
16220     DisplayMove(currentMove - 1);
16221     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16222     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16223     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16224         DisplayComment(currentMove - 1, commentList[currentMove]);
16225     }
16226     ClearMap(); // [HGM] exclude: invalidate map
16227 }
16228
16229
16230 void
16231 ForwardEvent ()
16232 {
16233     if (gameMode == IcsExamining && !pausing) {
16234         SendToICS(ics_prefix);
16235         SendToICS("forward\n");
16236     } else {
16237         ForwardInner(currentMove + 1);
16238     }
16239 }
16240
16241 void
16242 ToEndEvent ()
16243 {
16244     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16245         /* to optimze, we temporarily turn off analysis mode while we feed
16246          * the remaining moves to the engine. Otherwise we get analysis output
16247          * after each move.
16248          */
16249         if (first.analysisSupport) {
16250           SendToProgram("exit\nforce\n", &first);
16251           first.analyzing = FALSE;
16252         }
16253     }
16254
16255     if (gameMode == IcsExamining && !pausing) {
16256         SendToICS(ics_prefix);
16257         SendToICS("forward 999999\n");
16258     } else {
16259         ForwardInner(forwardMostMove);
16260     }
16261
16262     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16263         /* we have fed all the moves, so reactivate analysis mode */
16264         SendToProgram("analyze\n", &first);
16265         first.analyzing = TRUE;
16266         /*first.maybeThinking = TRUE;*/
16267         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16268     }
16269 }
16270
16271 void
16272 BackwardInner (int target)
16273 {
16274     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16275
16276     if (appData.debugMode)
16277         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16278                 target, currentMove, forwardMostMove);
16279
16280     if (gameMode == EditPosition) return;
16281     seekGraphUp = FALSE;
16282     MarkTargetSquares(1);
16283     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16284     if (currentMove <= backwardMostMove) {
16285         ClearHighlights();
16286         DrawPosition(full_redraw, boards[currentMove]);
16287         return;
16288     }
16289     if (gameMode == PlayFromGameFile && !pausing)
16290       PauseEvent();
16291
16292     if (moveList[target][0]) {
16293         int fromX, fromY, toX, toY;
16294         toX = moveList[target][2] - AAA;
16295         toY = moveList[target][3] - ONE;
16296         if (moveList[target][1] == '@') {
16297             if (appData.highlightLastMove) {
16298                 SetHighlights(-1, -1, toX, toY);
16299             }
16300         } else {
16301             fromX = moveList[target][0] - AAA;
16302             fromY = moveList[target][1] - ONE;
16303             if (target == currentMove - 1) {
16304                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16305             }
16306             if (appData.highlightLastMove) {
16307                 SetHighlights(fromX, fromY, toX, toY);
16308             }
16309         }
16310     }
16311     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16312         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16313         while (currentMove > target) {
16314             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16315                 // null move cannot be undone. Reload program with move history before it.
16316                 int i;
16317                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16318                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16319                 }
16320                 SendBoard(&first, i);
16321               if(second.analyzing) SendBoard(&second, i);
16322                 for(currentMove=i; currentMove<target; currentMove++) {
16323                     SendMoveToProgram(currentMove, &first);
16324                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16325                 }
16326                 break;
16327             }
16328             SendToBoth("undo\n");
16329             currentMove--;
16330         }
16331     } else {
16332         currentMove = target;
16333     }
16334
16335     if (gameMode == EditGame || gameMode == EndOfGame) {
16336         whiteTimeRemaining = timeRemaining[0][currentMove];
16337         blackTimeRemaining = timeRemaining[1][currentMove];
16338     }
16339     DisplayBothClocks();
16340     DisplayMove(currentMove - 1);
16341     DrawPosition(full_redraw, boards[currentMove]);
16342     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16343     // [HGM] PV info: routine tests if comment empty
16344     DisplayComment(currentMove - 1, commentList[currentMove]);
16345     ClearMap(); // [HGM] exclude: invalidate map
16346 }
16347
16348 void
16349 BackwardEvent ()
16350 {
16351     if (gameMode == IcsExamining && !pausing) {
16352         SendToICS(ics_prefix);
16353         SendToICS("backward\n");
16354     } else {
16355         BackwardInner(currentMove - 1);
16356     }
16357 }
16358
16359 void
16360 ToStartEvent ()
16361 {
16362     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16363         /* to optimize, we temporarily turn off analysis mode while we undo
16364          * all the moves. Otherwise we get analysis output after each undo.
16365          */
16366         if (first.analysisSupport) {
16367           SendToProgram("exit\nforce\n", &first);
16368           first.analyzing = FALSE;
16369         }
16370     }
16371
16372     if (gameMode == IcsExamining && !pausing) {
16373         SendToICS(ics_prefix);
16374         SendToICS("backward 999999\n");
16375     } else {
16376         BackwardInner(backwardMostMove);
16377     }
16378
16379     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16380         /* we have fed all the moves, so reactivate analysis mode */
16381         SendToProgram("analyze\n", &first);
16382         first.analyzing = TRUE;
16383         /*first.maybeThinking = TRUE;*/
16384         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16385     }
16386 }
16387
16388 void
16389 ToNrEvent (int to)
16390 {
16391   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16392   if (to >= forwardMostMove) to = forwardMostMove;
16393   if (to <= backwardMostMove) to = backwardMostMove;
16394   if (to < currentMove) {
16395     BackwardInner(to);
16396   } else {
16397     ForwardInner(to);
16398   }
16399 }
16400
16401 void
16402 RevertEvent (Boolean annotate)
16403 {
16404     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16405         return;
16406     }
16407     if (gameMode != IcsExamining) {
16408         DisplayError(_("You are not examining a game"), 0);
16409         return;
16410     }
16411     if (pausing) {
16412         DisplayError(_("You can't revert while pausing"), 0);
16413         return;
16414     }
16415     SendToICS(ics_prefix);
16416     SendToICS("revert\n");
16417 }
16418
16419 void
16420 RetractMoveEvent ()
16421 {
16422     switch (gameMode) {
16423       case MachinePlaysWhite:
16424       case MachinePlaysBlack:
16425         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16426             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16427             return;
16428         }
16429         if (forwardMostMove < 2) return;
16430         currentMove = forwardMostMove = forwardMostMove - 2;
16431         whiteTimeRemaining = timeRemaining[0][currentMove];
16432         blackTimeRemaining = timeRemaining[1][currentMove];
16433         DisplayBothClocks();
16434         DisplayMove(currentMove - 1);
16435         ClearHighlights();/*!! could figure this out*/
16436         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16437         SendToProgram("remove\n", &first);
16438         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16439         break;
16440
16441       case BeginningOfGame:
16442       default:
16443         break;
16444
16445       case IcsPlayingWhite:
16446       case IcsPlayingBlack:
16447         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16448             SendToICS(ics_prefix);
16449             SendToICS("takeback 2\n");
16450         } else {
16451             SendToICS(ics_prefix);
16452             SendToICS("takeback 1\n");
16453         }
16454         break;
16455     }
16456 }
16457
16458 void
16459 MoveNowEvent ()
16460 {
16461     ChessProgramState *cps;
16462
16463     switch (gameMode) {
16464       case MachinePlaysWhite:
16465         if (!WhiteOnMove(forwardMostMove)) {
16466             DisplayError(_("It is your turn"), 0);
16467             return;
16468         }
16469         cps = &first;
16470         break;
16471       case MachinePlaysBlack:
16472         if (WhiteOnMove(forwardMostMove)) {
16473             DisplayError(_("It is your turn"), 0);
16474             return;
16475         }
16476         cps = &first;
16477         break;
16478       case TwoMachinesPlay:
16479         if (WhiteOnMove(forwardMostMove) ==
16480             (first.twoMachinesColor[0] == 'w')) {
16481             cps = &first;
16482         } else {
16483             cps = &second;
16484         }
16485         break;
16486       case BeginningOfGame:
16487       default:
16488         return;
16489     }
16490     SendToProgram("?\n", cps);
16491 }
16492
16493 void
16494 TruncateGameEvent ()
16495 {
16496     EditGameEvent();
16497     if (gameMode != EditGame) return;
16498     TruncateGame();
16499 }
16500
16501 void
16502 TruncateGame ()
16503 {
16504     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16505     if (forwardMostMove > currentMove) {
16506         if (gameInfo.resultDetails != NULL) {
16507             free(gameInfo.resultDetails);
16508             gameInfo.resultDetails = NULL;
16509             gameInfo.result = GameUnfinished;
16510         }
16511         forwardMostMove = currentMove;
16512         HistorySet(parseList, backwardMostMove, forwardMostMove,
16513                    currentMove-1);
16514     }
16515 }
16516
16517 void
16518 HintEvent ()
16519 {
16520     if (appData.noChessProgram) return;
16521     switch (gameMode) {
16522       case MachinePlaysWhite:
16523         if (WhiteOnMove(forwardMostMove)) {
16524             DisplayError(_("Wait until your turn."), 0);
16525             return;
16526         }
16527         break;
16528       case BeginningOfGame:
16529       case MachinePlaysBlack:
16530         if (!WhiteOnMove(forwardMostMove)) {
16531             DisplayError(_("Wait until your turn."), 0);
16532             return;
16533         }
16534         break;
16535       default:
16536         DisplayError(_("No hint available"), 0);
16537         return;
16538     }
16539     SendToProgram("hint\n", &first);
16540     hintRequested = TRUE;
16541 }
16542
16543 int
16544 SaveSelected (FILE *g, int dummy, char *dummy2)
16545 {
16546     ListGame * lg = (ListGame *) gameList.head;
16547     int nItem, cnt=0;
16548     FILE *f;
16549
16550     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16551         DisplayError(_("Game list not loaded or empty"), 0);
16552         return 0;
16553     }
16554
16555     creatingBook = TRUE; // suppresses stuff during load game
16556
16557     /* Get list size */
16558     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16559         if(lg->position >= 0) { // selected?
16560             LoadGame(f, nItem, "", TRUE);
16561             SaveGamePGN2(g); // leaves g open
16562             cnt++; DoEvents();
16563         }
16564         lg = (ListGame *) lg->node.succ;
16565     }
16566
16567     fclose(g);
16568     creatingBook = FALSE;
16569
16570     return cnt;
16571 }
16572
16573 void
16574 CreateBookEvent ()
16575 {
16576     ListGame * lg = (ListGame *) gameList.head;
16577     FILE *f, *g;
16578     int nItem;
16579     static int secondTime = FALSE;
16580
16581     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16582         DisplayError(_("Game list not loaded or empty"), 0);
16583         return;
16584     }
16585
16586     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16587         fclose(g);
16588         secondTime++;
16589         DisplayNote(_("Book file exists! Try again for overwrite."));
16590         return;
16591     }
16592
16593     creatingBook = TRUE;
16594     secondTime = FALSE;
16595
16596     /* Get list size */
16597     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16598         if(lg->position >= 0) {
16599             LoadGame(f, nItem, "", TRUE);
16600             AddGameToBook(TRUE);
16601             DoEvents();
16602         }
16603         lg = (ListGame *) lg->node.succ;
16604     }
16605
16606     creatingBook = FALSE;
16607     FlushBook();
16608 }
16609
16610 void
16611 BookEvent ()
16612 {
16613     if (appData.noChessProgram) return;
16614     switch (gameMode) {
16615       case MachinePlaysWhite:
16616         if (WhiteOnMove(forwardMostMove)) {
16617             DisplayError(_("Wait until your turn."), 0);
16618             return;
16619         }
16620         break;
16621       case BeginningOfGame:
16622       case MachinePlaysBlack:
16623         if (!WhiteOnMove(forwardMostMove)) {
16624             DisplayError(_("Wait until your turn."), 0);
16625             return;
16626         }
16627         break;
16628       case EditPosition:
16629         EditPositionDone(TRUE);
16630         break;
16631       case TwoMachinesPlay:
16632         return;
16633       default:
16634         break;
16635     }
16636     SendToProgram("bk\n", &first);
16637     bookOutput[0] = NULLCHAR;
16638     bookRequested = TRUE;
16639 }
16640
16641 void
16642 AboutGameEvent ()
16643 {
16644     char *tags = PGNTags(&gameInfo);
16645     TagsPopUp(tags, CmailMsg());
16646     free(tags);
16647 }
16648
16649 /* end button procedures */
16650
16651 void
16652 PrintPosition (FILE *fp, int move)
16653 {
16654     int i, j;
16655
16656     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16657         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16658             char c = PieceToChar(boards[move][i][j]);
16659             fputc(c == '?' ? '.' : c, fp);
16660             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16661         }
16662     }
16663     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16664       fprintf(fp, "white to play\n");
16665     else
16666       fprintf(fp, "black to play\n");
16667 }
16668
16669 void
16670 PrintOpponents (FILE *fp)
16671 {
16672     if (gameInfo.white != NULL) {
16673         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16674     } else {
16675         fprintf(fp, "\n");
16676     }
16677 }
16678
16679 /* Find last component of program's own name, using some heuristics */
16680 void
16681 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16682 {
16683     char *p, *q, c;
16684     int local = (strcmp(host, "localhost") == 0);
16685     while (!local && (p = strchr(prog, ';')) != NULL) {
16686         p++;
16687         while (*p == ' ') p++;
16688         prog = p;
16689     }
16690     if (*prog == '"' || *prog == '\'') {
16691         q = strchr(prog + 1, *prog);
16692     } else {
16693         q = strchr(prog, ' ');
16694     }
16695     if (q == NULL) q = prog + strlen(prog);
16696     p = q;
16697     while (p >= prog && *p != '/' && *p != '\\') p--;
16698     p++;
16699     if(p == prog && *p == '"') p++;
16700     c = *q; *q = 0;
16701     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16702     memcpy(buf, p, q - p);
16703     buf[q - p] = NULLCHAR;
16704     if (!local) {
16705         strcat(buf, "@");
16706         strcat(buf, host);
16707     }
16708 }
16709
16710 char *
16711 TimeControlTagValue ()
16712 {
16713     char buf[MSG_SIZ];
16714     if (!appData.clockMode) {
16715       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16716     } else if (movesPerSession > 0) {
16717       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16718     } else if (timeIncrement == 0) {
16719       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16720     } else {
16721       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16722     }
16723     return StrSave(buf);
16724 }
16725
16726 void
16727 SetGameInfo ()
16728 {
16729     /* This routine is used only for certain modes */
16730     VariantClass v = gameInfo.variant;
16731     ChessMove r = GameUnfinished;
16732     char *p = NULL;
16733
16734     if(keepInfo) return;
16735
16736     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16737         r = gameInfo.result;
16738         p = gameInfo.resultDetails;
16739         gameInfo.resultDetails = NULL;
16740     }
16741     ClearGameInfo(&gameInfo);
16742     gameInfo.variant = v;
16743
16744     switch (gameMode) {
16745       case MachinePlaysWhite:
16746         gameInfo.event = StrSave( appData.pgnEventHeader );
16747         gameInfo.site = StrSave(HostName());
16748         gameInfo.date = PGNDate();
16749         gameInfo.round = StrSave("-");
16750         gameInfo.white = StrSave(first.tidy);
16751         gameInfo.black = StrSave(UserName());
16752         gameInfo.timeControl = TimeControlTagValue();
16753         break;
16754
16755       case MachinePlaysBlack:
16756         gameInfo.event = StrSave( appData.pgnEventHeader );
16757         gameInfo.site = StrSave(HostName());
16758         gameInfo.date = PGNDate();
16759         gameInfo.round = StrSave("-");
16760         gameInfo.white = StrSave(UserName());
16761         gameInfo.black = StrSave(first.tidy);
16762         gameInfo.timeControl = TimeControlTagValue();
16763         break;
16764
16765       case TwoMachinesPlay:
16766         gameInfo.event = StrSave( appData.pgnEventHeader );
16767         gameInfo.site = StrSave(HostName());
16768         gameInfo.date = PGNDate();
16769         if (roundNr > 0) {
16770             char buf[MSG_SIZ];
16771             snprintf(buf, MSG_SIZ, "%d", roundNr);
16772             gameInfo.round = StrSave(buf);
16773         } else {
16774             gameInfo.round = StrSave("-");
16775         }
16776         if (first.twoMachinesColor[0] == 'w') {
16777             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16778             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16779         } else {
16780             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16781             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16782         }
16783         gameInfo.timeControl = TimeControlTagValue();
16784         break;
16785
16786       case EditGame:
16787         gameInfo.event = StrSave("Edited game");
16788         gameInfo.site = StrSave(HostName());
16789         gameInfo.date = PGNDate();
16790         gameInfo.round = StrSave("-");
16791         gameInfo.white = StrSave("-");
16792         gameInfo.black = StrSave("-");
16793         gameInfo.result = r;
16794         gameInfo.resultDetails = p;
16795         break;
16796
16797       case EditPosition:
16798         gameInfo.event = StrSave("Edited position");
16799         gameInfo.site = StrSave(HostName());
16800         gameInfo.date = PGNDate();
16801         gameInfo.round = StrSave("-");
16802         gameInfo.white = StrSave("-");
16803         gameInfo.black = StrSave("-");
16804         break;
16805
16806       case IcsPlayingWhite:
16807       case IcsPlayingBlack:
16808       case IcsObserving:
16809       case IcsExamining:
16810         break;
16811
16812       case PlayFromGameFile:
16813         gameInfo.event = StrSave("Game from non-PGN file");
16814         gameInfo.site = StrSave(HostName());
16815         gameInfo.date = PGNDate();
16816         gameInfo.round = StrSave("-");
16817         gameInfo.white = StrSave("?");
16818         gameInfo.black = StrSave("?");
16819         break;
16820
16821       default:
16822         break;
16823     }
16824 }
16825
16826 void
16827 ReplaceComment (int index, char *text)
16828 {
16829     int len;
16830     char *p;
16831     float score;
16832
16833     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16834        pvInfoList[index-1].depth == len &&
16835        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16836        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16837     while (*text == '\n') text++;
16838     len = strlen(text);
16839     while (len > 0 && text[len - 1] == '\n') len--;
16840
16841     if (commentList[index] != NULL)
16842       free(commentList[index]);
16843
16844     if (len == 0) {
16845         commentList[index] = NULL;
16846         return;
16847     }
16848   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16849       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16850       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16851     commentList[index] = (char *) malloc(len + 2);
16852     strncpy(commentList[index], text, len);
16853     commentList[index][len] = '\n';
16854     commentList[index][len + 1] = NULLCHAR;
16855   } else {
16856     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16857     char *p;
16858     commentList[index] = (char *) malloc(len + 7);
16859     safeStrCpy(commentList[index], "{\n", 3);
16860     safeStrCpy(commentList[index]+2, text, len+1);
16861     commentList[index][len+2] = NULLCHAR;
16862     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16863     strcat(commentList[index], "\n}\n");
16864   }
16865 }
16866
16867 void
16868 CrushCRs (char *text)
16869 {
16870   char *p = text;
16871   char *q = text;
16872   char ch;
16873
16874   do {
16875     ch = *p++;
16876     if (ch == '\r') continue;
16877     *q++ = ch;
16878   } while (ch != '\0');
16879 }
16880
16881 void
16882 AppendComment (int index, char *text, Boolean addBraces)
16883 /* addBraces  tells if we should add {} */
16884 {
16885     int oldlen, len;
16886     char *old;
16887
16888 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16889     if(addBraces == 3) addBraces = 0; else // force appending literally
16890     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16891
16892     CrushCRs(text);
16893     while (*text == '\n') text++;
16894     len = strlen(text);
16895     while (len > 0 && text[len - 1] == '\n') len--;
16896     text[len] = NULLCHAR;
16897
16898     if (len == 0) return;
16899
16900     if (commentList[index] != NULL) {
16901       Boolean addClosingBrace = addBraces;
16902         old = commentList[index];
16903         oldlen = strlen(old);
16904         while(commentList[index][oldlen-1] ==  '\n')
16905           commentList[index][--oldlen] = NULLCHAR;
16906         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16907         safeStrCpy(commentList[index], old, oldlen + len + 6);
16908         free(old);
16909         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16910         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16911           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16912           while (*text == '\n') { text++; len--; }
16913           commentList[index][--oldlen] = NULLCHAR;
16914       }
16915         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16916         else          strcat(commentList[index], "\n");
16917         strcat(commentList[index], text);
16918         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16919         else          strcat(commentList[index], "\n");
16920     } else {
16921         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16922         if(addBraces)
16923           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16924         else commentList[index][0] = NULLCHAR;
16925         strcat(commentList[index], text);
16926         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16927         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16928     }
16929 }
16930
16931 static char *
16932 FindStr (char * text, char * sub_text)
16933 {
16934     char * result = strstr( text, sub_text );
16935
16936     if( result != NULL ) {
16937         result += strlen( sub_text );
16938     }
16939
16940     return result;
16941 }
16942
16943 /* [AS] Try to extract PV info from PGN comment */
16944 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16945 char *
16946 GetInfoFromComment (int index, char * text)
16947 {
16948     char * sep = text, *p;
16949
16950     if( text != NULL && index > 0 ) {
16951         int score = 0;
16952         int depth = 0;
16953         int time = -1, sec = 0, deci;
16954         char * s_eval = FindStr( text, "[%eval " );
16955         char * s_emt = FindStr( text, "[%emt " );
16956 #if 0
16957         if( s_eval != NULL || s_emt != NULL ) {
16958 #else
16959         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16960 #endif
16961             /* New style */
16962             char delim;
16963
16964             if( s_eval != NULL ) {
16965                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16966                     return text;
16967                 }
16968
16969                 if( delim != ']' ) {
16970                     return text;
16971                 }
16972             }
16973
16974             if( s_emt != NULL ) {
16975             }
16976                 return text;
16977         }
16978         else {
16979             /* We expect something like: [+|-]nnn.nn/dd */
16980             int score_lo = 0;
16981
16982             if(*text != '{') return text; // [HGM] braces: must be normal comment
16983
16984             sep = strchr( text, '/' );
16985             if( sep == NULL || sep < (text+4) ) {
16986                 return text;
16987             }
16988
16989             p = text;
16990             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16991             if(p[1] == '(') { // comment starts with PV
16992                p = strchr(p, ')'); // locate end of PV
16993                if(p == NULL || sep < p+5) return text;
16994                // at this point we have something like "{(.*) +0.23/6 ..."
16995                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16996                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16997                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16998             }
16999             time = -1; sec = -1; deci = -1;
17000             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
17001                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
17002                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
17003                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
17004                 return text;
17005             }
17006
17007             if( score_lo < 0 || score_lo >= 100 ) {
17008                 return text;
17009             }
17010
17011             if(sec >= 0) time = 600*time + 10*sec; else
17012             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
17013
17014             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17015
17016             /* [HGM] PV time: now locate end of PV info */
17017             while( *++sep >= '0' && *sep <= '9'); // strip depth
17018             if(time >= 0)
17019             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17020             if(sec >= 0)
17021             while( *++sep >= '0' && *sep <= '9'); // strip seconds
17022             if(deci >= 0)
17023             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17024             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17025         }
17026
17027         if( depth <= 0 ) {
17028             return text;
17029         }
17030
17031         if( time < 0 ) {
17032             time = -1;
17033         }
17034
17035         pvInfoList[index-1].depth = depth;
17036         pvInfoList[index-1].score = score;
17037         pvInfoList[index-1].time  = 10*time; // centi-sec
17038         if(*sep == '}') *sep = 0; else *--sep = '{';
17039         if(p != text) {
17040             while(*p++ = *sep++)
17041                                 ;
17042             sep = text;
17043         } // squeeze out space between PV and comment, and return both
17044     }
17045     return sep;
17046 }
17047
17048 void
17049 SendToProgram (char *message, ChessProgramState *cps)
17050 {
17051     int count, outCount, error;
17052     char buf[MSG_SIZ];
17053
17054     if (cps->pr == NoProc) return;
17055     Attention(cps);
17056
17057     if (appData.debugMode) {
17058         TimeMark now;
17059         GetTimeMark(&now);
17060         fprintf(debugFP, "%ld >%-6s: %s",
17061                 SubtractTimeMarks(&now, &programStartTime),
17062                 cps->which, message);
17063         if(serverFP)
17064             fprintf(serverFP, "%ld >%-6s: %s",
17065                 SubtractTimeMarks(&now, &programStartTime),
17066                 cps->which, message), fflush(serverFP);
17067     }
17068
17069     count = strlen(message);
17070     outCount = OutputToProcess(cps->pr, message, count, &error);
17071     if (outCount < count && !exiting
17072                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17073       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17074       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17075         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17076             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17077                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17078                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17079                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17080             } else {
17081                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17082                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17083                 gameInfo.result = res;
17084             }
17085             gameInfo.resultDetails = StrSave(buf);
17086         }
17087         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17088         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17089     }
17090 }
17091
17092 void
17093 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17094 {
17095     char *end_str;
17096     char buf[MSG_SIZ];
17097     ChessProgramState *cps = (ChessProgramState *)closure;
17098
17099     if (isr != cps->isr) return; /* Killed intentionally */
17100     if (count <= 0) {
17101         if (count == 0) {
17102             RemoveInputSource(cps->isr);
17103             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17104                     _(cps->which), cps->program);
17105             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17106             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17107                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17108                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17109                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17110                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17111                 } else {
17112                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17113                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17114                     gameInfo.result = res;
17115                 }
17116                 gameInfo.resultDetails = StrSave(buf);
17117             }
17118             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17119             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17120         } else {
17121             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17122                     _(cps->which), cps->program);
17123             RemoveInputSource(cps->isr);
17124
17125             /* [AS] Program is misbehaving badly... kill it */
17126             if( count == -2 ) {
17127                 DestroyChildProcess( cps->pr, 9 );
17128                 cps->pr = NoProc;
17129             }
17130
17131             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17132         }
17133         return;
17134     }
17135
17136     if ((end_str = strchr(message, '\r')) != NULL)
17137       *end_str = NULLCHAR;
17138     if ((end_str = strchr(message, '\n')) != NULL)
17139       *end_str = NULLCHAR;
17140
17141     if (appData.debugMode) {
17142         TimeMark now; int print = 1;
17143         char *quote = ""; char c; int i;
17144
17145         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17146                 char start = message[0];
17147                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17148                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17149                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17150                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17151                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17152                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17153                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17154                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17155                    sscanf(message, "hint: %c", &c)!=1 &&
17156                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17157                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17158                     print = (appData.engineComments >= 2);
17159                 }
17160                 message[0] = start; // restore original message
17161         }
17162         if(print) {
17163                 GetTimeMark(&now);
17164                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17165                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17166                         quote,
17167                         message);
17168                 if(serverFP)
17169                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17170                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17171                         quote,
17172                         message), fflush(serverFP);
17173         }
17174     }
17175
17176     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17177     if (appData.icsEngineAnalyze) {
17178         if (strstr(message, "whisper") != NULL ||
17179              strstr(message, "kibitz") != NULL ||
17180             strstr(message, "tellics") != NULL) return;
17181     }
17182
17183     HandleMachineMove(message, cps);
17184 }
17185
17186
17187 void
17188 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17189 {
17190     char buf[MSG_SIZ];
17191     int seconds;
17192
17193     if( timeControl_2 > 0 ) {
17194         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17195             tc = timeControl_2;
17196         }
17197     }
17198     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17199     inc /= cps->timeOdds;
17200     st  /= cps->timeOdds;
17201
17202     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17203
17204     if (st > 0) {
17205       /* Set exact time per move, normally using st command */
17206       if (cps->stKludge) {
17207         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17208         seconds = st % 60;
17209         if (seconds == 0) {
17210           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17211         } else {
17212           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17213         }
17214       } else {
17215         snprintf(buf, MSG_SIZ, "st %d\n", st);
17216       }
17217     } else {
17218       /* Set conventional or incremental time control, using level command */
17219       if (seconds == 0) {
17220         /* Note old gnuchess bug -- minutes:seconds used to not work.
17221            Fixed in later versions, but still avoid :seconds
17222            when seconds is 0. */
17223         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17224       } else {
17225         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17226                  seconds, inc/1000.);
17227       }
17228     }
17229     SendToProgram(buf, cps);
17230
17231     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17232     /* Orthogonally, limit search to given depth */
17233     if (sd > 0) {
17234       if (cps->sdKludge) {
17235         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17236       } else {
17237         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17238       }
17239       SendToProgram(buf, cps);
17240     }
17241
17242     if(cps->nps >= 0) { /* [HGM] nps */
17243         if(cps->supportsNPS == FALSE)
17244           cps->nps = -1; // don't use if engine explicitly says not supported!
17245         else {
17246           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17247           SendToProgram(buf, cps);
17248         }
17249     }
17250 }
17251
17252 ChessProgramState *
17253 WhitePlayer ()
17254 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17255 {
17256     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17257        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17258         return &second;
17259     return &first;
17260 }
17261
17262 void
17263 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17264 {
17265     char message[MSG_SIZ];
17266     long time, otime;
17267
17268     /* Note: this routine must be called when the clocks are stopped
17269        or when they have *just* been set or switched; otherwise
17270        it will be off by the time since the current tick started.
17271     */
17272     if (machineWhite) {
17273         time = whiteTimeRemaining / 10;
17274         otime = blackTimeRemaining / 10;
17275     } else {
17276         time = blackTimeRemaining / 10;
17277         otime = whiteTimeRemaining / 10;
17278     }
17279     /* [HGM] translate opponent's time by time-odds factor */
17280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17281
17282     if (time <= 0) time = 1;
17283     if (otime <= 0) otime = 1;
17284
17285     snprintf(message, MSG_SIZ, "time %ld\n", time);
17286     SendToProgram(message, cps);
17287
17288     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17289     SendToProgram(message, cps);
17290 }
17291
17292 char *
17293 EngineDefinedVariant (ChessProgramState *cps, int n)
17294 {   // return name of n-th unknown variant that engine supports
17295     static char buf[MSG_SIZ];
17296     char *p, *s = cps->variants;
17297     if(!s) return NULL;
17298     do { // parse string from variants feature
17299       VariantClass v;
17300         p = strchr(s, ',');
17301         if(p) *p = NULLCHAR;
17302       v = StringToVariant(s);
17303       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17304         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17305             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17306                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17307                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17308                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17309             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17310         }
17311         if(p) *p++ = ',';
17312         if(n < 0) return buf;
17313     } while(s = p);
17314     return NULL;
17315 }
17316
17317 int
17318 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17319 {
17320   char buf[MSG_SIZ];
17321   int len = strlen(name);
17322   int val;
17323
17324   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17325     (*p) += len + 1;
17326     sscanf(*p, "%d", &val);
17327     *loc = (val != 0);
17328     while (**p && **p != ' ')
17329       (*p)++;
17330     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17331     SendToProgram(buf, cps);
17332     return TRUE;
17333   }
17334   return FALSE;
17335 }
17336
17337 int
17338 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17339 {
17340   char buf[MSG_SIZ];
17341   int len = strlen(name);
17342   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17343     (*p) += len + 1;
17344     sscanf(*p, "%d", loc);
17345     while (**p && **p != ' ') (*p)++;
17346     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17347     SendToProgram(buf, cps);
17348     return TRUE;
17349   }
17350   return FALSE;
17351 }
17352
17353 int
17354 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17355 {
17356   char buf[MSG_SIZ];
17357   int len = strlen(name);
17358   if (strncmp((*p), name, len) == 0
17359       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17360     (*p) += len + 2;
17361     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17362     FREE(*loc); *loc = malloc(len);
17363     strncpy(*loc, *p, len);
17364     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17365     while (**p && **p != '\"') (*p)++;
17366     if (**p == '\"') (*p)++;
17367     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17368     SendToProgram(buf, cps);
17369     return TRUE;
17370   }
17371   return FALSE;
17372 }
17373
17374 int
17375 ParseOption (Option *opt, ChessProgramState *cps)
17376 // [HGM] options: process the string that defines an engine option, and determine
17377 // name, type, default value, and allowed value range
17378 {
17379         char *p, *q, buf[MSG_SIZ];
17380         int n, min = (-1)<<31, max = 1<<31, def;
17381
17382         opt->target = &opt->value;   // OK for spin/slider and checkbox
17383         if(p = strstr(opt->name, " -spin ")) {
17384             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17385             if(max < min) max = min; // enforce consistency
17386             if(def < min) def = min;
17387             if(def > max) def = max;
17388             opt->value = def;
17389             opt->min = min;
17390             opt->max = max;
17391             opt->type = Spin;
17392         } else if((p = strstr(opt->name, " -slider "))) {
17393             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17394             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17395             if(max < min) max = min; // enforce consistency
17396             if(def < min) def = min;
17397             if(def > max) def = max;
17398             opt->value = def;
17399             opt->min = min;
17400             opt->max = max;
17401             opt->type = Spin; // Slider;
17402         } else if((p = strstr(opt->name, " -string "))) {
17403             opt->textValue = p+9;
17404             opt->type = TextBox;
17405             opt->target = &opt->textValue;
17406         } else if((p = strstr(opt->name, " -file "))) {
17407             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17408             opt->target = opt->textValue = p+7;
17409             opt->type = FileName; // FileName;
17410             opt->target = &opt->textValue;
17411         } else if((p = strstr(opt->name, " -path "))) {
17412             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17413             opt->target = opt->textValue = p+7;
17414             opt->type = PathName; // PathName;
17415             opt->target = &opt->textValue;
17416         } else if(p = strstr(opt->name, " -check ")) {
17417             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17418             opt->value = (def != 0);
17419             opt->type = CheckBox;
17420         } else if(p = strstr(opt->name, " -combo ")) {
17421             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17422             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17423             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17424             opt->value = n = 0;
17425             while(q = StrStr(q, " /// ")) {
17426                 n++; *q = 0;    // count choices, and null-terminate each of them
17427                 q += 5;
17428                 if(*q == '*') { // remember default, which is marked with * prefix
17429                     q++;
17430                     opt->value = n;
17431                 }
17432                 cps->comboList[cps->comboCnt++] = q;
17433             }
17434             cps->comboList[cps->comboCnt++] = NULL;
17435             opt->max = n + 1;
17436             opt->type = ComboBox;
17437         } else if(p = strstr(opt->name, " -button")) {
17438             opt->type = Button;
17439         } else if(p = strstr(opt->name, " -save")) {
17440             opt->type = SaveButton;
17441         } else return FALSE;
17442         *p = 0; // terminate option name
17443         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17444         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17445         // now look if the command-line options define a setting for this engine option.
17446         if(cps->optionSettings && cps->optionSettings[0])
17447             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17448         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17449           snprintf(buf, MSG_SIZ, "option %s", p);
17450                 if(p = strstr(buf, ",")) *p = 0;
17451                 if(q = strchr(buf, '=')) switch(opt->type) {
17452                     case ComboBox:
17453                         for(n=0; n<opt->max; n++)
17454                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17455                         break;
17456                     case TextBox:
17457                     case FileName:
17458                     case PathName:
17459                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17460                         break;
17461                     case Spin:
17462                     case CheckBox:
17463                         opt->value = atoi(q+1);
17464                     default:
17465                         break;
17466                 }
17467                 strcat(buf, "\n");
17468                 SendToProgram(buf, cps);
17469         }
17470         return TRUE;
17471 }
17472
17473 void
17474 FeatureDone (ChessProgramState *cps, int val)
17475 {
17476   DelayedEventCallback cb = GetDelayedEvent();
17477   if ((cb == InitBackEnd3 && cps == &first) ||
17478       (cb == SettingsMenuIfReady && cps == &second) ||
17479       (cb == LoadEngine) || (cb == StartSecond) ||
17480       (cb == TwoMachinesEventIfReady)) {
17481     CancelDelayedEvent();
17482     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17483   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17484   cps->initDone = val;
17485   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17486 }
17487
17488 /* Parse feature command from engine */
17489 void
17490 ParseFeatures (char *args, ChessProgramState *cps)
17491 {
17492   char *p = args;
17493   char *q = NULL;
17494   int val;
17495   char buf[MSG_SIZ];
17496
17497   for (;;) {
17498     while (*p == ' ') p++;
17499     if (*p == NULLCHAR) return;
17500
17501     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17502     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17503     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17504     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17505     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17506     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17507     if (BoolFeature(&p, "reuse", &val, cps)) {
17508       /* Engine can disable reuse, but can't enable it if user said no */
17509       if (!val) cps->reuse = FALSE;
17510       continue;
17511     }
17512     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17513     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17514       if (gameMode == TwoMachinesPlay) {
17515         DisplayTwoMachinesTitle();
17516       } else {
17517         DisplayTitle("");
17518       }
17519       continue;
17520     }
17521     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17522     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17523     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17524     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17525     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17526     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17527     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17528     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17529     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17530     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17531     if (IntFeature(&p, "done", &val, cps)) {
17532       FeatureDone(cps, val);
17533       continue;
17534     }
17535     /* Added by Tord: */
17536     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17537     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17538     /* End of additions by Tord */
17539
17540     /* [HGM] added features: */
17541     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17542     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17543     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17544     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17545     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17546     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17547     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17548     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17549         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17550         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17551         FREE(cps->option[cps->nrOptions].name);
17552         cps->option[cps->nrOptions].name = q; q = NULL;
17553         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17554           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17555             SendToProgram(buf, cps);
17556             continue;
17557         }
17558         if(cps->nrOptions >= MAX_OPTIONS) {
17559             cps->nrOptions--;
17560             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17561             DisplayError(buf, 0);
17562         }
17563         continue;
17564     }
17565     /* End of additions by HGM */
17566
17567     /* unknown feature: complain and skip */
17568     q = p;
17569     while (*q && *q != '=') q++;
17570     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17571     SendToProgram(buf, cps);
17572     p = q;
17573     if (*p == '=') {
17574       p++;
17575       if (*p == '\"') {
17576         p++;
17577         while (*p && *p != '\"') p++;
17578         if (*p == '\"') p++;
17579       } else {
17580         while (*p && *p != ' ') p++;
17581       }
17582     }
17583   }
17584
17585 }
17586
17587 void
17588 PeriodicUpdatesEvent (int newState)
17589 {
17590     if (newState == appData.periodicUpdates)
17591       return;
17592
17593     appData.periodicUpdates=newState;
17594
17595     /* Display type changes, so update it now */
17596 //    DisplayAnalysis();
17597
17598     /* Get the ball rolling again... */
17599     if (newState) {
17600         AnalysisPeriodicEvent(1);
17601         StartAnalysisClock();
17602     }
17603 }
17604
17605 void
17606 PonderNextMoveEvent (int newState)
17607 {
17608     if (newState == appData.ponderNextMove) return;
17609     if (gameMode == EditPosition) EditPositionDone(TRUE);
17610     if (newState) {
17611         SendToProgram("hard\n", &first);
17612         if (gameMode == TwoMachinesPlay) {
17613             SendToProgram("hard\n", &second);
17614         }
17615     } else {
17616         SendToProgram("easy\n", &first);
17617         thinkOutput[0] = NULLCHAR;
17618         if (gameMode == TwoMachinesPlay) {
17619             SendToProgram("easy\n", &second);
17620         }
17621     }
17622     appData.ponderNextMove = newState;
17623 }
17624
17625 void
17626 NewSettingEvent (int option, int *feature, char *command, int value)
17627 {
17628     char buf[MSG_SIZ];
17629
17630     if (gameMode == EditPosition) EditPositionDone(TRUE);
17631     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17632     if(feature == NULL || *feature) SendToProgram(buf, &first);
17633     if (gameMode == TwoMachinesPlay) {
17634         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17635     }
17636 }
17637
17638 void
17639 ShowThinkingEvent ()
17640 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17641 {
17642     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17643     int newState = appData.showThinking
17644         // [HGM] thinking: other features now need thinking output as well
17645         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17646
17647     if (oldState == newState) return;
17648     oldState = newState;
17649     if (gameMode == EditPosition) EditPositionDone(TRUE);
17650     if (oldState) {
17651         SendToProgram("post\n", &first);
17652         if (gameMode == TwoMachinesPlay) {
17653             SendToProgram("post\n", &second);
17654         }
17655     } else {
17656         SendToProgram("nopost\n", &first);
17657         thinkOutput[0] = NULLCHAR;
17658         if (gameMode == TwoMachinesPlay) {
17659             SendToProgram("nopost\n", &second);
17660         }
17661     }
17662 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17663 }
17664
17665 void
17666 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17667 {
17668   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17669   if (pr == NoProc) return;
17670   AskQuestion(title, question, replyPrefix, pr);
17671 }
17672
17673 void
17674 TypeInEvent (char firstChar)
17675 {
17676     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17677         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17678         gameMode == AnalyzeMode || gameMode == EditGame ||
17679         gameMode == EditPosition || gameMode == IcsExamining ||
17680         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17681         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17682                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17683                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17684         gameMode == Training) PopUpMoveDialog(firstChar);
17685 }
17686
17687 void
17688 TypeInDoneEvent (char *move)
17689 {
17690         Board board;
17691         int n, fromX, fromY, toX, toY;
17692         char promoChar;
17693         ChessMove moveType;
17694
17695         // [HGM] FENedit
17696         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17697                 EditPositionPasteFEN(move);
17698                 return;
17699         }
17700         // [HGM] movenum: allow move number to be typed in any mode
17701         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17702           ToNrEvent(2*n-1);
17703           return;
17704         }
17705         // undocumented kludge: allow command-line option to be typed in!
17706         // (potentially fatal, and does not implement the effect of the option.)
17707         // should only be used for options that are values on which future decisions will be made,
17708         // and definitely not on options that would be used during initialization.
17709         if(strstr(move, "!!! -") == move) {
17710             ParseArgsFromString(move+4);
17711             return;
17712         }
17713
17714       if (gameMode != EditGame && currentMove != forwardMostMove &&
17715         gameMode != Training) {
17716         DisplayMoveError(_("Displayed move is not current"));
17717       } else {
17718         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17719           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17720         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17721         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17722           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17723           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17724         } else {
17725           DisplayMoveError(_("Could not parse move"));
17726         }
17727       }
17728 }
17729
17730 void
17731 DisplayMove (int moveNumber)
17732 {
17733     char message[MSG_SIZ];
17734     char res[MSG_SIZ];
17735     char cpThinkOutput[MSG_SIZ];
17736
17737     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17738
17739     if (moveNumber == forwardMostMove - 1 ||
17740         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17741
17742         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17743
17744         if (strchr(cpThinkOutput, '\n')) {
17745             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17746         }
17747     } else {
17748         *cpThinkOutput = NULLCHAR;
17749     }
17750
17751     /* [AS] Hide thinking from human user */
17752     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17753         *cpThinkOutput = NULLCHAR;
17754         if( thinkOutput[0] != NULLCHAR ) {
17755             int i;
17756
17757             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17758                 cpThinkOutput[i] = '.';
17759             }
17760             cpThinkOutput[i] = NULLCHAR;
17761             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17762         }
17763     }
17764
17765     if (moveNumber == forwardMostMove - 1 &&
17766         gameInfo.resultDetails != NULL) {
17767         if (gameInfo.resultDetails[0] == NULLCHAR) {
17768           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17769         } else {
17770           snprintf(res, MSG_SIZ, " {%s} %s",
17771                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17772         }
17773     } else {
17774         res[0] = NULLCHAR;
17775     }
17776
17777     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17778         DisplayMessage(res, cpThinkOutput);
17779     } else {
17780       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17781                 WhiteOnMove(moveNumber) ? " " : ".. ",
17782                 parseList[moveNumber], res);
17783         DisplayMessage(message, cpThinkOutput);
17784     }
17785 }
17786
17787 void
17788 DisplayComment (int moveNumber, char *text)
17789 {
17790     char title[MSG_SIZ];
17791
17792     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17793       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17794     } else {
17795       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17796               WhiteOnMove(moveNumber) ? " " : ".. ",
17797               parseList[moveNumber]);
17798     }
17799     if (text != NULL && (appData.autoDisplayComment || commentUp))
17800         CommentPopUp(title, text);
17801 }
17802
17803 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17804  * might be busy thinking or pondering.  It can be omitted if your
17805  * gnuchess is configured to stop thinking immediately on any user
17806  * input.  However, that gnuchess feature depends on the FIONREAD
17807  * ioctl, which does not work properly on some flavors of Unix.
17808  */
17809 void
17810 Attention (ChessProgramState *cps)
17811 {
17812 #if ATTENTION
17813     if (!cps->useSigint) return;
17814     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17815     switch (gameMode) {
17816       case MachinePlaysWhite:
17817       case MachinePlaysBlack:
17818       case TwoMachinesPlay:
17819       case IcsPlayingWhite:
17820       case IcsPlayingBlack:
17821       case AnalyzeMode:
17822       case AnalyzeFile:
17823         /* Skip if we know it isn't thinking */
17824         if (!cps->maybeThinking) return;
17825         if (appData.debugMode)
17826           fprintf(debugFP, "Interrupting %s\n", cps->which);
17827         InterruptChildProcess(cps->pr);
17828         cps->maybeThinking = FALSE;
17829         break;
17830       default:
17831         break;
17832     }
17833 #endif /*ATTENTION*/
17834 }
17835
17836 int
17837 CheckFlags ()
17838 {
17839     if (whiteTimeRemaining <= 0) {
17840         if (!whiteFlag) {
17841             whiteFlag = TRUE;
17842             if (appData.icsActive) {
17843                 if (appData.autoCallFlag &&
17844                     gameMode == IcsPlayingBlack && !blackFlag) {
17845                   SendToICS(ics_prefix);
17846                   SendToICS("flag\n");
17847                 }
17848             } else {
17849                 if (blackFlag) {
17850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17851                 } else {
17852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17853                     if (appData.autoCallFlag) {
17854                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17855                         return TRUE;
17856                     }
17857                 }
17858             }
17859         }
17860     }
17861     if (blackTimeRemaining <= 0) {
17862         if (!blackFlag) {
17863             blackFlag = TRUE;
17864             if (appData.icsActive) {
17865                 if (appData.autoCallFlag &&
17866                     gameMode == IcsPlayingWhite && !whiteFlag) {
17867                   SendToICS(ics_prefix);
17868                   SendToICS("flag\n");
17869                 }
17870             } else {
17871                 if (whiteFlag) {
17872                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17873                 } else {
17874                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17875                     if (appData.autoCallFlag) {
17876                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17877                         return TRUE;
17878                     }
17879                 }
17880             }
17881         }
17882     }
17883     return FALSE;
17884 }
17885
17886 void
17887 CheckTimeControl ()
17888 {
17889     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17890         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17891
17892     /*
17893      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17894      */
17895     if ( !WhiteOnMove(forwardMostMove) ) {
17896         /* White made time control */
17897         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17898         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17899         /* [HGM] time odds: correct new time quota for time odds! */
17900                                             / WhitePlayer()->timeOdds;
17901         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17902     } else {
17903         lastBlack -= blackTimeRemaining;
17904         /* Black made time control */
17905         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17906                                             / WhitePlayer()->other->timeOdds;
17907         lastWhite = whiteTimeRemaining;
17908     }
17909 }
17910
17911 void
17912 DisplayBothClocks ()
17913 {
17914     int wom = gameMode == EditPosition ?
17915       !blackPlaysFirst : WhiteOnMove(currentMove);
17916     DisplayWhiteClock(whiteTimeRemaining, wom);
17917     DisplayBlackClock(blackTimeRemaining, !wom);
17918 }
17919
17920
17921 /* Timekeeping seems to be a portability nightmare.  I think everyone
17922    has ftime(), but I'm really not sure, so I'm including some ifdefs
17923    to use other calls if you don't.  Clocks will be less accurate if
17924    you have neither ftime nor gettimeofday.
17925 */
17926
17927 /* VS 2008 requires the #include outside of the function */
17928 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17929 #include <sys/timeb.h>
17930 #endif
17931
17932 /* Get the current time as a TimeMark */
17933 void
17934 GetTimeMark (TimeMark *tm)
17935 {
17936 #if HAVE_GETTIMEOFDAY
17937
17938     struct timeval timeVal;
17939     struct timezone timeZone;
17940
17941     gettimeofday(&timeVal, &timeZone);
17942     tm->sec = (long) timeVal.tv_sec;
17943     tm->ms = (int) (timeVal.tv_usec / 1000L);
17944
17945 #else /*!HAVE_GETTIMEOFDAY*/
17946 #if HAVE_FTIME
17947
17948 // include <sys/timeb.h> / moved to just above start of function
17949     struct timeb timeB;
17950
17951     ftime(&timeB);
17952     tm->sec = (long) timeB.time;
17953     tm->ms = (int) timeB.millitm;
17954
17955 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17956     tm->sec = (long) time(NULL);
17957     tm->ms = 0;
17958 #endif
17959 #endif
17960 }
17961
17962 /* Return the difference in milliseconds between two
17963    time marks.  We assume the difference will fit in a long!
17964 */
17965 long
17966 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17967 {
17968     return 1000L*(tm2->sec - tm1->sec) +
17969            (long) (tm2->ms - tm1->ms);
17970 }
17971
17972
17973 /*
17974  * Code to manage the game clocks.
17975  *
17976  * In tournament play, black starts the clock and then white makes a move.
17977  * We give the human user a slight advantage if he is playing white---the
17978  * clocks don't run until he makes his first move, so it takes zero time.
17979  * Also, we don't account for network lag, so we could get out of sync
17980  * with GNU Chess's clock -- but then, referees are always right.
17981  */
17982
17983 static TimeMark tickStartTM;
17984 static long intendedTickLength;
17985
17986 long
17987 NextTickLength (long timeRemaining)
17988 {
17989     long nominalTickLength, nextTickLength;
17990
17991     if (timeRemaining > 0L && timeRemaining <= 10000L)
17992       nominalTickLength = 100L;
17993     else
17994       nominalTickLength = 1000L;
17995     nextTickLength = timeRemaining % nominalTickLength;
17996     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17997
17998     return nextTickLength;
17999 }
18000
18001 /* Adjust clock one minute up or down */
18002 void
18003 AdjustClock (Boolean which, int dir)
18004 {
18005     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
18006     if(which) blackTimeRemaining += 60000*dir;
18007     else      whiteTimeRemaining += 60000*dir;
18008     DisplayBothClocks();
18009     adjustedClock = TRUE;
18010 }
18011
18012 /* Stop clocks and reset to a fresh time control */
18013 void
18014 ResetClocks ()
18015 {
18016     (void) StopClockTimer();
18017     if (appData.icsActive) {
18018         whiteTimeRemaining = blackTimeRemaining = 0;
18019     } else if (searchTime) {
18020         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18021         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18022     } else { /* [HGM] correct new time quote for time odds */
18023         whiteTC = blackTC = fullTimeControlString;
18024         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18025         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18026     }
18027     if (whiteFlag || blackFlag) {
18028         DisplayTitle("");
18029         whiteFlag = blackFlag = FALSE;
18030     }
18031     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18032     DisplayBothClocks();
18033     adjustedClock = FALSE;
18034 }
18035
18036 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18037
18038 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18039
18040 /* Decrement running clock by amount of time that has passed */
18041 void
18042 DecrementClocks ()
18043 {
18044     long tRemaining;
18045     long lastTickLength, fudge;
18046     TimeMark now;
18047
18048     if (!appData.clockMode) return;
18049     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18050
18051     GetTimeMark(&now);
18052
18053     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18054
18055     /* Fudge if we woke up a little too soon */
18056     fudge = intendedTickLength - lastTickLength;
18057     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18058
18059     if (WhiteOnMove(forwardMostMove)) {
18060         if(whiteNPS >= 0) lastTickLength = 0;
18061          tRemaining = whiteTimeRemaining -= lastTickLength;
18062         if( tRemaining < 0 && !appData.icsActive) {
18063             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18064             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18065                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18066                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18067             }
18068         }
18069         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18070         DisplayWhiteClock(whiteTimeRemaining - fudge,
18071                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18072         timeSuffix = 0;
18073     } else {
18074         if(blackNPS >= 0) lastTickLength = 0;
18075          tRemaining = blackTimeRemaining -= lastTickLength;
18076         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18077             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18078             if(suddenDeath) {
18079                 blackStartMove = forwardMostMove;
18080                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18081             }
18082         }
18083         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18084         DisplayBlackClock(blackTimeRemaining - fudge,
18085                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18086         timeSuffix = 0;
18087     }
18088     if (CheckFlags()) return;
18089
18090     if(twoBoards) { // count down secondary board's clocks as well
18091         activePartnerTime -= lastTickLength;
18092         partnerUp = 1;
18093         if(activePartner == 'W')
18094             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18095         else
18096             DisplayBlackClock(activePartnerTime, TRUE);
18097         partnerUp = 0;
18098     }
18099
18100     tickStartTM = now;
18101     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18102     StartClockTimer(intendedTickLength);
18103
18104     /* if the time remaining has fallen below the alarm threshold, sound the
18105      * alarm. if the alarm has sounded and (due to a takeback or time control
18106      * with increment) the time remaining has increased to a level above the
18107      * threshold, reset the alarm so it can sound again.
18108      */
18109
18110     if (appData.icsActive && appData.icsAlarm) {
18111
18112         /* make sure we are dealing with the user's clock */
18113         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18114                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18115            )) return;
18116
18117         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18118             alarmSounded = FALSE;
18119         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18120             PlayAlarmSound();
18121             alarmSounded = TRUE;
18122         }
18123     }
18124 }
18125
18126
18127 /* A player has just moved, so stop the previously running
18128    clock and (if in clock mode) start the other one.
18129    We redisplay both clocks in case we're in ICS mode, because
18130    ICS gives us an update to both clocks after every move.
18131    Note that this routine is called *after* forwardMostMove
18132    is updated, so the last fractional tick must be subtracted
18133    from the color that is *not* on move now.
18134 */
18135 void
18136 SwitchClocks (int newMoveNr)
18137 {
18138     long lastTickLength;
18139     TimeMark now;
18140     int flagged = FALSE;
18141
18142     GetTimeMark(&now);
18143
18144     if (StopClockTimer() && appData.clockMode) {
18145         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18146         if (!WhiteOnMove(forwardMostMove)) {
18147             if(blackNPS >= 0) lastTickLength = 0;
18148             blackTimeRemaining -= lastTickLength;
18149            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18150 //         if(pvInfoList[forwardMostMove].time == -1)
18151                  pvInfoList[forwardMostMove].time =               // use GUI time
18152                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18153         } else {
18154            if(whiteNPS >= 0) lastTickLength = 0;
18155            whiteTimeRemaining -= lastTickLength;
18156            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18157 //         if(pvInfoList[forwardMostMove].time == -1)
18158                  pvInfoList[forwardMostMove].time =
18159                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18160         }
18161         flagged = CheckFlags();
18162     }
18163     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18164     CheckTimeControl();
18165
18166     if (flagged || !appData.clockMode) return;
18167
18168     switch (gameMode) {
18169       case MachinePlaysBlack:
18170       case MachinePlaysWhite:
18171       case BeginningOfGame:
18172         if (pausing) return;
18173         break;
18174
18175       case EditGame:
18176       case PlayFromGameFile:
18177       case IcsExamining:
18178         return;
18179
18180       default:
18181         break;
18182     }
18183
18184     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18185         if(WhiteOnMove(forwardMostMove))
18186              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18187         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18188     }
18189
18190     tickStartTM = now;
18191     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18192       whiteTimeRemaining : blackTimeRemaining);
18193     StartClockTimer(intendedTickLength);
18194 }
18195
18196
18197 /* Stop both clocks */
18198 void
18199 StopClocks ()
18200 {
18201     long lastTickLength;
18202     TimeMark now;
18203
18204     if (!StopClockTimer()) return;
18205     if (!appData.clockMode) return;
18206
18207     GetTimeMark(&now);
18208
18209     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18210     if (WhiteOnMove(forwardMostMove)) {
18211         if(whiteNPS >= 0) lastTickLength = 0;
18212         whiteTimeRemaining -= lastTickLength;
18213         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18214     } else {
18215         if(blackNPS >= 0) lastTickLength = 0;
18216         blackTimeRemaining -= lastTickLength;
18217         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18218     }
18219     CheckFlags();
18220 }
18221
18222 /* Start clock of player on move.  Time may have been reset, so
18223    if clock is already running, stop and restart it. */
18224 void
18225 StartClocks ()
18226 {
18227     (void) StopClockTimer(); /* in case it was running already */
18228     DisplayBothClocks();
18229     if (CheckFlags()) return;
18230
18231     if (!appData.clockMode) return;
18232     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18233
18234     GetTimeMark(&tickStartTM);
18235     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18236       whiteTimeRemaining : blackTimeRemaining);
18237
18238    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18239     whiteNPS = blackNPS = -1;
18240     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18241        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18242         whiteNPS = first.nps;
18243     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18244        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18245         blackNPS = first.nps;
18246     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18247         whiteNPS = second.nps;
18248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18249         blackNPS = second.nps;
18250     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18251
18252     StartClockTimer(intendedTickLength);
18253 }
18254
18255 char *
18256 TimeString (long ms)
18257 {
18258     long second, minute, hour, day;
18259     char *sign = "";
18260     static char buf[40], moveTime[8];
18261
18262     if (ms > 0 && ms <= 9900) {
18263       /* convert milliseconds to tenths, rounding up */
18264       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18265
18266       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18267       return buf;
18268     }
18269
18270     /* convert milliseconds to seconds, rounding up */
18271     /* use floating point to avoid strangeness of integer division
18272        with negative dividends on many machines */
18273     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18274
18275     if (second < 0) {
18276         sign = "-";
18277         second = -second;
18278     }
18279
18280     day = second / (60 * 60 * 24);
18281     second = second % (60 * 60 * 24);
18282     hour = second / (60 * 60);
18283     second = second % (60 * 60);
18284     minute = second / 60;
18285     second = second % 60;
18286
18287     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18288     else *moveTime = NULLCHAR;
18289
18290     if (day > 0)
18291       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18292               sign, day, hour, minute, second, moveTime);
18293     else if (hour > 0)
18294       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18295     else
18296       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18297
18298     return buf;
18299 }
18300
18301
18302 /*
18303  * This is necessary because some C libraries aren't ANSI C compliant yet.
18304  */
18305 char *
18306 StrStr (char *string, char *match)
18307 {
18308     int i, length;
18309
18310     length = strlen(match);
18311
18312     for (i = strlen(string) - length; i >= 0; i--, string++)
18313       if (!strncmp(match, string, length))
18314         return string;
18315
18316     return NULL;
18317 }
18318
18319 char *
18320 StrCaseStr (char *string, char *match)
18321 {
18322     int i, j, length;
18323
18324     length = strlen(match);
18325
18326     for (i = strlen(string) - length; i >= 0; i--, string++) {
18327         for (j = 0; j < length; j++) {
18328             if (ToLower(match[j]) != ToLower(string[j]))
18329               break;
18330         }
18331         if (j == length) return string;
18332     }
18333
18334     return NULL;
18335 }
18336
18337 #ifndef _amigados
18338 int
18339 StrCaseCmp (char *s1, char *s2)
18340 {
18341     char c1, c2;
18342
18343     for (;;) {
18344         c1 = ToLower(*s1++);
18345         c2 = ToLower(*s2++);
18346         if (c1 > c2) return 1;
18347         if (c1 < c2) return -1;
18348         if (c1 == NULLCHAR) return 0;
18349     }
18350 }
18351
18352
18353 int
18354 ToLower (int c)
18355 {
18356     return isupper(c) ? tolower(c) : c;
18357 }
18358
18359
18360 int
18361 ToUpper (int c)
18362 {
18363     return islower(c) ? toupper(c) : c;
18364 }
18365 #endif /* !_amigados    */
18366
18367 char *
18368 StrSave (char *s)
18369 {
18370   char *ret;
18371
18372   if ((ret = (char *) malloc(strlen(s) + 1)))
18373     {
18374       safeStrCpy(ret, s, strlen(s)+1);
18375     }
18376   return ret;
18377 }
18378
18379 char *
18380 StrSavePtr (char *s, char **savePtr)
18381 {
18382     if (*savePtr) {
18383         free(*savePtr);
18384     }
18385     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18386       safeStrCpy(*savePtr, s, strlen(s)+1);
18387     }
18388     return(*savePtr);
18389 }
18390
18391 char *
18392 PGNDate ()
18393 {
18394     time_t clock;
18395     struct tm *tm;
18396     char buf[MSG_SIZ];
18397
18398     clock = time((time_t *)NULL);
18399     tm = localtime(&clock);
18400     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18401             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18402     return StrSave(buf);
18403 }
18404
18405
18406 char *
18407 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18408 {
18409     int i, j, fromX, fromY, toX, toY;
18410     int whiteToPlay, haveRights = nrCastlingRights;
18411     char buf[MSG_SIZ];
18412     char *p, *q;
18413     int emptycount;
18414     ChessSquare piece;
18415
18416     whiteToPlay = (gameMode == EditPosition) ?
18417       !blackPlaysFirst : (move % 2 == 0);
18418     p = buf;
18419
18420     /* Piece placement data */
18421     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18422         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18423         emptycount = 0;
18424         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18425             if (boards[move][i][j] == EmptySquare) {
18426                 emptycount++;
18427             } else { ChessSquare piece = boards[move][i][j];
18428                 if (emptycount > 0) {
18429                     if(emptycount<10) /* [HGM] can be >= 10 */
18430                         *p++ = '0' + emptycount;
18431                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18432                     emptycount = 0;
18433                 }
18434                 if(PieceToChar(piece) == '+') {
18435                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18436                     *p++ = '+';
18437                     piece = (ChessSquare)(CHUDEMOTED(piece));
18438                 }
18439                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18440                 if(*p = PieceSuffix(piece)) p++;
18441                 if(p[-1] == '~') {
18442                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18443                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18444                     *p++ = '~';
18445                 }
18446             }
18447         }
18448         if (emptycount > 0) {
18449             if(emptycount<10) /* [HGM] can be >= 10 */
18450                 *p++ = '0' + emptycount;
18451             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18452             emptycount = 0;
18453         }
18454         *p++ = '/';
18455     }
18456     *(p - 1) = ' ';
18457
18458     /* [HGM] print Crazyhouse or Shogi holdings */
18459     if( gameInfo.holdingsWidth ) {
18460         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18461         q = p;
18462         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18463             piece = boards[move][i][BOARD_WIDTH-1];
18464             if( piece != EmptySquare )
18465               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18466                   *p++ = PieceToChar(piece);
18467         }
18468         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18469             piece = boards[move][handSize-i-1][0];
18470             if( piece != EmptySquare )
18471               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18472                   *p++ = PieceToChar(piece);
18473         }
18474
18475         if( q == p ) *p++ = '-';
18476         *p++ = ']';
18477         *p++ = ' ';
18478     }
18479
18480     /* Active color */
18481     *p++ = whiteToPlay ? 'w' : 'b';
18482     *p++ = ' ';
18483
18484   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18485     haveRights = 0; q = p;
18486     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18487       piece = boards[move][0][i];
18488       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18489         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18490       }
18491     }
18492     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18493       piece = boards[move][BOARD_HEIGHT-1][i];
18494       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18495         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18496       }
18497     }
18498     if(p == q) *p++ = '-';
18499     *p++ = ' ';
18500   }
18501
18502   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18503     while(*p++ = *q++)
18504                       ;
18505     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18506   } else {
18507   if(haveRights) {
18508      int handW=0, handB=0;
18509      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18510         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18511         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18512      }
18513      q = p;
18514      if(appData.fischerCastling) {
18515         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18516            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18517                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18518         } else {
18519        /* [HGM] write directly from rights */
18520            if(boards[move][CASTLING][2] != NoRights &&
18521               boards[move][CASTLING][0] != NoRights   )
18522                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18523            if(boards[move][CASTLING][2] != NoRights &&
18524               boards[move][CASTLING][1] != NoRights   )
18525                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18526         }
18527         if(handB) {
18528            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18529                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18530         } else {
18531            if(boards[move][CASTLING][5] != NoRights &&
18532               boards[move][CASTLING][3] != NoRights   )
18533                 *p++ = boards[move][CASTLING][3] + AAA;
18534            if(boards[move][CASTLING][5] != NoRights &&
18535               boards[move][CASTLING][4] != NoRights   )
18536                 *p++ = boards[move][CASTLING][4] + AAA;
18537         }
18538      } else {
18539
18540         /* [HGM] write true castling rights */
18541         if( nrCastlingRights == 6 ) {
18542             int q, k=0;
18543             if(boards[move][CASTLING][0] != NoRights &&
18544                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18545             q = (boards[move][CASTLING][1] != NoRights &&
18546                  boards[move][CASTLING][2] != NoRights  );
18547             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18548                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18549                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18550                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18551             }
18552             if(q) *p++ = 'Q';
18553             k = 0;
18554             if(boards[move][CASTLING][3] != NoRights &&
18555                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18556             q = (boards[move][CASTLING][4] != NoRights &&
18557                  boards[move][CASTLING][5] != NoRights  );
18558             if(handB) {
18559                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18560                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18561                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18562             }
18563             if(q) *p++ = 'q';
18564         }
18565      }
18566      if (q == p) *p++ = '-'; /* No castling rights */
18567      *p++ = ' ';
18568   }
18569
18570   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18571      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18572      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18573     /* En passant target square */
18574     if (move > backwardMostMove) {
18575         fromX = moveList[move - 1][0] - AAA;
18576         fromY = moveList[move - 1][1] - ONE;
18577         toX = moveList[move - 1][2] - AAA;
18578         toY = moveList[move - 1][3] - ONE;
18579         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18580             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18581             /* 2-square pawn move just happened */
18582             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18583             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18584             if(gameInfo.variant == VariantBerolina) {
18585                 *p++ = toX + AAA;
18586                 *p++ = toY + ONE;
18587             }
18588         } else {
18589             *p++ = '-';
18590         }
18591     } else if(move == backwardMostMove) {
18592         // [HGM] perhaps we should always do it like this, and forget the above?
18593         if((signed char)boards[move][EP_STATUS] >= 0) {
18594             *p++ = boards[move][EP_STATUS] + AAA;
18595             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18596         } else {
18597             *p++ = '-';
18598         }
18599     } else {
18600         *p++ = '-';
18601     }
18602     *p++ = ' ';
18603   }
18604   }
18605
18606     i = boards[move][CHECK_COUNT];
18607     if(i) {
18608         sprintf(p, "%d+%d ", i&255, i>>8);
18609         while(*p) p++;
18610     }
18611
18612     if(moveCounts)
18613     {   int i = 0, j=move;
18614
18615         /* [HGM] find reversible plies */
18616         if (appData.debugMode) { int k;
18617             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18618             for(k=backwardMostMove; k<=forwardMostMove; k++)
18619                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18620
18621         }
18622
18623         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18624         if( j == backwardMostMove ) i += initialRulePlies;
18625         sprintf(p, "%d ", i);
18626         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18627
18628         /* Fullmove number */
18629         sprintf(p, "%d", (move / 2) + 1);
18630     } else *--p = NULLCHAR;
18631
18632     return StrSave(buf);
18633 }
18634
18635 Boolean
18636 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18637 {
18638     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18639     char *p, c;
18640     int emptycount, virgin[BOARD_FILES];
18641     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18642
18643     p = fen;
18644
18645     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18646
18647     /* Piece placement data */
18648     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18649         j = 0;
18650         for (;;) {
18651             if (*p == '/' || *p == ' ' || *p == '[' ) {
18652                 if(j > w) w = j;
18653                 emptycount = gameInfo.boardWidth - j;
18654                 while (emptycount--)
18655                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18656                 if (*p == '/') p++;
18657                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18658                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18659                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18660                     }
18661                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18662                 }
18663                 break;
18664 #if(BOARD_FILES >= 10)*0
18665             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18666                 p++; emptycount=10;
18667                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18668                 while (emptycount--)
18669                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18670 #endif
18671             } else if (*p == '*') {
18672                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18673             } else if (isdigit(*p)) {
18674                 emptycount = *p++ - '0';
18675                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18676                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18677                 while (emptycount--)
18678                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18679             } else if (*p == '<') {
18680                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18681                 else if (i != 0 || !shuffle) return FALSE;
18682                 p++;
18683             } else if (shuffle && *p == '>') {
18684                 p++; // for now ignore closing shuffle range, and assume rank-end
18685             } else if (*p == '?') {
18686                 if (j >= gameInfo.boardWidth) return FALSE;
18687                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18688                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18689             } else if (*p == '+' || isalpha(*p)) {
18690                 char *q, *s = SUFFIXES;
18691                 if (j >= gameInfo.boardWidth) return FALSE;
18692                 if(*p=='+') {
18693                     char c = *++p;
18694                     if(q = strchr(s, p[1])) p++;
18695                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18696                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18697                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18698                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18699                 } else {
18700                     char c = *p++;
18701                     if(q = strchr(s, *p)) p++;
18702                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18703                 }
18704
18705                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18706                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18707                     piece = (ChessSquare) (PROMOTED(piece));
18708                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18709                     p++;
18710                 }
18711                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18712                 if(piece == king) wKingRank = i;
18713                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18714             } else {
18715                 return FALSE;
18716             }
18717         }
18718     }
18719     while (*p == '/' || *p == ' ') p++;
18720
18721     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18722
18723     /* [HGM] by default clear Crazyhouse holdings, if present */
18724     if(gameInfo.holdingsWidth) {
18725        for(i=0; i<handSize; i++) {
18726            board[i][0]             = EmptySquare; /* black holdings */
18727            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18728            board[i][1]             = (ChessSquare) 0; /* black counts */
18729            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18730        }
18731     }
18732
18733     /* [HGM] look for Crazyhouse holdings here */
18734     while(*p==' ') p++;
18735     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18736         int swap=0, wcnt=0, bcnt=0;
18737         if(*p == '[') p++;
18738         if(*p == '<') swap++, p++;
18739         if(*p == '-' ) p++; /* empty holdings */ else {
18740             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18741             /* if we would allow FEN reading to set board size, we would   */
18742             /* have to add holdings and shift the board read so far here   */
18743             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18744                 p++;
18745                 if((int) piece >= (int) BlackPawn ) {
18746                     i = (int)piece - (int)BlackPawn;
18747                     i = PieceToNumber((ChessSquare)i);
18748                     if( i >= gameInfo.holdingsSize ) return FALSE;
18749                     board[handSize-1-i][0] = piece; /* black holdings */
18750                     board[handSize-1-i][1]++;       /* black counts   */
18751                     bcnt++;
18752                 } else {
18753                     i = (int)piece - (int)WhitePawn;
18754                     i = PieceToNumber((ChessSquare)i);
18755                     if( i >= gameInfo.holdingsSize ) return FALSE;
18756                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18757                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18758                     wcnt++;
18759                 }
18760             }
18761             if(subst) { // substitute back-rank question marks by holdings pieces
18762                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18763                     int k, m, n = bcnt + 1;
18764                     if(board[0][j] == ClearBoard) {
18765                         if(!wcnt) return FALSE;
18766                         n = rand() % wcnt;
18767                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18768                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18769                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18770                             break;
18771                         }
18772                     }
18773                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18774                         if(!bcnt) return FALSE;
18775                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18776                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18777                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18778                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18779                             break;
18780                         }
18781                     }
18782                 }
18783                 subst = 0;
18784             }
18785         }
18786         if(*p == ']') p++;
18787     }
18788
18789     if(subst) return FALSE; // substitution requested, but no holdings
18790
18791     while(*p == ' ') p++;
18792
18793     /* Active color */
18794     c = *p++;
18795     if(appData.colorNickNames) {
18796       if( c == appData.colorNickNames[0] ) c = 'w'; else
18797       if( c == appData.colorNickNames[1] ) c = 'b';
18798     }
18799     switch (c) {
18800       case 'w':
18801         *blackPlaysFirst = FALSE;
18802         break;
18803       case 'b':
18804         *blackPlaysFirst = TRUE;
18805         break;
18806       default:
18807         return FALSE;
18808     }
18809
18810     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18811     /* return the extra info in global variiables             */
18812
18813     while(*p==' ') p++;
18814
18815     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18816         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18817         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18818     }
18819
18820     /* set defaults in case FEN is incomplete */
18821     board[EP_STATUS] = EP_UNKNOWN;
18822     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18823     for(i=0; i<nrCastlingRights; i++ ) {
18824         board[CASTLING][i] =
18825             appData.fischerCastling ? NoRights : initialRights[i];
18826     }   /* assume possible unless obviously impossible */
18827     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18828     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18829     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18830                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18831     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18832     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18833     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18834                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18835     FENrulePlies = 0;
18836
18837     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18838       char *q = p;
18839       int w=0, b=0;
18840       while(isalpha(*p)) {
18841         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18842         if(islower(*p)) b |= 1 << (*p++ - 'a');
18843       }
18844       if(*p == '-') p++;
18845       if(p != q) {
18846         board[TOUCHED_W] = ~w;
18847         board[TOUCHED_B] = ~b;
18848         while(*p == ' ') p++;
18849       }
18850     } else
18851
18852     if(nrCastlingRights) {
18853       int fischer = 0;
18854       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18855       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18856           /* castling indicator present, so default becomes no castlings */
18857           for(i=0; i<nrCastlingRights; i++ ) {
18858                  board[CASTLING][i] = NoRights;
18859           }
18860       }
18861       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18862              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18863              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18864              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18865         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18866
18867         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18868             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18869             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18870         }
18871         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18872             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18873         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18874                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18875         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18876                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18877         switch(c) {
18878           case'K':
18879               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18880               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18881               board[CASTLING][2] = whiteKingFile;
18882               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18883               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18884               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18885               break;
18886           case'Q':
18887               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18888               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18889               board[CASTLING][2] = whiteKingFile;
18890               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18891               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18892               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18893               break;
18894           case'k':
18895               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18896               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18897               board[CASTLING][5] = blackKingFile;
18898               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18899               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18900               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18901               break;
18902           case'q':
18903               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18904               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18905               board[CASTLING][5] = blackKingFile;
18906               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18907               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18908               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18909           case '-':
18910               break;
18911           default: /* FRC castlings */
18912               if(c >= 'a') { /* black rights */
18913                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18914                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18915                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18916                   if(i == BOARD_RGHT) break;
18917                   board[CASTLING][5] = i;
18918                   c -= AAA;
18919                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18920                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18921                   if(c > i)
18922                       board[CASTLING][3] = c;
18923                   else
18924                       board[CASTLING][4] = c;
18925               } else { /* white rights */
18926                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18927                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18928                     if(board[0][i] == WhiteKing) break;
18929                   if(i == BOARD_RGHT) break;
18930                   board[CASTLING][2] = i;
18931                   c -= AAA - 'a' + 'A';
18932                   if(board[0][c] >= WhiteKing) break;
18933                   if(c > i)
18934                       board[CASTLING][0] = c;
18935                   else
18936                       board[CASTLING][1] = c;
18937               }
18938         }
18939       }
18940       for(i=0; i<nrCastlingRights; i++)
18941         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18942       if(gameInfo.variant == VariantSChess)
18943         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18944       if(fischer && shuffle) appData.fischerCastling = TRUE;
18945     if (appData.debugMode) {
18946         fprintf(debugFP, "FEN castling rights:");
18947         for(i=0; i<nrCastlingRights; i++)
18948         fprintf(debugFP, " %d", board[CASTLING][i]);
18949         fprintf(debugFP, "\n");
18950     }
18951
18952       while(*p==' ') p++;
18953     }
18954
18955     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18956
18957     /* read e.p. field in games that know e.p. capture */
18958     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18959        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18960        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18961       if(*p=='-') {
18962         p++; board[EP_STATUS] = EP_NONE;
18963       } else {
18964          int d, r, c = *p - AAA;
18965
18966          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18967              p++;
18968              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18969              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18970              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18971              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18972              board[LAST_TO] = 256*(r + d) + c;
18973              c = *p++ - AAA;
18974              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18975                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18976                  board[LAST_TO] = 256*r + c;
18977                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18978              }
18979          }
18980       }
18981     }
18982
18983     while(*p == ' ') p++;
18984
18985     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18986     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18987         board[CHECK_COUNT] = i + 256*j;
18988         while(*p && *p != ' ') p++;
18989     }
18990
18991     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18992     if(c > 0) {
18993         FENrulePlies = i; /* 50-move ply counter */
18994         /* (The move number is still ignored)    */
18995         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18996     }
18997
18998     return TRUE;
18999 }
19000
19001 void
19002 EditPositionPasteFEN (char *fen)
19003 {
19004   if (fen != NULL) {
19005     Board initial_position;
19006
19007     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
19008       DisplayError(_("Bad FEN position in clipboard"), 0);
19009       return ;
19010     } else {
19011       int savedBlackPlaysFirst = blackPlaysFirst;
19012       EditPositionEvent();
19013       blackPlaysFirst = savedBlackPlaysFirst;
19014       CopyBoard(boards[0], initial_position);
19015       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19016       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19017       DisplayBothClocks();
19018       DrawPosition(FALSE, boards[currentMove]);
19019     }
19020   }
19021 }
19022
19023 static char cseq[12] = "\\   ";
19024
19025 Boolean
19026 set_cont_sequence (char *new_seq)
19027 {
19028     int len;
19029     Boolean ret;
19030
19031     // handle bad attempts to set the sequence
19032         if (!new_seq)
19033                 return 0; // acceptable error - no debug
19034
19035     len = strlen(new_seq);
19036     ret = (len > 0) && (len < sizeof(cseq));
19037     if (ret)
19038       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19039     else if (appData.debugMode)
19040       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19041     return ret;
19042 }
19043
19044 /*
19045     reformat a source message so words don't cross the width boundary.  internal
19046     newlines are not removed.  returns the wrapped size (no null character unless
19047     included in source message).  If dest is NULL, only calculate the size required
19048     for the dest buffer.  lp argument indicats line position upon entry, and it's
19049     passed back upon exit.
19050 */
19051 int
19052 wrap (char *dest, char *src, int count, int width, int *lp)
19053 {
19054     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19055
19056     cseq_len = strlen(cseq);
19057     old_line = line = *lp;
19058     ansi = len = clen = 0;
19059
19060     for (i=0; i < count; i++)
19061     {
19062         if (src[i] == '\033')
19063             ansi = 1;
19064
19065         // if we hit the width, back up
19066         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19067         {
19068             // store i & len in case the word is too long
19069             old_i = i, old_len = len;
19070
19071             // find the end of the last word
19072             while (i && src[i] != ' ' && src[i] != '\n')
19073             {
19074                 i--;
19075                 len--;
19076             }
19077
19078             // word too long?  restore i & len before splitting it
19079             if ((old_i-i+clen) >= width)
19080             {
19081                 i = old_i;
19082                 len = old_len;
19083             }
19084
19085             // extra space?
19086             if (i && src[i-1] == ' ')
19087                 len--;
19088
19089             if (src[i] != ' ' && src[i] != '\n')
19090             {
19091                 i--;
19092                 if (len)
19093                     len--;
19094             }
19095
19096             // now append the newline and continuation sequence
19097             if (dest)
19098                 dest[len] = '\n';
19099             len++;
19100             if (dest)
19101                 strncpy(dest+len, cseq, cseq_len);
19102             len += cseq_len;
19103             line = cseq_len;
19104             clen = cseq_len;
19105             continue;
19106         }
19107
19108         if (dest)
19109             dest[len] = src[i];
19110         len++;
19111         if (!ansi)
19112             line++;
19113         if (src[i] == '\n')
19114             line = 0;
19115         if (src[i] == 'm')
19116             ansi = 0;
19117     }
19118     if (dest && appData.debugMode)
19119     {
19120         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19121             count, width, line, len, *lp);
19122         show_bytes(debugFP, src, count);
19123         fprintf(debugFP, "\ndest: ");
19124         show_bytes(debugFP, dest, len);
19125         fprintf(debugFP, "\n");
19126     }
19127     *lp = dest ? line : old_line;
19128
19129     return len;
19130 }
19131
19132 // [HGM] vari: routines for shelving variations
19133 Boolean modeRestore = FALSE;
19134
19135 void
19136 PushInner (int firstMove, int lastMove)
19137 {
19138         int i, j, nrMoves = lastMove - firstMove;
19139
19140         // push current tail of game on stack
19141         savedResult[storedGames] = gameInfo.result;
19142         savedDetails[storedGames] = gameInfo.resultDetails;
19143         gameInfo.resultDetails = NULL;
19144         savedFirst[storedGames] = firstMove;
19145         savedLast [storedGames] = lastMove;
19146         savedFramePtr[storedGames] = framePtr;
19147         framePtr -= nrMoves; // reserve space for the boards
19148         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19149             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19150             for(j=0; j<MOVE_LEN; j++)
19151                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19152             for(j=0; j<2*MOVE_LEN; j++)
19153                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19154             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19155             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19156             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19157             pvInfoList[firstMove+i-1].depth = 0;
19158             commentList[framePtr+i] = commentList[firstMove+i];
19159             commentList[firstMove+i] = NULL;
19160         }
19161
19162         storedGames++;
19163         forwardMostMove = firstMove; // truncate game so we can start variation
19164 }
19165
19166 void
19167 PushTail (int firstMove, int lastMove)
19168 {
19169         if(appData.icsActive) { // only in local mode
19170                 forwardMostMove = currentMove; // mimic old ICS behavior
19171                 return;
19172         }
19173         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19174
19175         PushInner(firstMove, lastMove);
19176         if(storedGames == 1) GreyRevert(FALSE);
19177         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19178 }
19179
19180 void
19181 PopInner (Boolean annotate)
19182 {
19183         int i, j, nrMoves;
19184         char buf[8000], moveBuf[20];
19185
19186         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19187         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19188         nrMoves = savedLast[storedGames] - currentMove;
19189         if(annotate) {
19190                 int cnt = 10;
19191                 if(!WhiteOnMove(currentMove))
19192                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19193                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19194                 for(i=currentMove; i<forwardMostMove; i++) {
19195                         if(WhiteOnMove(i))
19196                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19197                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19198                         strcat(buf, moveBuf);
19199                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19200                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19201                 }
19202                 strcat(buf, ")");
19203         }
19204         for(i=1; i<=nrMoves; i++) { // copy last variation back
19205             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19206             for(j=0; j<MOVE_LEN; j++)
19207                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19208             for(j=0; j<2*MOVE_LEN; j++)
19209                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19210             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19211             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19212             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19213             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19214             commentList[currentMove+i] = commentList[framePtr+i];
19215             commentList[framePtr+i] = NULL;
19216         }
19217         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19218         framePtr = savedFramePtr[storedGames];
19219         gameInfo.result = savedResult[storedGames];
19220         if(gameInfo.resultDetails != NULL) {
19221             free(gameInfo.resultDetails);
19222       }
19223         gameInfo.resultDetails = savedDetails[storedGames];
19224         forwardMostMove = currentMove + nrMoves;
19225 }
19226
19227 Boolean
19228 PopTail (Boolean annotate)
19229 {
19230         if(appData.icsActive) return FALSE; // only in local mode
19231         if(!storedGames) return FALSE; // sanity
19232         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19233
19234         PopInner(annotate);
19235         if(currentMove < forwardMostMove) ForwardEvent(); else
19236         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19237
19238         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19239         return TRUE;
19240 }
19241
19242 void
19243 CleanupTail ()
19244 {       // remove all shelved variations
19245         int i;
19246         for(i=0; i<storedGames; i++) {
19247             if(savedDetails[i])
19248                 free(savedDetails[i]);
19249             savedDetails[i] = NULL;
19250         }
19251         for(i=framePtr; i<MAX_MOVES; i++) {
19252                 if(commentList[i]) free(commentList[i]);
19253                 commentList[i] = NULL;
19254         }
19255         framePtr = MAX_MOVES-1;
19256         storedGames = 0;
19257 }
19258
19259 void
19260 LoadVariation (int index, char *text)
19261 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19262         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19263         int level = 0, move;
19264
19265         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19266         // first find outermost bracketing variation
19267         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19268             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19269                 if(*p == '{') wait = '}'; else
19270                 if(*p == '[') wait = ']'; else
19271                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19272                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19273             }
19274             if(*p == wait) wait = NULLCHAR; // closing ]} found
19275             p++;
19276         }
19277         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19278         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19279         end[1] = NULLCHAR; // clip off comment beyond variation
19280         ToNrEvent(currentMove-1);
19281         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19282         // kludge: use ParsePV() to append variation to game
19283         move = currentMove;
19284         ParsePV(start, TRUE, TRUE);
19285         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19286         ClearPremoveHighlights();
19287         CommentPopDown();
19288         ToNrEvent(currentMove+1);
19289 }
19290
19291 int transparency[2];
19292
19293 void
19294 LoadTheme ()
19295 {
19296 #define BUF_SIZ (2*MSG_SIZ)
19297     char *p, *q, buf[BUF_SIZ];
19298     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19299         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19300         ParseArgsFromString(buf);
19301         ActivateTheme(TRUE); // also redo colors
19302         return;
19303     }
19304     p = nickName;
19305     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19306     {
19307         int len;
19308         q = appData.themeNames;
19309         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19310       if(appData.useBitmaps) {
19311         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19312                 Shorten(appData.liteBackTextureFile));
19313         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19314                 Shorten(appData.darkBackTextureFile),
19315                 appData.liteBackTextureMode,
19316                 appData.darkBackTextureMode );
19317       } else {
19318         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19319       }
19320       if(!appData.useBitmaps || transparency[0]) {
19321         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19322       }
19323       if(!appData.useBitmaps || transparency[1]) {
19324         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19325       }
19326       if(appData.useBorder) {
19327         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19328                 appData.border);
19329       } else {
19330         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19331       }
19332       if(appData.useFont) {
19333         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19334                 appData.renderPiecesWithFont,
19335                 appData.fontToPieceTable,
19336                 Col2Text(9),    // appData.fontBackColorWhite
19337                 Col2Text(10) ); // appData.fontForeColorBlack
19338       } else {
19339         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19340         if(appData.pieceDirectory[0]) {
19341           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19342           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19343             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19344         }
19345         if(!appData.pieceDirectory[0] || !appData.trueColors)
19346           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19347                 Col2Text(0),   // whitePieceColor
19348                 Col2Text(1) ); // blackPieceColor
19349       }
19350       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19351                 Col2Text(4),   // highlightSquareColor
19352                 Col2Text(5) ); // premoveHighlightColor
19353         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19354         if(insert != q) insert[-1] = NULLCHAR;
19355         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19356         if(q)   free(q);
19357     }
19358     ActivateTheme(FALSE);
19359 }