Fix reading of startposition FEN starting with *
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <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
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #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"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144         char *m = moveList[moveNum];
5145         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
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5352         } else {
5353             sprintf(move, "%c%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5355         }
5356     }
5357 }
5358
5359 void
5360 ProcessICSInitScript (FILE *f)
5361 {
5362     char buf[MSG_SIZ];
5363
5364     while (fgets(buf, MSG_SIZ, f)) {
5365         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5366     }
5367
5368     fclose(f);
5369 }
5370
5371
5372 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5373 int dragging;
5374 static ClickType lastClickType;
5375
5376 int
5377 Partner (ChessSquare *p)
5378 { // change piece into promotion partner if one shogi-promotes to the other
5379   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5380   ChessSquare partner;
5381   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5382   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5383   *p = partner;
5384   return 1;
5385 }
5386
5387 void
5388 Sweep (int step)
5389 {
5390     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5391     static int toggleFlag;
5392     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5393     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5394     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5395     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5396     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5397     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5398     do {
5399         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5400         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5401         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5402         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5403         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5404         if(!step) step = -1;
5405     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5406             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5407             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5408             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5409     if(toX >= 0) {
5410         int victim = boards[currentMove][toY][toX];
5411         boards[currentMove][toY][toX] = promoSweep;
5412         DrawPosition(FALSE, boards[currentMove]);
5413         boards[currentMove][toY][toX] = victim;
5414     } else
5415     ChangeDragPiece(promoSweep);
5416 }
5417
5418 int
5419 PromoScroll (int x, int y)
5420 {
5421   int step = 0;
5422
5423   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5424   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return FALSE;
5427   lastX = x; lastY = y;
5428   if((promoSweep < BlackPawn) == flipView) step = -step;
5429   if(step > 0) selectFlag = 1;
5430   if(!selectFlag) Sweep(step);
5431   return FALSE;
5432 }
5433
5434 void
5435 NextPiece (int step)
5436 {
5437     ChessSquare piece = boards[currentMove][toY][toX];
5438     do {
5439         pieceSweep -= step;
5440         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5441         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5442         if(!step) step = -1;
5443     } while(PieceToChar(pieceSweep) == '.');
5444     boards[currentMove][toY][toX] = pieceSweep;
5445     DrawPosition(FALSE, boards[currentMove]);
5446     boards[currentMove][toY][toX] = piece;
5447 }
5448 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5449 void
5450 AlphaRank (char *move, int n)
5451 {
5452 //    char *p = move, c; int x, y;
5453
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5456     }
5457
5458     if(move[1]=='*' &&
5459        move[2]>='0' && move[2]<='9' &&
5460        move[3]>='a' && move[3]<='x'    ) {
5461         move[1] = '@';
5462         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5463         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5464     } else
5465     if(move[0]>='0' && move[0]<='9' &&
5466        move[1]>='a' && move[1]<='x' &&
5467        move[2]>='0' && move[2]<='9' &&
5468        move[3]>='a' && move[3]<='x'    ) {
5469         /* input move, Shogi -> normal */
5470         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5471         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5472         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5474     } else
5475     if(move[1]=='@' &&
5476        move[3]>='0' && move[3]<='9' &&
5477        move[2]>='a' && move[2]<='x'    ) {
5478         move[1] = '*';
5479         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5481     } else
5482     if(
5483        move[0]>='a' && move[0]<='x' &&
5484        move[3]>='0' && move[3]<='9' &&
5485        move[2]>='a' && move[2]<='x'    ) {
5486          /* output move, normal -> Shogi */
5487         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5488         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5489         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5490         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5491         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5492     }
5493     if (appData.debugMode) {
5494         fprintf(debugFP, "   out = '%s'\n", move);
5495     }
5496 }
5497
5498 char yy_textstr[8000];
5499
5500 /* Parser for moves from gnuchess, ICS, or user typein box */
5501 Boolean
5502 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5503 {
5504     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5505
5506     switch (*moveType) {
5507       case WhitePromotion:
5508       case BlackPromotion:
5509       case WhiteNonPromotion:
5510       case BlackNonPromotion:
5511       case NormalMove:
5512       case FirstLeg:
5513       case WhiteCapturesEnPassant:
5514       case BlackCapturesEnPassant:
5515       case WhiteKingSideCastle:
5516       case WhiteQueenSideCastle:
5517       case BlackKingSideCastle:
5518       case BlackQueenSideCastle:
5519       case WhiteKingSideCastleWild:
5520       case WhiteQueenSideCastleWild:
5521       case BlackKingSideCastleWild:
5522       case BlackQueenSideCastleWild:
5523       /* Code added by Tord: */
5524       case WhiteHSideCastleFR:
5525       case WhiteASideCastleFR:
5526       case BlackHSideCastleFR:
5527       case BlackASideCastleFR:
5528       /* End of code added by Tord */
5529       case IllegalMove:         /* bug or odd chess variant */
5530         if(currentMoveString[1] == '@') { // illegal drop
5531           *fromX = WhiteOnMove(moveNum) ?
5532             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5533             (int) CharToPiece(ToLower(currentMoveString[0]));
5534           goto drop;
5535         }
5536         *fromX = currentMoveString[0] - AAA;
5537         *fromY = currentMoveString[1] - ONE;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = currentMoveString[4];
5541         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5542             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5543     if (appData.debugMode) {
5544         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5545     }
5546             *fromX = *fromY = *toX = *toY = 0;
5547             return FALSE;
5548         }
5549         if (appData.testLegality) {
5550           return (*moveType != IllegalMove);
5551         } else {
5552           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5553                          // [HGM] lion: if this is a double move we are less critical
5554                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5555         }
5556
5557       case WhiteDrop:
5558       case BlackDrop:
5559         *fromX = *moveType == WhiteDrop ?
5560           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5561           (int) CharToPiece(ToLower(currentMoveString[0]));
5562       drop:
5563         *fromY = DROP_RANK;
5564         *toX = currentMoveString[2] - AAA;
5565         *toY = currentMoveString[3] - ONE;
5566         *promoChar = NULLCHAR;
5567         return TRUE;
5568
5569       case AmbiguousMove:
5570       case ImpossibleMove:
5571       case EndOfFile:
5572       case ElapsedTime:
5573       case Comment:
5574       case PGNTag:
5575       case NAG:
5576       case WhiteWins:
5577       case BlackWins:
5578       case GameIsDrawn:
5579       default:
5580     if (appData.debugMode) {
5581         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5582     }
5583         /* bug? */
5584         *fromX = *fromY = *toX = *toY = 0;
5585         *promoChar = NULLCHAR;
5586         return FALSE;
5587     }
5588 }
5589
5590 Boolean pushed = FALSE;
5591 char *lastParseAttempt;
5592
5593 void
5594 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5595 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5596   int fromX, fromY, toX, toY; char promoChar;
5597   ChessMove moveType;
5598   Boolean valid;
5599   int nr = 0;
5600
5601   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5602   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5603     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5604     pushed = TRUE;
5605   }
5606   endPV = forwardMostMove;
5607   do {
5608     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5609     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5610     lastParseAttempt = pv;
5611     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5612     if(!valid && nr == 0 &&
5613        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5614         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5615         // Hande case where played move is different from leading PV move
5616         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5617         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5618         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5619         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5620           endPV += 2; // if position different, keep this
5621           moveList[endPV-1][0] = fromX + AAA;
5622           moveList[endPV-1][1] = fromY + ONE;
5623           moveList[endPV-1][2] = toX + AAA;
5624           moveList[endPV-1][3] = toY + ONE;
5625           parseList[endPV-1][0] = NULLCHAR;
5626           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5627         }
5628       }
5629     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5630     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5631     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5632     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5633         valid++; // allow comments in PV
5634         continue;
5635     }
5636     nr++;
5637     if(endPV+1 > framePtr) break; // no space, truncate
5638     if(!valid) break;
5639     endPV++;
5640     CopyBoard(boards[endPV], boards[endPV-1]);
5641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5642     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5643     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5644     CoordsToAlgebraic(boards[endPV - 1],
5645                              PosFlags(endPV - 1),
5646                              fromY, fromX, toY, toX, promoChar,
5647                              parseList[endPV - 1]);
5648   } while(valid);
5649   if(atEnd == 2) return; // used hidden, for PV conversion
5650   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5651   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5652   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5653                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5654   DrawPosition(TRUE, boards[currentMove]);
5655 }
5656
5657 int
5658 MultiPV (ChessProgramState *cps)
5659 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5660         int i;
5661         for(i=0; i<cps->nrOptions; i++)
5662             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5663                 return i;
5664         return -1;
5665 }
5666
5667 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5668
5669 Boolean
5670 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5671 {
5672         int startPV, multi, lineStart, origIndex = index;
5673         char *p, buf2[MSG_SIZ];
5674         ChessProgramState *cps = (pane ? &second : &first);
5675
5676         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5677         lastX = x; lastY = y;
5678         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5679         lineStart = startPV = index;
5680         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5681         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5682         index = startPV;
5683         do{ while(buf[index] && buf[index] != '\n') index++;
5684         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5685         buf[index] = 0;
5686         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5687                 int n = cps->option[multi].value;
5688                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5689                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5690                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5691                 cps->option[multi].value = n;
5692                 *start = *end = 0;
5693                 return FALSE;
5694         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5695                 ExcludeClick(origIndex - lineStart);
5696                 return FALSE;
5697         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5698                 Collapse(origIndex - lineStart);
5699                 return FALSE;
5700         }
5701         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5702         *start = startPV; *end = index-1;
5703         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5704         return TRUE;
5705 }
5706
5707 char *
5708 PvToSAN (char *pv)
5709 {
5710         static char buf[10*MSG_SIZ];
5711         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5712         *buf = NULLCHAR;
5713         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5714         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5715         for(i = forwardMostMove; i<endPV; i++){
5716             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5717             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5718             k += strlen(buf+k);
5719         }
5720         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5721         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5722         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5723         endPV = savedEnd;
5724         return buf;
5725 }
5726
5727 Boolean
5728 LoadPV (int x, int y)
5729 { // called on right mouse click to load PV
5730   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5731   lastX = x; lastY = y;
5732   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5733   extendGame = FALSE;
5734   return TRUE;
5735 }
5736
5737 void
5738 UnLoadPV ()
5739 {
5740   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5741   if(endPV < 0) return;
5742   if(appData.autoCopyPV) CopyFENToClipboard();
5743   endPV = -1;
5744   if(extendGame && currentMove > forwardMostMove) {
5745         Boolean saveAnimate = appData.animate;
5746         if(pushed) {
5747             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5748                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5749             } else storedGames--; // abandon shelved tail of original game
5750         }
5751         pushed = FALSE;
5752         forwardMostMove = currentMove;
5753         currentMove = oldFMM;
5754         appData.animate = FALSE;
5755         ToNrEvent(forwardMostMove);
5756         appData.animate = saveAnimate;
5757   }
5758   currentMove = forwardMostMove;
5759   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5760   ClearPremoveHighlights();
5761   DrawPosition(TRUE, boards[currentMove]);
5762 }
5763
5764 void
5765 MovePV (int x, int y, int h)
5766 { // step through PV based on mouse coordinates (called on mouse move)
5767   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5768
5769   // we must somehow check if right button is still down (might be released off board!)
5770   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5771   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5772   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5773   if(!step) return;
5774   lastX = x; lastY = y;
5775
5776   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5777   if(endPV < 0) return;
5778   if(y < margin) step = 1; else
5779   if(y > h - margin) step = -1;
5780   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5781   currentMove += step;
5782   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5783   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5784                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5785   DrawPosition(FALSE, boards[currentMove]);
5786 }
5787
5788
5789 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5790 // All positions will have equal probability, but the current method will not provide a unique
5791 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5792 #define DARK 1
5793 #define LITE 2
5794 #define ANY 3
5795
5796 int squaresLeft[4];
5797 int piecesLeft[(int)BlackPawn];
5798 int seed, nrOfShuffles;
5799
5800 void
5801 GetPositionNumber ()
5802 {       // sets global variable seed
5803         int i;
5804
5805         seed = appData.defaultFrcPosition;
5806         if(seed < 0) { // randomize based on time for negative FRC position numbers
5807                 for(i=0; i<50; i++) seed += random();
5808                 seed = random() ^ random() >> 8 ^ random() << 8;
5809                 if(seed<0) seed = -seed;
5810         }
5811 }
5812
5813 int
5814 put (Board board, int pieceType, int rank, int n, int shade)
5815 // put the piece on the (n-1)-th empty squares of the given shade
5816 {
5817         int i;
5818
5819         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5820                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5821                         board[rank][i] = (ChessSquare) pieceType;
5822                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5823                         squaresLeft[ANY]--;
5824                         piecesLeft[pieceType]--;
5825                         return i;
5826                 }
5827         }
5828         return -1;
5829 }
5830
5831
5832 void
5833 AddOnePiece (Board board, int pieceType, int rank, int shade)
5834 // calculate where the next piece goes, (any empty square), and put it there
5835 {
5836         int i;
5837
5838         i = seed % squaresLeft[shade];
5839         nrOfShuffles *= squaresLeft[shade];
5840         seed /= squaresLeft[shade];
5841         put(board, pieceType, rank, i, shade);
5842 }
5843
5844 void
5845 AddTwoPieces (Board board, int pieceType, int rank)
5846 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5847 {
5848         int i, n=squaresLeft[ANY], j=n-1, k;
5849
5850         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5851         i = seed % k;  // pick one
5852         nrOfShuffles *= k;
5853         seed /= k;
5854         while(i >= j) i -= j--;
5855         j = n - 1 - j; i += j;
5856         put(board, pieceType, rank, j, ANY);
5857         put(board, pieceType, rank, i, ANY);
5858 }
5859
5860 void
5861 SetUpShuffle (Board board, int number)
5862 {
5863         int i, p, first=1;
5864
5865         GetPositionNumber(); nrOfShuffles = 1;
5866
5867         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5868         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5869         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5870
5871         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5872
5873         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5874             p = (int) board[0][i];
5875             if(p < (int) BlackPawn) piecesLeft[p] ++;
5876             board[0][i] = EmptySquare;
5877         }
5878
5879         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5880             // shuffles restricted to allow normal castling put KRR first
5881             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5882                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5883             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5884                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5885             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5886                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5887             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5888                 put(board, WhiteRook, 0, 0, ANY);
5889             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5890         }
5891
5892         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5893             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5894             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5895                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5896                 while(piecesLeft[p] >= 2) {
5897                     AddOnePiece(board, p, 0, LITE);
5898                     AddOnePiece(board, p, 0, DARK);
5899                 }
5900                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5901             }
5902
5903         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5904             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5905             // but we leave King and Rooks for last, to possibly obey FRC restriction
5906             if(p == (int)WhiteRook) continue;
5907             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5908             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5909         }
5910
5911         // now everything is placed, except perhaps King (Unicorn) and Rooks
5912
5913         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5914             // Last King gets castling rights
5915             while(piecesLeft[(int)WhiteUnicorn]) {
5916                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5917                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5918             }
5919
5920             while(piecesLeft[(int)WhiteKing]) {
5921                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5923             }
5924
5925
5926         } else {
5927             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5928             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5929         }
5930
5931         // Only Rooks can be left; simply place them all
5932         while(piecesLeft[(int)WhiteRook]) {
5933                 i = put(board, WhiteRook, 0, 0, ANY);
5934                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5935                         if(first) {
5936                                 first=0;
5937                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5938                         }
5939                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5940                 }
5941         }
5942         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5943             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5944         }
5945
5946         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5947 }
5948
5949 int
5950 SetCharTable (char *table, const char * map)
5951 /* [HGM] moved here from winboard.c because of its general usefulness */
5952 /*       Basically a safe strcpy that uses the last character as King */
5953 {
5954     int result = FALSE; int NrPieces;
5955
5956     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5957                     && NrPieces >= 12 && !(NrPieces&1)) {
5958         int i; /* [HGM] Accept even length from 12 to 34 */
5959
5960         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5961         for( i=0; i<NrPieces/2-1; i++ ) {
5962             table[i] = map[i];
5963             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5964         }
5965         table[(int) WhiteKing]  = map[NrPieces/2-1];
5966         table[(int) BlackKing]  = map[NrPieces-1];
5967
5968         result = TRUE;
5969     }
5970
5971     return result;
5972 }
5973
5974 void
5975 Prelude (Board board)
5976 {       // [HGM] superchess: random selection of exo-pieces
5977         int i, j, k; ChessSquare p;
5978         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5979
5980         GetPositionNumber(); // use FRC position number
5981
5982         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5983             SetCharTable(pieceToChar, appData.pieceToCharTable);
5984             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5985                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5986         }
5987
5988         j = seed%4;                 seed /= 4;
5989         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5990         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5991         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5992         j = seed%3 + (seed%3 >= j); seed /= 3;
5993         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5994         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5995         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5996         j = seed%3;                 seed /= 3;
5997         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5998         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5999         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6000         j = seed%2 + (seed%2 >= j); seed /= 2;
6001         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6002         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6003         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6004         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6005         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6006         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6007         put(board, exoPieces[0],    0, 0, ANY);
6008         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6009 }
6010
6011 void
6012 InitPosition (int redraw)
6013 {
6014     ChessSquare (* pieces)[BOARD_FILES];
6015     int i, j, pawnRow=1, pieceRows=1, overrule,
6016     oldx = gameInfo.boardWidth,
6017     oldy = gameInfo.boardHeight,
6018     oldh = gameInfo.holdingsWidth;
6019     static int oldv;
6020
6021     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6022
6023     /* [AS] Initialize pv info list [HGM] and game status */
6024     {
6025         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6026             pvInfoList[i].depth = 0;
6027             boards[i][EP_STATUS] = EP_NONE;
6028             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6029         }
6030
6031         initialRulePlies = 0; /* 50-move counter start */
6032
6033         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6034         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6035     }
6036
6037
6038     /* [HGM] logic here is completely changed. In stead of full positions */
6039     /* the initialized data only consist of the two backranks. The switch */
6040     /* selects which one we will use, which is than copied to the Board   */
6041     /* initialPosition, which for the rest is initialized by Pawns and    */
6042     /* empty squares. This initial position is then copied to boards[0],  */
6043     /* possibly after shuffling, so that it remains available.            */
6044
6045     gameInfo.holdingsWidth = 0; /* default board sizes */
6046     gameInfo.boardWidth    = 8;
6047     gameInfo.boardHeight   = 8;
6048     gameInfo.holdingsSize  = 0;
6049     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6050     for(i=0; i<BOARD_FILES-6; i++)
6051       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6052     initialPosition[EP_STATUS] = EP_NONE;
6053     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6054     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6055     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6056          SetCharTable(pieceNickName, appData.pieceNickNames);
6057     else SetCharTable(pieceNickName, "............");
6058     pieces = FIDEArray;
6059
6060     switch (gameInfo.variant) {
6061     case VariantFischeRandom:
6062       shuffleOpenings = TRUE;
6063       appData.fischerCastling = TRUE;
6064     default:
6065       break;
6066     case VariantShatranj:
6067       pieces = ShatranjArray;
6068       nrCastlingRights = 0;
6069       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6070       break;
6071     case VariantMakruk:
6072       pieces = makrukArray;
6073       nrCastlingRights = 0;
6074       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6075       break;
6076     case VariantASEAN:
6077       pieces = aseanArray;
6078       nrCastlingRights = 0;
6079       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6080       break;
6081     case VariantTwoKings:
6082       pieces = twoKingsArray;
6083       break;
6084     case VariantGrand:
6085       pieces = GrandArray;
6086       nrCastlingRights = 0;
6087       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6088       gameInfo.boardWidth = 10;
6089       gameInfo.boardHeight = 10;
6090       gameInfo.holdingsSize = 7;
6091       break;
6092     case VariantCapaRandom:
6093       shuffleOpenings = TRUE;
6094       appData.fischerCastling = TRUE;
6095     case VariantCapablanca:
6096       pieces = CapablancaArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6099       break;
6100     case VariantGothic:
6101       pieces = GothicArray;
6102       gameInfo.boardWidth = 10;
6103       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6104       break;
6105     case VariantSChess:
6106       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6107       gameInfo.holdingsSize = 7;
6108       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6109       break;
6110     case VariantJanus:
6111       pieces = JanusArray;
6112       gameInfo.boardWidth = 10;
6113       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6114       nrCastlingRights = 6;
6115         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6116         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6117         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6118         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6119         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6120         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6121       break;
6122     case VariantFalcon:
6123       pieces = FalconArray;
6124       gameInfo.boardWidth = 10;
6125       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6126       break;
6127     case VariantXiangqi:
6128       pieces = XiangqiArray;
6129       gameInfo.boardWidth  = 9;
6130       gameInfo.boardHeight = 10;
6131       nrCastlingRights = 0;
6132       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6133       break;
6134     case VariantShogi:
6135       pieces = ShogiArray;
6136       gameInfo.boardWidth  = 9;
6137       gameInfo.boardHeight = 9;
6138       gameInfo.holdingsSize = 7;
6139       nrCastlingRights = 0;
6140       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6141       break;
6142     case VariantChu:
6143       pieces = ChuArray; pieceRows = 3;
6144       gameInfo.boardWidth  = 12;
6145       gameInfo.boardHeight = 12;
6146       nrCastlingRights = 0;
6147       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6148                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6149       break;
6150     case VariantCourier:
6151       pieces = CourierArray;
6152       gameInfo.boardWidth  = 12;
6153       nrCastlingRights = 0;
6154       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6155       break;
6156     case VariantKnightmate:
6157       pieces = KnightmateArray;
6158       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6159       break;
6160     case VariantSpartan:
6161       pieces = SpartanArray;
6162       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6163       break;
6164     case VariantLion:
6165       pieces = lionArray;
6166       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6167       break;
6168     case VariantChuChess:
6169       pieces = ChuChessArray;
6170       gameInfo.boardWidth = 10;
6171       gameInfo.boardHeight = 10;
6172       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6173       break;
6174     case VariantFairy:
6175       pieces = fairyArray;
6176       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6177       break;
6178     case VariantGreat:
6179       pieces = GreatArray;
6180       gameInfo.boardWidth = 10;
6181       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6182       gameInfo.holdingsSize = 8;
6183       break;
6184     case VariantSuper:
6185       pieces = FIDEArray;
6186       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6187       gameInfo.holdingsSize = 8;
6188       startedFromSetupPosition = TRUE;
6189       break;
6190     case VariantCrazyhouse:
6191     case VariantBughouse:
6192       pieces = FIDEArray;
6193       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6194       gameInfo.holdingsSize = 5;
6195       break;
6196     case VariantWildCastle:
6197       pieces = FIDEArray;
6198       /* !!?shuffle with kings guaranteed to be on d or e file */
6199       shuffleOpenings = 1;
6200       break;
6201     case VariantNoCastle:
6202       pieces = FIDEArray;
6203       nrCastlingRights = 0;
6204       /* !!?unconstrained back-rank shuffle */
6205       shuffleOpenings = 1;
6206       break;
6207     }
6208
6209     overrule = 0;
6210     if(appData.NrFiles >= 0) {
6211         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6212         gameInfo.boardWidth = appData.NrFiles;
6213     }
6214     if(appData.NrRanks >= 0) {
6215         gameInfo.boardHeight = appData.NrRanks;
6216     }
6217     if(appData.holdingsSize >= 0) {
6218         i = appData.holdingsSize;
6219         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6220         gameInfo.holdingsSize = i;
6221     }
6222     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6223     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6224         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6225
6226     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6227     if(pawnRow < 1) pawnRow = 1;
6228     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6229        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6230     if(gameInfo.variant == VariantChu) pawnRow = 3;
6231
6232     /* User pieceToChar list overrules defaults */
6233     if(appData.pieceToCharTable != NULL)
6234         SetCharTable(pieceToChar, appData.pieceToCharTable);
6235
6236     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6237
6238         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6239             s = (ChessSquare) 0; /* account holding counts in guard band */
6240         for( i=0; i<BOARD_HEIGHT; i++ )
6241             initialPosition[i][j] = s;
6242
6243         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6244         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6245         initialPosition[pawnRow][j] = WhitePawn;
6246         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6247         if(gameInfo.variant == VariantXiangqi) {
6248             if(j&1) {
6249                 initialPosition[pawnRow][j] =
6250                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6251                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6252                    initialPosition[2][j] = WhiteCannon;
6253                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6254                 }
6255             }
6256         }
6257         if(gameInfo.variant == VariantChu) {
6258              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6259                initialPosition[pawnRow+1][j] = WhiteCobra,
6260                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6261              for(i=1; i<pieceRows; i++) {
6262                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6263                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6264              }
6265         }
6266         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6267             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6268                initialPosition[0][j] = WhiteRook;
6269                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6270             }
6271         }
6272         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6273     }
6274     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6275     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6276
6277             j=BOARD_LEFT+1;
6278             initialPosition[1][j] = WhiteBishop;
6279             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6280             j=BOARD_RGHT-2;
6281             initialPosition[1][j] = WhiteRook;
6282             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6283     }
6284
6285     if( nrCastlingRights == -1) {
6286         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6287         /*       This sets default castling rights from none to normal corners   */
6288         /* Variants with other castling rights must set them themselves above    */
6289         nrCastlingRights = 6;
6290
6291         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6292         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6293         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6294         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6295         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6296         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6297      }
6298
6299      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6300      if(gameInfo.variant == VariantGreat) { // promotion commoners
6301         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6302         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6303         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6304         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6305      }
6306      if( gameInfo.variant == VariantSChess ) {
6307       initialPosition[1][0] = BlackMarshall;
6308       initialPosition[2][0] = BlackAngel;
6309       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6310       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6311       initialPosition[1][1] = initialPosition[2][1] =
6312       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6313      }
6314   if (appData.debugMode) {
6315     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6316   }
6317     if(shuffleOpenings) {
6318         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6319         startedFromSetupPosition = TRUE;
6320     }
6321     if(startedFromPositionFile) {
6322       /* [HGM] loadPos: use PositionFile for every new game */
6323       CopyBoard(initialPosition, filePosition);
6324       for(i=0; i<nrCastlingRights; i++)
6325           initialRights[i] = filePosition[CASTLING][i];
6326       startedFromSetupPosition = TRUE;
6327     }
6328
6329     CopyBoard(boards[0], initialPosition);
6330
6331     if(oldx != gameInfo.boardWidth ||
6332        oldy != gameInfo.boardHeight ||
6333        oldv != gameInfo.variant ||
6334        oldh != gameInfo.holdingsWidth
6335                                          )
6336             InitDrawingSizes(-2 ,0);
6337
6338     oldv = gameInfo.variant;
6339     if (redraw)
6340       DrawPosition(TRUE, boards[currentMove]);
6341 }
6342
6343 void
6344 SendBoard (ChessProgramState *cps, int moveNum)
6345 {
6346     char message[MSG_SIZ];
6347
6348     if (cps->useSetboard) {
6349       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6350       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6351       SendToProgram(message, cps);
6352       free(fen);
6353
6354     } else {
6355       ChessSquare *bp;
6356       int i, j, left=0, right=BOARD_WIDTH;
6357       /* Kludge to set black to move, avoiding the troublesome and now
6358        * deprecated "black" command.
6359        */
6360       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6361         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6362
6363       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6364
6365       SendToProgram("edit\n", cps);
6366       SendToProgram("#\n", cps);
6367       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6368         bp = &boards[moveNum][i][left];
6369         for (j = left; j < right; j++, bp++) {
6370           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6371           if ((int) *bp < (int) BlackPawn) {
6372             if(j == BOARD_RGHT+1)
6373                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6374             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6375             if(message[0] == '+' || message[0] == '~') {
6376               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6377                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6378                         AAA + j, ONE + i);
6379             }
6380             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6381                 message[1] = BOARD_RGHT   - 1 - j + '1';
6382                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6383             }
6384             SendToProgram(message, cps);
6385           }
6386         }
6387       }
6388
6389       SendToProgram("c\n", cps);
6390       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6391         bp = &boards[moveNum][i][left];
6392         for (j = left; j < right; j++, bp++) {
6393           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6394           if (((int) *bp != (int) EmptySquare)
6395               && ((int) *bp >= (int) BlackPawn)) {
6396             if(j == BOARD_LEFT-2)
6397                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6398             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6399                     AAA + j, ONE + i);
6400             if(message[0] == '+' || message[0] == '~') {
6401               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6402                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6403                         AAA + j, ONE + i);
6404             }
6405             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6406                 message[1] = BOARD_RGHT   - 1 - j + '1';
6407                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6408             }
6409             SendToProgram(message, cps);
6410           }
6411         }
6412       }
6413
6414       SendToProgram(".\n", cps);
6415     }
6416     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6417 }
6418
6419 char exclusionHeader[MSG_SIZ];
6420 int exCnt, excludePtr;
6421 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6422 static Exclusion excluTab[200];
6423 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6424
6425 static void
6426 WriteMap (int s)
6427 {
6428     int j;
6429     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6430     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6431 }
6432
6433 static void
6434 ClearMap ()
6435 {
6436     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6437     excludePtr = 24; exCnt = 0;
6438     WriteMap(0);
6439 }
6440
6441 static void
6442 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6443 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6444     char buf[2*MOVE_LEN], *p;
6445     Exclusion *e = excluTab;
6446     int i;
6447     for(i=0; i<exCnt; i++)
6448         if(e[i].ff == fromX && e[i].fr == fromY &&
6449            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6450     if(i == exCnt) { // was not in exclude list; add it
6451         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6452         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6453             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6454             return; // abort
6455         }
6456         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6457         excludePtr++; e[i].mark = excludePtr++;
6458         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6459         exCnt++;
6460     }
6461     exclusionHeader[e[i].mark] = state;
6462 }
6463
6464 static int
6465 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6466 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6467     char buf[MSG_SIZ];
6468     int j, k;
6469     ChessMove moveType;
6470     if((signed char)promoChar == -1) { // kludge to indicate best move
6471         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6472             return 1; // if unparsable, abort
6473     }
6474     // update exclusion map (resolving toggle by consulting existing state)
6475     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6476     j = k%8; k >>= 3;
6477     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6478     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6479          excludeMap[k] |=   1<<j;
6480     else excludeMap[k] &= ~(1<<j);
6481     // update header
6482     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6483     // inform engine
6484     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6485     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6486     SendToBoth(buf);
6487     return (state == '+');
6488 }
6489
6490 static void
6491 ExcludeClick (int index)
6492 {
6493     int i, j;
6494     Exclusion *e = excluTab;
6495     if(index < 25) { // none, best or tail clicked
6496         if(index < 13) { // none: include all
6497             WriteMap(0); // clear map
6498             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6499             SendToBoth("include all\n"); // and inform engine
6500         } else if(index > 18) { // tail
6501             if(exclusionHeader[19] == '-') { // tail was excluded
6502                 SendToBoth("include all\n");
6503                 WriteMap(0); // clear map completely
6504                 // now re-exclude selected moves
6505                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6506                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6507             } else { // tail was included or in mixed state
6508                 SendToBoth("exclude all\n");
6509                 WriteMap(0xFF); // fill map completely
6510                 // now re-include selected moves
6511                 j = 0; // count them
6512                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6513                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6514                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6515             }
6516         } else { // best
6517             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6518         }
6519     } else {
6520         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6521             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6522             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6523             break;
6524         }
6525     }
6526 }
6527
6528 ChessSquare
6529 DefaultPromoChoice (int white)
6530 {
6531     ChessSquare result;
6532     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6533        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6534         result = WhiteFerz; // no choice
6535     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6536         result= WhiteKing; // in Suicide Q is the last thing we want
6537     else if(gameInfo.variant == VariantSpartan)
6538         result = white ? WhiteQueen : WhiteAngel;
6539     else result = WhiteQueen;
6540     if(!white) result = WHITE_TO_BLACK result;
6541     return result;
6542 }
6543
6544 static int autoQueen; // [HGM] oneclick
6545
6546 int
6547 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6548 {
6549     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6550     /* [HGM] add Shogi promotions */
6551     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6552     ChessSquare piece, partner;
6553     ChessMove moveType;
6554     Boolean premove;
6555
6556     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6557     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6558
6559     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6560       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6561         return FALSE;
6562
6563     piece = boards[currentMove][fromY][fromX];
6564     if(gameInfo.variant == VariantChu) {
6565         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6566         promotionZoneSize = BOARD_HEIGHT/3;
6567         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6568     } else if(gameInfo.variant == VariantShogi) {
6569         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6570         highestPromotingPiece = (int)WhiteAlfil;
6571     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6572         promotionZoneSize = 3;
6573     }
6574
6575     // Treat Lance as Pawn when it is not representing Amazon or Lance
6576     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6577         if(piece == WhiteLance) piece = WhitePawn; else
6578         if(piece == BlackLance) piece = BlackPawn;
6579     }
6580
6581     // next weed out all moves that do not touch the promotion zone at all
6582     if((int)piece >= BlackPawn) {
6583         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6584              return FALSE;
6585         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6586         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6587     } else {
6588         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6589            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6590         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6591              return FALSE;
6592     }
6593
6594     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6595
6596     // weed out mandatory Shogi promotions
6597     if(gameInfo.variant == VariantShogi) {
6598         if(piece >= BlackPawn) {
6599             if(toY == 0 && piece == BlackPawn ||
6600                toY == 0 && piece == BlackQueen ||
6601                toY <= 1 && piece == BlackKnight) {
6602                 *promoChoice = '+';
6603                 return FALSE;
6604             }
6605         } else {
6606             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6607                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6608                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6609                 *promoChoice = '+';
6610                 return FALSE;
6611             }
6612         }
6613     }
6614
6615     // weed out obviously illegal Pawn moves
6616     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6617         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6618         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6619         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6620         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6621         // note we are not allowed to test for valid (non-)capture, due to premove
6622     }
6623
6624     // we either have a choice what to promote to, or (in Shogi) whether to promote
6625     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6626        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6627         ChessSquare p=BlackFerz;  // no choice
6628         while(p < EmptySquare) {  //but make sure we use piece that exists
6629             *promoChoice = PieceToChar(p++);
6630             if(*promoChoice != '.') break;
6631         }
6632         return FALSE;
6633     }
6634     // no sense asking what we must promote to if it is going to explode...
6635     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6636         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6637         return FALSE;
6638     }
6639     // give caller the default choice even if we will not make it
6640     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6641     partner = piece; // pieces can promote if the pieceToCharTable says so
6642     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6643     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6644     if(        sweepSelect && gameInfo.variant != VariantGreat
6645                            && gameInfo.variant != VariantGrand
6646                            && gameInfo.variant != VariantSuper) return FALSE;
6647     if(autoQueen) return FALSE; // predetermined
6648
6649     // suppress promotion popup on illegal moves that are not premoves
6650     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6651               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6652     if(appData.testLegality && !premove) {
6653         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6654                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6655         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6656         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6657             return FALSE;
6658     }
6659
6660     return TRUE;
6661 }
6662
6663 int
6664 InPalace (int row, int column)
6665 {   /* [HGM] for Xiangqi */
6666     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6667          column < (BOARD_WIDTH + 4)/2 &&
6668          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6669     return FALSE;
6670 }
6671
6672 int
6673 PieceForSquare (int x, int y)
6674 {
6675   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6676      return -1;
6677   else
6678      return boards[currentMove][y][x];
6679 }
6680
6681 int
6682 OKToStartUserMove (int x, int y)
6683 {
6684     ChessSquare from_piece;
6685     int white_piece;
6686
6687     if (matchMode) return FALSE;
6688     if (gameMode == EditPosition) return TRUE;
6689
6690     if (x >= 0 && y >= 0)
6691       from_piece = boards[currentMove][y][x];
6692     else
6693       from_piece = EmptySquare;
6694
6695     if (from_piece == EmptySquare) return FALSE;
6696
6697     white_piece = (int)from_piece >= (int)WhitePawn &&
6698       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6699
6700     switch (gameMode) {
6701       case AnalyzeFile:
6702       case TwoMachinesPlay:
6703       case EndOfGame:
6704         return FALSE;
6705
6706       case IcsObserving:
6707       case IcsIdle:
6708         return FALSE;
6709
6710       case MachinePlaysWhite:
6711       case IcsPlayingBlack:
6712         if (appData.zippyPlay) return FALSE;
6713         if (white_piece) {
6714             DisplayMoveError(_("You are playing Black"));
6715             return FALSE;
6716         }
6717         break;
6718
6719       case MachinePlaysBlack:
6720       case IcsPlayingWhite:
6721         if (appData.zippyPlay) return FALSE;
6722         if (!white_piece) {
6723             DisplayMoveError(_("You are playing White"));
6724             return FALSE;
6725         }
6726         break;
6727
6728       case PlayFromGameFile:
6729             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6730       case EditGame:
6731         if (!white_piece && WhiteOnMove(currentMove)) {
6732             DisplayMoveError(_("It is White's turn"));
6733             return FALSE;
6734         }
6735         if (white_piece && !WhiteOnMove(currentMove)) {
6736             DisplayMoveError(_("It is Black's turn"));
6737             return FALSE;
6738         }
6739         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6740             /* Editing correspondence game history */
6741             /* Could disallow this or prompt for confirmation */
6742             cmailOldMove = -1;
6743         }
6744         break;
6745
6746       case BeginningOfGame:
6747         if (appData.icsActive) return FALSE;
6748         if (!appData.noChessProgram) {
6749             if (!white_piece) {
6750                 DisplayMoveError(_("You are playing White"));
6751                 return FALSE;
6752             }
6753         }
6754         break;
6755
6756       case Training:
6757         if (!white_piece && WhiteOnMove(currentMove)) {
6758             DisplayMoveError(_("It is White's turn"));
6759             return FALSE;
6760         }
6761         if (white_piece && !WhiteOnMove(currentMove)) {
6762             DisplayMoveError(_("It is Black's turn"));
6763             return FALSE;
6764         }
6765         break;
6766
6767       default:
6768       case IcsExamining:
6769         break;
6770     }
6771     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6772         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6773         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6774         && gameMode != AnalyzeFile && gameMode != Training) {
6775         DisplayMoveError(_("Displayed position is not current"));
6776         return FALSE;
6777     }
6778     return TRUE;
6779 }
6780
6781 Boolean
6782 OnlyMove (int *x, int *y, Boolean captures)
6783 {
6784     DisambiguateClosure cl;
6785     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6786     switch(gameMode) {
6787       case MachinePlaysBlack:
6788       case IcsPlayingWhite:
6789       case BeginningOfGame:
6790         if(!WhiteOnMove(currentMove)) return FALSE;
6791         break;
6792       case MachinePlaysWhite:
6793       case IcsPlayingBlack:
6794         if(WhiteOnMove(currentMove)) return FALSE;
6795         break;
6796       case EditGame:
6797         break;
6798       default:
6799         return FALSE;
6800     }
6801     cl.pieceIn = EmptySquare;
6802     cl.rfIn = *y;
6803     cl.ffIn = *x;
6804     cl.rtIn = -1;
6805     cl.ftIn = -1;
6806     cl.promoCharIn = NULLCHAR;
6807     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6808     if( cl.kind == NormalMove ||
6809         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6810         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6811         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6812       fromX = cl.ff;
6813       fromY = cl.rf;
6814       *x = cl.ft;
6815       *y = cl.rt;
6816       return TRUE;
6817     }
6818     if(cl.kind != ImpossibleMove) return FALSE;
6819     cl.pieceIn = EmptySquare;
6820     cl.rfIn = -1;
6821     cl.ffIn = -1;
6822     cl.rtIn = *y;
6823     cl.ftIn = *x;
6824     cl.promoCharIn = NULLCHAR;
6825     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6826     if( cl.kind == NormalMove ||
6827         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6828         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6829         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6830       fromX = cl.ff;
6831       fromY = cl.rf;
6832       *x = cl.ft;
6833       *y = cl.rt;
6834       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6835       return TRUE;
6836     }
6837     return FALSE;
6838 }
6839
6840 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6841 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6842 int lastLoadGameUseList = FALSE;
6843 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6844 ChessMove lastLoadGameStart = EndOfFile;
6845 int doubleClick;
6846 Boolean addToBookFlag;
6847
6848 void
6849 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6850 {
6851     ChessMove moveType;
6852     ChessSquare pup;
6853     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6854
6855     /* Check if the user is playing in turn.  This is complicated because we
6856        let the user "pick up" a piece before it is his turn.  So the piece he
6857        tried to pick up may have been captured by the time he puts it down!
6858        Therefore we use the color the user is supposed to be playing in this
6859        test, not the color of the piece that is currently on the starting
6860        square---except in EditGame mode, where the user is playing both
6861        sides; fortunately there the capture race can't happen.  (It can
6862        now happen in IcsExamining mode, but that's just too bad.  The user
6863        will get a somewhat confusing message in that case.)
6864        */
6865
6866     switch (gameMode) {
6867       case AnalyzeFile:
6868       case TwoMachinesPlay:
6869       case EndOfGame:
6870       case IcsObserving:
6871       case IcsIdle:
6872         /* We switched into a game mode where moves are not accepted,
6873            perhaps while the mouse button was down. */
6874         return;
6875
6876       case MachinePlaysWhite:
6877         /* User is moving for Black */
6878         if (WhiteOnMove(currentMove)) {
6879             DisplayMoveError(_("It is White's turn"));
6880             return;
6881         }
6882         break;
6883
6884       case MachinePlaysBlack:
6885         /* User is moving for White */
6886         if (!WhiteOnMove(currentMove)) {
6887             DisplayMoveError(_("It is Black's turn"));
6888             return;
6889         }
6890         break;
6891
6892       case PlayFromGameFile:
6893             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6894       case EditGame:
6895       case IcsExamining:
6896       case BeginningOfGame:
6897       case AnalyzeMode:
6898       case Training:
6899         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6900         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6901             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6902             /* User is moving for Black */
6903             if (WhiteOnMove(currentMove)) {
6904                 DisplayMoveError(_("It is White's turn"));
6905                 return;
6906             }
6907         } else {
6908             /* User is moving for White */
6909             if (!WhiteOnMove(currentMove)) {
6910                 DisplayMoveError(_("It is Black's turn"));
6911                 return;
6912             }
6913         }
6914         break;
6915
6916       case IcsPlayingBlack:
6917         /* User is moving for Black */
6918         if (WhiteOnMove(currentMove)) {
6919             if (!appData.premove) {
6920                 DisplayMoveError(_("It is White's turn"));
6921             } else if (toX >= 0 && toY >= 0) {
6922                 premoveToX = toX;
6923                 premoveToY = toY;
6924                 premoveFromX = fromX;
6925                 premoveFromY = fromY;
6926                 premovePromoChar = promoChar;
6927                 gotPremove = 1;
6928                 if (appData.debugMode)
6929                     fprintf(debugFP, "Got premove: fromX %d,"
6930                             "fromY %d, toX %d, toY %d\n",
6931                             fromX, fromY, toX, toY);
6932             }
6933             return;
6934         }
6935         break;
6936
6937       case IcsPlayingWhite:
6938         /* User is moving for White */
6939         if (!WhiteOnMove(currentMove)) {
6940             if (!appData.premove) {
6941                 DisplayMoveError(_("It is Black's turn"));
6942             } else if (toX >= 0 && toY >= 0) {
6943                 premoveToX = toX;
6944                 premoveToY = toY;
6945                 premoveFromX = fromX;
6946                 premoveFromY = fromY;
6947                 premovePromoChar = promoChar;
6948                 gotPremove = 1;
6949                 if (appData.debugMode)
6950                     fprintf(debugFP, "Got premove: fromX %d,"
6951                             "fromY %d, toX %d, toY %d\n",
6952                             fromX, fromY, toX, toY);
6953             }
6954             return;
6955         }
6956         break;
6957
6958       default:
6959         break;
6960
6961       case EditPosition:
6962         /* EditPosition, empty square, or different color piece;
6963            click-click move is possible */
6964         if (toX == -2 || toY == -2) {
6965             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6966             DrawPosition(FALSE, boards[currentMove]);
6967             return;
6968         } else if (toX >= 0 && toY >= 0) {
6969             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6970                 ChessSquare q, p = boards[0][rf][ff];
6971                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6972                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6973                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6974                 if(PieceToChar(q) == '+') gatingPiece = p;
6975             }
6976             boards[0][toY][toX] = boards[0][fromY][fromX];
6977             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6978                 if(boards[0][fromY][0] != EmptySquare) {
6979                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6980                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6981                 }
6982             } else
6983             if(fromX == BOARD_RGHT+1) {
6984                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6985                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6986                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6987                 }
6988             } else
6989             boards[0][fromY][fromX] = gatingPiece;
6990             DrawPosition(FALSE, boards[currentMove]);
6991             return;
6992         }
6993         return;
6994     }
6995
6996     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6997     pup = boards[currentMove][toY][toX];
6998
6999     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7000     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7001          if( pup != EmptySquare ) return;
7002          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7003            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7004                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7005            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7006            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7007            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7008            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7009          fromY = DROP_RANK;
7010     }
7011
7012     /* [HGM] always test for legality, to get promotion info */
7013     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7014                                          fromY, fromX, toY, toX, promoChar);
7015
7016     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7017
7018     /* [HGM] but possibly ignore an IllegalMove result */
7019     if (appData.testLegality) {
7020         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7021             DisplayMoveError(_("Illegal move"));
7022             return;
7023         }
7024     }
7025
7026     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7027         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7028              ClearPremoveHighlights(); // was included
7029         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7030         return;
7031     }
7032
7033     if(addToBookFlag) { // adding moves to book
7034         char buf[MSG_SIZ], move[MSG_SIZ];
7035         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7036         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7037         AddBookMove(buf);
7038         addToBookFlag = FALSE;
7039         ClearHighlights();
7040         return;
7041     }
7042
7043     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7044 }
7045
7046 /* Common tail of UserMoveEvent and DropMenuEvent */
7047 int
7048 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7049 {
7050     char *bookHit = 0;
7051
7052     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7053         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7054         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7055         if(WhiteOnMove(currentMove)) {
7056             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7057         } else {
7058             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7059         }
7060     }
7061
7062     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7063        move type in caller when we know the move is a legal promotion */
7064     if(moveType == NormalMove && promoChar)
7065         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7066
7067     /* [HGM] <popupFix> The following if has been moved here from
7068        UserMoveEvent(). Because it seemed to belong here (why not allow
7069        piece drops in training games?), and because it can only be
7070        performed after it is known to what we promote. */
7071     if (gameMode == Training) {
7072       /* compare the move played on the board to the next move in the
7073        * game. If they match, display the move and the opponent's response.
7074        * If they don't match, display an error message.
7075        */
7076       int saveAnimate;
7077       Board testBoard;
7078       CopyBoard(testBoard, boards[currentMove]);
7079       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7080
7081       if (CompareBoards(testBoard, boards[currentMove+1])) {
7082         ForwardInner(currentMove+1);
7083
7084         /* Autoplay the opponent's response.
7085          * if appData.animate was TRUE when Training mode was entered,
7086          * the response will be animated.
7087          */
7088         saveAnimate = appData.animate;
7089         appData.animate = animateTraining;
7090         ForwardInner(currentMove+1);
7091         appData.animate = saveAnimate;
7092
7093         /* check for the end of the game */
7094         if (currentMove >= forwardMostMove) {
7095           gameMode = PlayFromGameFile;
7096           ModeHighlight();
7097           SetTrainingModeOff();
7098           DisplayInformation(_("End of game"));
7099         }
7100       } else {
7101         DisplayError(_("Incorrect move"), 0);
7102       }
7103       return 1;
7104     }
7105
7106   /* Ok, now we know that the move is good, so we can kill
7107      the previous line in Analysis Mode */
7108   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7109                                 && currentMove < forwardMostMove) {
7110     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7111     else forwardMostMove = currentMove;
7112   }
7113
7114   ClearMap();
7115
7116   /* If we need the chess program but it's dead, restart it */
7117   ResurrectChessProgram();
7118
7119   /* A user move restarts a paused game*/
7120   if (pausing)
7121     PauseEvent();
7122
7123   thinkOutput[0] = NULLCHAR;
7124
7125   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7126
7127   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7128     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7129     return 1;
7130   }
7131
7132   if (gameMode == BeginningOfGame) {
7133     if (appData.noChessProgram) {
7134       gameMode = EditGame;
7135       SetGameInfo();
7136     } else {
7137       char buf[MSG_SIZ];
7138       gameMode = MachinePlaysBlack;
7139       StartClocks();
7140       SetGameInfo();
7141       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7142       DisplayTitle(buf);
7143       if (first.sendName) {
7144         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7145         SendToProgram(buf, &first);
7146       }
7147       StartClocks();
7148     }
7149     ModeHighlight();
7150   }
7151
7152   /* Relay move to ICS or chess engine */
7153   if (appData.icsActive) {
7154     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7155         gameMode == IcsExamining) {
7156       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7157         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7158         SendToICS("draw ");
7159         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7160       }
7161       // also send plain move, in case ICS does not understand atomic claims
7162       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7163       ics_user_moved = 1;
7164     }
7165   } else {
7166     if (first.sendTime && (gameMode == BeginningOfGame ||
7167                            gameMode == MachinePlaysWhite ||
7168                            gameMode == MachinePlaysBlack)) {
7169       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7170     }
7171     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7172          // [HGM] book: if program might be playing, let it use book
7173         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7174         first.maybeThinking = TRUE;
7175     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7176         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7177         SendBoard(&first, currentMove+1);
7178         if(second.analyzing) {
7179             if(!second.useSetboard) SendToProgram("undo\n", &second);
7180             SendBoard(&second, currentMove+1);
7181         }
7182     } else {
7183         SendMoveToProgram(forwardMostMove-1, &first);
7184         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7185     }
7186     if (currentMove == cmailOldMove + 1) {
7187       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7188     }
7189   }
7190
7191   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7192
7193   switch (gameMode) {
7194   case EditGame:
7195     if(appData.testLegality)
7196     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7197     case MT_NONE:
7198     case MT_CHECK:
7199       break;
7200     case MT_CHECKMATE:
7201     case MT_STAINMATE:
7202       if (WhiteOnMove(currentMove)) {
7203         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7204       } else {
7205         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7206       }
7207       break;
7208     case MT_STALEMATE:
7209       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7210       break;
7211     }
7212     break;
7213
7214   case MachinePlaysBlack:
7215   case MachinePlaysWhite:
7216     /* disable certain menu options while machine is thinking */
7217     SetMachineThinkingEnables();
7218     break;
7219
7220   default:
7221     break;
7222   }
7223
7224   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7225   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7226
7227   if(bookHit) { // [HGM] book: simulate book reply
7228         static char bookMove[MSG_SIZ]; // a bit generous?
7229
7230         programStats.nodes = programStats.depth = programStats.time =
7231         programStats.score = programStats.got_only_move = 0;
7232         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7233
7234         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7235         strcat(bookMove, bookHit);
7236         HandleMachineMove(bookMove, &first);
7237   }
7238   return 1;
7239 }
7240
7241 void
7242 MarkByFEN(char *fen)
7243 {
7244         int r, f;
7245         if(!appData.markers || !appData.highlightDragging) return;
7246         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7247         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7248         while(*fen) {
7249             int s = 0;
7250             marker[r][f] = 0;
7251             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7252             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7253             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7254             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7255             if(*fen == 'T') marker[r][f++] = 0; else
7256             if(*fen == 'Y') marker[r][f++] = 1; else
7257             if(*fen == 'G') marker[r][f++] = 3; else
7258             if(*fen == 'B') marker[r][f++] = 4; else
7259             if(*fen == 'C') marker[r][f++] = 5; else
7260             if(*fen == 'M') marker[r][f++] = 6; else
7261             if(*fen == 'W') marker[r][f++] = 7; else
7262             if(*fen == 'D') marker[r][f++] = 8; else
7263             if(*fen == 'R') marker[r][f++] = 2; else {
7264                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7265               f += s; fen -= s>0;
7266             }
7267             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7268             if(r < 0) break;
7269             fen++;
7270         }
7271         DrawPosition(TRUE, NULL);
7272 }
7273
7274 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7275
7276 void
7277 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7278 {
7279     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7280     Markers *m = (Markers *) closure;
7281     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7282         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7283                          || kind == WhiteCapturesEnPassant
7284                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7285     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7286 }
7287
7288 static int hoverSavedValid;
7289
7290 void
7291 MarkTargetSquares (int clear)
7292 {
7293   int x, y, sum=0;
7294   if(clear) { // no reason to ever suppress clearing
7295     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7296     hoverSavedValid = 0;
7297     if(!sum) return; // nothing was cleared,no redraw needed
7298   } else {
7299     int capt = 0;
7300     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7301        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7302     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7303     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7304       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7305       if(capt)
7306       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7307     }
7308   }
7309   DrawPosition(FALSE, NULL);
7310 }
7311
7312 int
7313 Explode (Board board, int fromX, int fromY, int toX, int toY)
7314 {
7315     if(gameInfo.variant == VariantAtomic &&
7316        (board[toY][toX] != EmptySquare ||                     // capture?
7317         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7318                          board[fromY][fromX] == BlackPawn   )
7319       )) {
7320         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7321         return TRUE;
7322     }
7323     return FALSE;
7324 }
7325
7326 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7327
7328 int
7329 CanPromote (ChessSquare piece, int y)
7330 {
7331         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7332         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7333         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7334         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7335            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7336            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7337          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7338         return (piece == BlackPawn && y <= zone ||
7339                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7340                 piece == BlackLance && y <= zone ||
7341                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7342 }
7343
7344 void
7345 HoverEvent (int xPix, int yPix, int x, int y)
7346 {
7347         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7348         int r, f;
7349         if(!first.highlight) return;
7350         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7351         if(x == oldX && y == oldY) return; // only do something if we enter new square
7352         oldFromX = fromX; oldFromY = fromY;
7353         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7354           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7355             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7356           hoverSavedValid = 1;
7357         } else if(oldX != x || oldY != y) {
7358           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7359           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7360           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7361             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7362           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7363             char buf[MSG_SIZ];
7364             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7365             SendToProgram(buf, &first);
7366           }
7367           oldX = x; oldY = y;
7368 //        SetHighlights(fromX, fromY, x, y);
7369         }
7370 }
7371
7372 void ReportClick(char *action, int x, int y)
7373 {
7374         char buf[MSG_SIZ]; // Inform engine of what user does
7375         int r, f;
7376         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7377           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7378             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7379         if(!first.highlight || gameMode == EditPosition) return;
7380         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7381         SendToProgram(buf, &first);
7382 }
7383
7384 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7385
7386 void
7387 LeftClick (ClickType clickType, int xPix, int yPix)
7388 {
7389     int x, y;
7390     Boolean saveAnimate;
7391     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7392     char promoChoice = NULLCHAR;
7393     ChessSquare piece;
7394     static TimeMark lastClickTime, prevClickTime;
7395
7396     x = EventToSquare(xPix, BOARD_WIDTH);
7397     y = EventToSquare(yPix, BOARD_HEIGHT);
7398     if (!flipView && y >= 0) {
7399         y = BOARD_HEIGHT - 1 - y;
7400     }
7401     if (flipView && x >= 0) {
7402         x = BOARD_WIDTH - 1 - x;
7403     }
7404
7405     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7406         static int dummy;
7407         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7408         right = TRUE;
7409         return;
7410     }
7411
7412     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7413
7414     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7415
7416     if (clickType == Press) ErrorPopDown();
7417     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7418
7419     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7420         defaultPromoChoice = promoSweep;
7421         promoSweep = EmptySquare;   // terminate sweep
7422         promoDefaultAltered = TRUE;
7423         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7424     }
7425
7426     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7427         if(clickType == Release) return; // ignore upclick of click-click destination
7428         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7429         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7430         if(gameInfo.holdingsWidth &&
7431                 (WhiteOnMove(currentMove)
7432                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7433                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7434             // click in right holdings, for determining promotion piece
7435             ChessSquare p = boards[currentMove][y][x];
7436             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7437             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7438             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7439                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7440                 fromX = fromY = -1;
7441                 return;
7442             }
7443         }
7444         DrawPosition(FALSE, boards[currentMove]);
7445         return;
7446     }
7447
7448     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7449     if(clickType == Press
7450             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7451               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7452               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7453         return;
7454
7455     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7456         // could be static click on premove from-square: abort premove
7457         gotPremove = 0;
7458         ClearPremoveHighlights();
7459     }
7460
7461     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7462         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7463
7464     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7465         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7466                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7467         defaultPromoChoice = DefaultPromoChoice(side);
7468     }
7469
7470     autoQueen = appData.alwaysPromoteToQueen;
7471
7472     if (fromX == -1) {
7473       int originalY = y;
7474       gatingPiece = EmptySquare;
7475       if (clickType != Press) {
7476         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7477             DragPieceEnd(xPix, yPix); dragging = 0;
7478             DrawPosition(FALSE, NULL);
7479         }
7480         return;
7481       }
7482       doubleClick = FALSE;
7483       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7484         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7485       }
7486       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7487       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7488          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7489          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7490             /* First square */
7491             if (OKToStartUserMove(fromX, fromY)) {
7492                 second = 0;
7493                 ReportClick("lift", x, y);
7494                 MarkTargetSquares(0);
7495                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7496                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7497                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7498                     promoSweep = defaultPromoChoice;
7499                     selectFlag = 0; lastX = xPix; lastY = yPix;
7500                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7501                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7502                 }
7503                 if (appData.highlightDragging) {
7504                     SetHighlights(fromX, fromY, -1, -1);
7505                 } else {
7506                     ClearHighlights();
7507                 }
7508             } else fromX = fromY = -1;
7509             return;
7510         }
7511     }
7512 printf("to click %d,%d\n",x,y);
7513     /* fromX != -1 */
7514     if (clickType == Press && gameMode != EditPosition) {
7515         ChessSquare fromP;
7516         ChessSquare toP;
7517         int frc;
7518
7519         // ignore off-board to clicks
7520         if(y < 0 || x < 0) return;
7521
7522         /* Check if clicking again on the same color piece */
7523         fromP = boards[currentMove][fromY][fromX];
7524         toP = boards[currentMove][y][x];
7525         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7526         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7527             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7528            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7529              WhitePawn <= toP && toP <= WhiteKing &&
7530              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7531              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7532             (BlackPawn <= fromP && fromP <= BlackKing &&
7533              BlackPawn <= toP && toP <= BlackKing &&
7534              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7535              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7536             /* Clicked again on same color piece -- changed his mind */
7537             second = (x == fromX && y == fromY);
7538             killX = killY = -1;
7539             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7540                 second = FALSE; // first double-click rather than scond click
7541                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7542             }
7543             promoDefaultAltered = FALSE;
7544             MarkTargetSquares(1);
7545            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7546             if (appData.highlightDragging) {
7547                 SetHighlights(x, y, -1, -1);
7548             } else {
7549                 ClearHighlights();
7550             }
7551             if (OKToStartUserMove(x, y)) {
7552                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7553                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7554                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7555                  gatingPiece = boards[currentMove][fromY][fromX];
7556                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7557                 fromX = x;
7558                 fromY = y; dragging = 1;
7559                 if(!second) ReportClick("lift", x, y);
7560                 MarkTargetSquares(0);
7561                 DragPieceBegin(xPix, yPix, FALSE);
7562                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7563                     promoSweep = defaultPromoChoice;
7564                     selectFlag = 0; lastX = xPix; lastY = yPix;
7565                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7566                 }
7567             }
7568            }
7569            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7570            second = FALSE;
7571         }
7572         // ignore clicks on holdings
7573         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7574     }
7575 printf("A type=%d\n",clickType);
7576
7577     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7578         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7579         return;
7580     }
7581
7582     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7583         DragPieceEnd(xPix, yPix); dragging = 0;
7584         if(clearFlag) {
7585             // a deferred attempt to click-click move an empty square on top of a piece
7586             boards[currentMove][y][x] = EmptySquare;
7587             ClearHighlights();
7588             DrawPosition(FALSE, boards[currentMove]);
7589             fromX = fromY = -1; clearFlag = 0;
7590             return;
7591         }
7592         if (appData.animateDragging) {
7593             /* Undo animation damage if any */
7594             DrawPosition(FALSE, NULL);
7595         }
7596         if (second) {
7597             /* Second up/down in same square; just abort move */
7598             second = 0;
7599             fromX = fromY = -1;
7600             gatingPiece = EmptySquare;
7601             MarkTargetSquares(1);
7602             ClearHighlights();
7603             gotPremove = 0;
7604             ClearPremoveHighlights();
7605         } else {
7606             /* First upclick in same square; start click-click mode */
7607             SetHighlights(x, y, -1, -1);
7608         }
7609         return;
7610     }
7611
7612     clearFlag = 0;
7613 printf("B\n");
7614     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7615        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7616         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7617         DisplayMessage(_("only marked squares are legal"),"");
7618         DrawPosition(TRUE, NULL);
7619         return; // ignore to-click
7620     }
7621 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7622     /* we now have a different from- and (possibly off-board) to-square */
7623     /* Completed move */
7624     if(!sweepSelecting) {
7625         toX = x;
7626         toY = y;
7627     }
7628
7629     piece = boards[currentMove][fromY][fromX];
7630
7631     saveAnimate = appData.animate;
7632     if (clickType == Press) {
7633         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7634         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7635             // must be Edit Position mode with empty-square selected
7636             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7637             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7638             return;
7639         }
7640         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7641             return;
7642         }
7643         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7644             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7645         } else
7646         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7647         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7648           if(appData.sweepSelect) {
7649             promoSweep = defaultPromoChoice;
7650             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7651             selectFlag = 0; lastX = xPix; lastY = yPix;
7652             Sweep(0); // Pawn that is going to promote: preview promotion piece
7653             sweepSelecting = 1;
7654             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7655             MarkTargetSquares(1);
7656           }
7657           return; // promo popup appears on up-click
7658         }
7659         /* Finish clickclick move */
7660         if (appData.animate || appData.highlightLastMove) {
7661             SetHighlights(fromX, fromY, toX, toY);
7662         } else {
7663             ClearHighlights();
7664         }
7665     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7666         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7667         if (appData.animate || appData.highlightLastMove) {
7668             SetHighlights(fromX, fromY, toX, toY);
7669         } else {
7670             ClearHighlights();
7671         }
7672     } else {
7673 #if 0
7674 // [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
7675         /* Finish drag move */
7676         if (appData.highlightLastMove) {
7677             SetHighlights(fromX, fromY, toX, toY);
7678         } else {
7679             ClearHighlights();
7680         }
7681 #endif
7682         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7683         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7684           dragging *= 2;            // flag button-less dragging if we are dragging
7685           MarkTargetSquares(1);
7686           if(x == killX && y == killY) killX = killY = -1; else {
7687             killX = x; killY = y;     //remeber this square as intermediate
7688             ReportClick("put", x, y); // and inform engine
7689             ReportClick("lift", x, y);
7690             MarkTargetSquares(0);
7691             return;
7692           }
7693         }
7694         DragPieceEnd(xPix, yPix); dragging = 0;
7695         /* Don't animate move and drag both */
7696         appData.animate = FALSE;
7697     }
7698
7699     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7700     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7701         ChessSquare piece = boards[currentMove][fromY][fromX];
7702         if(gameMode == EditPosition && piece != EmptySquare &&
7703            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7704             int n;
7705
7706             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7707                 n = PieceToNumber(piece - (int)BlackPawn);
7708                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7709                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7710                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7711             } else
7712             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7713                 n = PieceToNumber(piece);
7714                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7715                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7716                 boards[currentMove][n][BOARD_WIDTH-2]++;
7717             }
7718             boards[currentMove][fromY][fromX] = EmptySquare;
7719         }
7720         ClearHighlights();
7721         fromX = fromY = -1;
7722         MarkTargetSquares(1);
7723         DrawPosition(TRUE, boards[currentMove]);
7724         return;
7725     }
7726
7727     // off-board moves should not be highlighted
7728     if(x < 0 || y < 0) ClearHighlights();
7729     else ReportClick("put", x, y);
7730
7731     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7732
7733     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7734
7735     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7736         SetHighlights(fromX, fromY, toX, toY);
7737         MarkTargetSquares(1);
7738         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7739             // [HGM] super: promotion to captured piece selected from holdings
7740             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7741             promotionChoice = TRUE;
7742             // kludge follows to temporarily execute move on display, without promoting yet
7743             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7744             boards[currentMove][toY][toX] = p;
7745             DrawPosition(FALSE, boards[currentMove]);
7746             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7747             boards[currentMove][toY][toX] = q;
7748             DisplayMessage("Click in holdings to choose piece", "");
7749             return;
7750         }
7751         PromotionPopUp(promoChoice);
7752     } else {
7753         int oldMove = currentMove;
7754         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7755         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7756         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7757         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7758            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7759             DrawPosition(TRUE, boards[currentMove]);
7760         MarkTargetSquares(1);
7761         fromX = fromY = -1;
7762     }
7763     appData.animate = saveAnimate;
7764     if (appData.animate || appData.animateDragging) {
7765         /* Undo animation damage if needed */
7766         DrawPosition(FALSE, NULL);
7767     }
7768 }
7769
7770 int
7771 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7772 {   // front-end-free part taken out of PieceMenuPopup
7773     int whichMenu; int xSqr, ySqr;
7774
7775     if(seekGraphUp) { // [HGM] seekgraph
7776         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7777         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7778         return -2;
7779     }
7780
7781     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7782          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7783         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7784         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7785         if(action == Press)   {
7786             originalFlip = flipView;
7787             flipView = !flipView; // temporarily flip board to see game from partners perspective
7788             DrawPosition(TRUE, partnerBoard);
7789             DisplayMessage(partnerStatus, "");
7790             partnerUp = TRUE;
7791         } else if(action == Release) {
7792             flipView = originalFlip;
7793             DrawPosition(TRUE, boards[currentMove]);
7794             partnerUp = FALSE;
7795         }
7796         return -2;
7797     }
7798
7799     xSqr = EventToSquare(x, BOARD_WIDTH);
7800     ySqr = EventToSquare(y, BOARD_HEIGHT);
7801     if (action == Release) {
7802         if(pieceSweep != EmptySquare) {
7803             EditPositionMenuEvent(pieceSweep, toX, toY);
7804             pieceSweep = EmptySquare;
7805         } else UnLoadPV(); // [HGM] pv
7806     }
7807     if (action != Press) return -2; // return code to be ignored
7808     switch (gameMode) {
7809       case IcsExamining:
7810         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7811       case EditPosition:
7812         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7813         if (xSqr < 0 || ySqr < 0) return -1;
7814         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7815         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7816         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7817         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7818         NextPiece(0);
7819         return 2; // grab
7820       case IcsObserving:
7821         if(!appData.icsEngineAnalyze) return -1;
7822       case IcsPlayingWhite:
7823       case IcsPlayingBlack:
7824         if(!appData.zippyPlay) goto noZip;
7825       case AnalyzeMode:
7826       case AnalyzeFile:
7827       case MachinePlaysWhite:
7828       case MachinePlaysBlack:
7829       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7830         if (!appData.dropMenu) {
7831           LoadPV(x, y);
7832           return 2; // flag front-end to grab mouse events
7833         }
7834         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7835            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7836       case EditGame:
7837       noZip:
7838         if (xSqr < 0 || ySqr < 0) return -1;
7839         if (!appData.dropMenu || appData.testLegality &&
7840             gameInfo.variant != VariantBughouse &&
7841             gameInfo.variant != VariantCrazyhouse) return -1;
7842         whichMenu = 1; // drop menu
7843         break;
7844       default:
7845         return -1;
7846     }
7847
7848     if (((*fromX = xSqr) < 0) ||
7849         ((*fromY = ySqr) < 0)) {
7850         *fromX = *fromY = -1;
7851         return -1;
7852     }
7853     if (flipView)
7854       *fromX = BOARD_WIDTH - 1 - *fromX;
7855     else
7856       *fromY = BOARD_HEIGHT - 1 - *fromY;
7857
7858     return whichMenu;
7859 }
7860
7861 void
7862 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7863 {
7864 //    char * hint = lastHint;
7865     FrontEndProgramStats stats;
7866
7867     stats.which = cps == &first ? 0 : 1;
7868     stats.depth = cpstats->depth;
7869     stats.nodes = cpstats->nodes;
7870     stats.score = cpstats->score;
7871     stats.time = cpstats->time;
7872     stats.pv = cpstats->movelist;
7873     stats.hint = lastHint;
7874     stats.an_move_index = 0;
7875     stats.an_move_count = 0;
7876
7877     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7878         stats.hint = cpstats->move_name;
7879         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7880         stats.an_move_count = cpstats->nr_moves;
7881     }
7882
7883     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
7884
7885     SetProgramStats( &stats );
7886 }
7887
7888 void
7889 ClearEngineOutputPane (int which)
7890 {
7891     static FrontEndProgramStats dummyStats;
7892     dummyStats.which = which;
7893     dummyStats.pv = "#";
7894     SetProgramStats( &dummyStats );
7895 }
7896
7897 #define MAXPLAYERS 500
7898
7899 char *
7900 TourneyStandings (int display)
7901 {
7902     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7903     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7904     char result, *p, *names[MAXPLAYERS];
7905
7906     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7907         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7908     names[0] = p = strdup(appData.participants);
7909     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7910
7911     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7912
7913     while(result = appData.results[nr]) {
7914         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7915         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7916         wScore = bScore = 0;
7917         switch(result) {
7918           case '+': wScore = 2; break;
7919           case '-': bScore = 2; break;
7920           case '=': wScore = bScore = 1; break;
7921           case ' ':
7922           case '*': return strdup("busy"); // tourney not finished
7923         }
7924         score[w] += wScore;
7925         score[b] += bScore;
7926         games[w]++;
7927         games[b]++;
7928         nr++;
7929     }
7930     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7931     for(w=0; w<nPlayers; w++) {
7932         bScore = -1;
7933         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7934         ranking[w] = b; points[w] = bScore; score[b] = -2;
7935     }
7936     p = malloc(nPlayers*34+1);
7937     for(w=0; w<nPlayers && w<display; w++)
7938         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7939     free(names[0]);
7940     return p;
7941 }
7942
7943 void
7944 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7945 {       // count all piece types
7946         int p, f, r;
7947         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7948         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7949         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7950                 p = board[r][f];
7951                 pCnt[p]++;
7952                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7953                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7954                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7955                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7956                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7957                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7958         }
7959 }
7960
7961 int
7962 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7963 {
7964         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7965         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7966
7967         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7968         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7969         if(myPawns == 2 && nMine == 3) // KPP
7970             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7971         if(myPawns == 1 && nMine == 2) // KP
7972             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7973         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7974             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7975         if(myPawns) return FALSE;
7976         if(pCnt[WhiteRook+side])
7977             return pCnt[BlackRook-side] ||
7978                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7979                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7980                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7981         if(pCnt[WhiteCannon+side]) {
7982             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7983             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7984         }
7985         if(pCnt[WhiteKnight+side])
7986             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7987         return FALSE;
7988 }
7989
7990 int
7991 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7992 {
7993         VariantClass v = gameInfo.variant;
7994
7995         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7996         if(v == VariantShatranj) return TRUE; // always winnable through baring
7997         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7998         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7999
8000         if(v == VariantXiangqi) {
8001                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8002
8003                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8004                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8005                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8006                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8007                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8008                 if(stale) // we have at least one last-rank P plus perhaps C
8009                     return majors // KPKX
8010                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8011                 else // KCA*E*
8012                     return pCnt[WhiteFerz+side] // KCAK
8013                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8014                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8015                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8016
8017         } else if(v == VariantKnightmate) {
8018                 if(nMine == 1) return FALSE;
8019                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8020         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8021                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8022
8023                 if(nMine == 1) return FALSE; // bare King
8024                 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
8025                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8026                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8027                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8028                 if(pCnt[WhiteKnight+side])
8029                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8030                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8031                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8032                 if(nBishops)
8033                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8034                 if(pCnt[WhiteAlfil+side])
8035                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8036                 if(pCnt[WhiteWazir+side])
8037                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8038         }
8039
8040         return TRUE;
8041 }
8042
8043 int
8044 CompareWithRights (Board b1, Board b2)
8045 {
8046     int rights = 0;
8047     if(!CompareBoards(b1, b2)) return FALSE;
8048     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8049     /* compare castling rights */
8050     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8051            rights++; /* King lost rights, while rook still had them */
8052     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8053         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8054            rights++; /* but at least one rook lost them */
8055     }
8056     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8057            rights++;
8058     if( b1[CASTLING][5] != NoRights ) {
8059         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8060            rights++;
8061     }
8062     return rights == 0;
8063 }
8064
8065 int
8066 Adjudicate (ChessProgramState *cps)
8067 {       // [HGM] some adjudications useful with buggy engines
8068         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8069         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8070         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8071         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8072         int k, drop, count = 0; static int bare = 1;
8073         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8074         Boolean canAdjudicate = !appData.icsActive;
8075
8076         // most tests only when we understand the game, i.e. legality-checking on
8077             if( appData.testLegality )
8078             {   /* [HGM] Some more adjudications for obstinate engines */
8079                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8080                 static int moveCount = 6;
8081                 ChessMove result;
8082                 char *reason = NULL;
8083
8084                 /* Count what is on board. */
8085                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8086
8087                 /* Some material-based adjudications that have to be made before stalemate test */
8088                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8089                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8090                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8091                      if(canAdjudicate && appData.checkMates) {
8092                          if(engineOpponent)
8093                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8094                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8095                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8096                          return 1;
8097                      }
8098                 }
8099
8100                 /* Bare King in Shatranj (loses) or Losers (wins) */
8101                 if( nrW == 1 || nrB == 1) {
8102                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8103                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8104                      if(canAdjudicate && appData.checkMates) {
8105                          if(engineOpponent)
8106                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8107                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8108                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8109                          return 1;
8110                      }
8111                   } else
8112                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8113                   {    /* bare King */
8114                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8115                         if(canAdjudicate && appData.checkMates) {
8116                             /* but only adjudicate if adjudication enabled */
8117                             if(engineOpponent)
8118                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8119                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8120                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8121                             return 1;
8122                         }
8123                   }
8124                 } else bare = 1;
8125
8126
8127             // don't wait for engine to announce game end if we can judge ourselves
8128             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8129               case MT_CHECK:
8130                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8131                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8132                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8133                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8134                             checkCnt++;
8135                         if(checkCnt >= 2) {
8136                             reason = "Xboard adjudication: 3rd check";
8137                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8138                             break;
8139                         }
8140                     }
8141                 }
8142               case MT_NONE:
8143               default:
8144                 break;
8145               case MT_STEALMATE:
8146               case MT_STALEMATE:
8147               case MT_STAINMATE:
8148                 reason = "Xboard adjudication: Stalemate";
8149                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8150                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8151                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8152                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8153                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8154                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8155                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8156                                                                         EP_CHECKMATE : EP_WINS);
8157                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8158                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8159                 }
8160                 break;
8161               case MT_CHECKMATE:
8162                 reason = "Xboard adjudication: Checkmate";
8163                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8164                 if(gameInfo.variant == VariantShogi) {
8165                     if(forwardMostMove > backwardMostMove
8166                        && moveList[forwardMostMove-1][1] == '@'
8167                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8168                         reason = "XBoard adjudication: pawn-drop mate";
8169                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8170                     }
8171                 }
8172                 break;
8173             }
8174
8175                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8176                     case EP_STALEMATE:
8177                         result = GameIsDrawn; break;
8178                     case EP_CHECKMATE:
8179                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8180                     case EP_WINS:
8181                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8182                     default:
8183                         result = EndOfFile;
8184                 }
8185                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8186                     if(engineOpponent)
8187                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8188                     GameEnds( result, reason, GE_XBOARD );
8189                     return 1;
8190                 }
8191
8192                 /* Next absolutely insufficient mating material. */
8193                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8194                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8195                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8196
8197                      /* always flag draws, for judging claims */
8198                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8199
8200                      if(canAdjudicate && appData.materialDraws) {
8201                          /* but only adjudicate them if adjudication enabled */
8202                          if(engineOpponent) {
8203                            SendToProgram("force\n", engineOpponent); // suppress reply
8204                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8205                          }
8206                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8207                          return 1;
8208                      }
8209                 }
8210
8211                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8212                 if(gameInfo.variant == VariantXiangqi ?
8213                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8214                  : nrW + nrB == 4 &&
8215                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8216                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8217                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8218                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8219                    ) ) {
8220                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8221                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8222                           if(engineOpponent) {
8223                             SendToProgram("force\n", engineOpponent); // suppress reply
8224                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8225                           }
8226                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8227                           return 1;
8228                      }
8229                 } else moveCount = 6;
8230             }
8231
8232         // Repetition draws and 50-move rule can be applied independently of legality testing
8233
8234                 /* Check for rep-draws */
8235                 count = 0;
8236                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8237                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8238                 for(k = forwardMostMove-2;
8239                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8240                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8241                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8242                     k-=2)
8243                 {   int rights=0;
8244                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8245                         /* compare castling rights */
8246                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8247                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8248                                 rights++; /* King lost rights, while rook still had them */
8249                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8250                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8251                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8252                                    rights++; /* but at least one rook lost them */
8253                         }
8254                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8255                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8256                                 rights++;
8257                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8258                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8259                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8260                                    rights++;
8261                         }
8262                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8263                             && appData.drawRepeats > 1) {
8264                              /* adjudicate after user-specified nr of repeats */
8265                              int result = GameIsDrawn;
8266                              char *details = "XBoard adjudication: repetition draw";
8267                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8268                                 // [HGM] xiangqi: check for forbidden perpetuals
8269                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8270                                 for(m=forwardMostMove; m>k; m-=2) {
8271                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8272                                         ourPerpetual = 0; // the current mover did not always check
8273                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8274                                         hisPerpetual = 0; // the opponent did not always check
8275                                 }
8276                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8277                                                                         ourPerpetual, hisPerpetual);
8278                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8279                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8280                                     details = "Xboard adjudication: perpetual checking";
8281                                 } else
8282                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8283                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8284                                 } else
8285                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8286                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8287                                         result = BlackWins;
8288                                         details = "Xboard adjudication: repetition";
8289                                     }
8290                                 } else // it must be XQ
8291                                 // Now check for perpetual chases
8292                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8293                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8294                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8295                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8296                                         static char resdet[MSG_SIZ];
8297                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8298                                         details = resdet;
8299                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8300                                     } else
8301                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8302                                         break; // Abort repetition-checking loop.
8303                                 }
8304                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8305                              }
8306                              if(engineOpponent) {
8307                                SendToProgram("force\n", engineOpponent); // suppress reply
8308                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8309                              }
8310                              GameEnds( result, details, GE_XBOARD );
8311                              return 1;
8312                         }
8313                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8314                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8315                     }
8316                 }
8317
8318                 /* Now we test for 50-move draws. Determine ply count */
8319                 count = forwardMostMove;
8320                 /* look for last irreversble move */
8321                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8322                     count--;
8323                 /* if we hit starting position, add initial plies */
8324                 if( count == backwardMostMove )
8325                     count -= initialRulePlies;
8326                 count = forwardMostMove - count;
8327                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8328                         // adjust reversible move counter for checks in Xiangqi
8329                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8330                         if(i < backwardMostMove) i = backwardMostMove;
8331                         while(i <= forwardMostMove) {
8332                                 lastCheck = inCheck; // check evasion does not count
8333                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8334                                 if(inCheck || lastCheck) count--; // check does not count
8335                                 i++;
8336                         }
8337                 }
8338                 if( count >= 100)
8339                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8340                          /* this is used to judge if draw claims are legal */
8341                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8342                          if(engineOpponent) {
8343                            SendToProgram("force\n", engineOpponent); // suppress reply
8344                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8345                          }
8346                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8347                          return 1;
8348                 }
8349
8350                 /* if draw offer is pending, treat it as a draw claim
8351                  * when draw condition present, to allow engines a way to
8352                  * claim draws before making their move to avoid a race
8353                  * condition occurring after their move
8354                  */
8355                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8356                          char *p = NULL;
8357                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8358                              p = "Draw claim: 50-move rule";
8359                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8360                              p = "Draw claim: 3-fold repetition";
8361                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8362                              p = "Draw claim: insufficient mating material";
8363                          if( p != NULL && canAdjudicate) {
8364                              if(engineOpponent) {
8365                                SendToProgram("force\n", engineOpponent); // suppress reply
8366                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8367                              }
8368                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8369                              return 1;
8370                          }
8371                 }
8372
8373                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8374                     if(engineOpponent) {
8375                       SendToProgram("force\n", engineOpponent); // suppress reply
8376                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8377                     }
8378                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8379                     return 1;
8380                 }
8381         return 0;
8382 }
8383
8384 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8385 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8386 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8387
8388 static int
8389 BitbaseProbe ()
8390 {
8391     int pieces[10], squares[10], cnt=0, r, f, res;
8392     static int loaded;
8393     static PPROBE_EGBB probeBB;
8394     if(!appData.testLegality) return 10;
8395     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8396     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8397     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8398     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8399         ChessSquare piece = boards[forwardMostMove][r][f];
8400         int black = (piece >= BlackPawn);
8401         int type = piece - black*BlackPawn;
8402         if(piece == EmptySquare) continue;
8403         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8404         if(type == WhiteKing) type = WhiteQueen + 1;
8405         type = egbbCode[type];
8406         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8407         pieces[cnt] = type + black*6;
8408         if(++cnt > 5) return 11;
8409     }
8410     pieces[cnt] = squares[cnt] = 0;
8411     // probe EGBB
8412     if(loaded == 2) return 13; // loading failed before
8413     if(loaded == 0) {
8414         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8415         HMODULE lib;
8416         PLOAD_EGBB loadBB;
8417         loaded = 2; // prepare for failure
8418         if(!path) return 13; // no egbb installed
8419         strncpy(buf, path + 8, MSG_SIZ);
8420         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8421         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8422         lib = LoadLibrary(buf);
8423         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8424         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8425         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8426         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8427         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8428         loaded = 1; // success!
8429     }
8430     res = probeBB(forwardMostMove & 1, pieces, squares);
8431     return res > 0 ? 1 : res < 0 ? -1 : 0;
8432 }
8433
8434 char *
8435 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8436 {   // [HGM] book: this routine intercepts moves to simulate book replies
8437     char *bookHit = NULL;
8438
8439     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8440         char buf[MSG_SIZ];
8441         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8442         SendToProgram(buf, cps);
8443     }
8444     //first determine if the incoming move brings opponent into his book
8445     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8446         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8447     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8448     if(bookHit != NULL && !cps->bookSuspend) {
8449         // make sure opponent is not going to reply after receiving move to book position
8450         SendToProgram("force\n", cps);
8451         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8452     }
8453     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8454     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8455     // now arrange restart after book miss
8456     if(bookHit) {
8457         // after a book hit we never send 'go', and the code after the call to this routine
8458         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8459         char buf[MSG_SIZ], *move = bookHit;
8460         if(cps->useSAN) {
8461             int fromX, fromY, toX, toY;
8462             char promoChar;
8463             ChessMove moveType;
8464             move = buf + 30;
8465             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8466                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8467                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8468                                     PosFlags(forwardMostMove),
8469                                     fromY, fromX, toY, toX, promoChar, move);
8470             } else {
8471                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8472                 bookHit = NULL;
8473             }
8474         }
8475         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8476         SendToProgram(buf, cps);
8477         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8478     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8479         SendToProgram("go\n", cps);
8480         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8481     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8482         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8483             SendToProgram("go\n", cps);
8484         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8485     }
8486     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8487 }
8488
8489 int
8490 LoadError (char *errmess, ChessProgramState *cps)
8491 {   // unloads engine and switches back to -ncp mode if it was first
8492     if(cps->initDone) return FALSE;
8493     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8494     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8495     cps->pr = NoProc;
8496     if(cps == &first) {
8497         appData.noChessProgram = TRUE;
8498         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8499         gameMode = BeginningOfGame; ModeHighlight();
8500         SetNCPMode();
8501     }
8502     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8503     DisplayMessage("", ""); // erase waiting message
8504     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8505     return TRUE;
8506 }
8507
8508 char *savedMessage;
8509 ChessProgramState *savedState;
8510 void
8511 DeferredBookMove (void)
8512 {
8513         if(savedState->lastPing != savedState->lastPong)
8514                     ScheduleDelayedEvent(DeferredBookMove, 10);
8515         else
8516         HandleMachineMove(savedMessage, savedState);
8517 }
8518
8519 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8520 static ChessProgramState *stalledEngine;
8521 static char stashedInputMove[MSG_SIZ];
8522
8523 void
8524 HandleMachineMove (char *message, ChessProgramState *cps)
8525 {
8526     static char firstLeg[20];
8527     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8528     char realname[MSG_SIZ];
8529     int fromX, fromY, toX, toY;
8530     ChessMove moveType;
8531     char promoChar, roar;
8532     char *p, *pv=buf1;
8533     int machineWhite, oldError;
8534     char *bookHit;
8535
8536     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8537         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8538         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8539             DisplayError(_("Invalid pairing from pairing engine"), 0);
8540             return;
8541         }
8542         pairingReceived = 1;
8543         NextMatchGame();
8544         return; // Skim the pairing messages here.
8545     }
8546
8547     oldError = cps->userError; cps->userError = 0;
8548
8549 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8550     /*
8551      * Kludge to ignore BEL characters
8552      */
8553     while (*message == '\007') message++;
8554
8555     /*
8556      * [HGM] engine debug message: ignore lines starting with '#' character
8557      */
8558     if(cps->debug && *message == '#') return;
8559
8560     /*
8561      * Look for book output
8562      */
8563     if (cps == &first && bookRequested) {
8564         if (message[0] == '\t' || message[0] == ' ') {
8565             /* Part of the book output is here; append it */
8566             strcat(bookOutput, message);
8567             strcat(bookOutput, "  \n");
8568             return;
8569         } else if (bookOutput[0] != NULLCHAR) {
8570             /* All of book output has arrived; display it */
8571             char *p = bookOutput;
8572             while (*p != NULLCHAR) {
8573                 if (*p == '\t') *p = ' ';
8574                 p++;
8575             }
8576             DisplayInformation(bookOutput);
8577             bookRequested = FALSE;
8578             /* Fall through to parse the current output */
8579         }
8580     }
8581
8582     /*
8583      * Look for machine move.
8584      */
8585     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8586         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8587     {
8588         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8589             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8590             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8591             stalledEngine = cps;
8592             if(appData.ponderNextMove) { // bring opponent out of ponder
8593                 if(gameMode == TwoMachinesPlay) {
8594                     if(cps->other->pause)
8595                         PauseEngine(cps->other);
8596                     else
8597                         SendToProgram("easy\n", cps->other);
8598                 }
8599             }
8600             StopClocks();
8601             return;
8602         }
8603
8604         /* This method is only useful on engines that support ping */
8605         if (cps->lastPing != cps->lastPong) {
8606           if (gameMode == BeginningOfGame) {
8607             /* Extra move from before last new; ignore */
8608             if (appData.debugMode) {
8609                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8610             }
8611           } else {
8612             if (appData.debugMode) {
8613                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8614                         cps->which, gameMode);
8615             }
8616
8617             SendToProgram("undo\n", cps);
8618           }
8619           return;
8620         }
8621
8622         switch (gameMode) {
8623           case BeginningOfGame:
8624             /* Extra move from before last reset; ignore */
8625             if (appData.debugMode) {
8626                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8627             }
8628             return;
8629
8630           case EndOfGame:
8631           case IcsIdle:
8632           default:
8633             /* Extra move after we tried to stop.  The mode test is
8634                not a reliable way of detecting this problem, but it's
8635                the best we can do on engines that don't support ping.
8636             */
8637             if (appData.debugMode) {
8638                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8639                         cps->which, gameMode);
8640             }
8641             SendToProgram("undo\n", cps);
8642             return;
8643
8644           case MachinePlaysWhite:
8645           case IcsPlayingWhite:
8646             machineWhite = TRUE;
8647             break;
8648
8649           case MachinePlaysBlack:
8650           case IcsPlayingBlack:
8651             machineWhite = FALSE;
8652             break;
8653
8654           case TwoMachinesPlay:
8655             machineWhite = (cps->twoMachinesColor[0] == 'w');
8656             break;
8657         }
8658         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8659             if (appData.debugMode) {
8660                 fprintf(debugFP,
8661                         "Ignoring move out of turn by %s, gameMode %d"
8662                         ", forwardMost %d\n",
8663                         cps->which, gameMode, forwardMostMove);
8664             }
8665             return;
8666         }
8667
8668         if(cps->alphaRank) AlphaRank(machineMove, 4);
8669
8670         // [HGM] lion: (some very limited) support for Alien protocol
8671         killX = killY = -1;
8672         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8673             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8674             return;
8675         } else if(firstLeg[0]) { // there was a previous leg;
8676             // only support case where same piece makes two step
8677             char buf[20], *p = machineMove+1, *q = buf+1, f;
8678             safeStrCpy(buf, machineMove, 20);
8679             while(isdigit(*q)) q++; // find start of to-square
8680             safeStrCpy(machineMove, firstLeg, 20);
8681             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8682             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8683             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8684             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8685             firstLeg[0] = NULLCHAR;
8686         }
8687
8688         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8689                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8690             /* Machine move could not be parsed; ignore it. */
8691           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8692                     machineMove, _(cps->which));
8693             DisplayMoveError(buf1);
8694             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8695                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8696             if (gameMode == TwoMachinesPlay) {
8697               GameEnds(machineWhite ? BlackWins : WhiteWins,
8698                        buf1, GE_XBOARD);
8699             }
8700             return;
8701         }
8702
8703         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8704         /* So we have to redo legality test with true e.p. status here,  */
8705         /* to make sure an illegal e.p. capture does not slip through,   */
8706         /* to cause a forfeit on a justified illegal-move complaint      */
8707         /* of the opponent.                                              */
8708         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8709            ChessMove moveType;
8710            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8711                              fromY, fromX, toY, toX, promoChar);
8712             if(moveType == IllegalMove) {
8713               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8714                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8715                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8716                            buf1, GE_XBOARD);
8717                 return;
8718            } else if(!appData.fischerCastling)
8719            /* [HGM] Kludge to handle engines that send FRC-style castling
8720               when they shouldn't (like TSCP-Gothic) */
8721            switch(moveType) {
8722              case WhiteASideCastleFR:
8723              case BlackASideCastleFR:
8724                toX+=2;
8725                currentMoveString[2]++;
8726                break;
8727              case WhiteHSideCastleFR:
8728              case BlackHSideCastleFR:
8729                toX--;
8730                currentMoveString[2]--;
8731                break;
8732              default: ; // nothing to do, but suppresses warning of pedantic compilers
8733            }
8734         }
8735         hintRequested = FALSE;
8736         lastHint[0] = NULLCHAR;
8737         bookRequested = FALSE;
8738         /* Program may be pondering now */
8739         cps->maybeThinking = TRUE;
8740         if (cps->sendTime == 2) cps->sendTime = 1;
8741         if (cps->offeredDraw) cps->offeredDraw--;
8742
8743         /* [AS] Save move info*/
8744         pvInfoList[ forwardMostMove ].score = programStats.score;
8745         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8746         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8747
8748         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8749
8750         /* Test suites abort the 'game' after one move */
8751         if(*appData.finger) {
8752            static FILE *f;
8753            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8754            if(!f) f = fopen(appData.finger, "w");
8755            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8756            else { DisplayFatalError("Bad output file", errno, 0); return; }
8757            free(fen);
8758            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8759         }
8760
8761         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8762         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8763             int count = 0;
8764
8765             while( count < adjudicateLossPlies ) {
8766                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8767
8768                 if( count & 1 ) {
8769                     score = -score; /* Flip score for winning side */
8770                 }
8771
8772                 if( score > appData.adjudicateLossThreshold ) {
8773                     break;
8774                 }
8775
8776                 count++;
8777             }
8778
8779             if( count >= adjudicateLossPlies ) {
8780                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8781
8782                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8783                     "Xboard adjudication",
8784                     GE_XBOARD );
8785
8786                 return;
8787             }
8788         }
8789
8790         if(Adjudicate(cps)) {
8791             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8792             return; // [HGM] adjudicate: for all automatic game ends
8793         }
8794
8795 #if ZIPPY
8796         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8797             first.initDone) {
8798           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8799                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8800                 SendToICS("draw ");
8801                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8802           }
8803           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8804           ics_user_moved = 1;
8805           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8806                 char buf[3*MSG_SIZ];
8807
8808                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8809                         programStats.score / 100.,
8810                         programStats.depth,
8811                         programStats.time / 100.,
8812                         (unsigned int)programStats.nodes,
8813                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8814                         programStats.movelist);
8815                 SendToICS(buf);
8816           }
8817         }
8818 #endif
8819
8820         /* [AS] Clear stats for next move */
8821         ClearProgramStats();
8822         thinkOutput[0] = NULLCHAR;
8823         hiddenThinkOutputState = 0;
8824
8825         bookHit = NULL;
8826         if (gameMode == TwoMachinesPlay) {
8827             /* [HGM] relaying draw offers moved to after reception of move */
8828             /* and interpreting offer as claim if it brings draw condition */
8829             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8830                 SendToProgram("draw\n", cps->other);
8831             }
8832             if (cps->other->sendTime) {
8833                 SendTimeRemaining(cps->other,
8834                                   cps->other->twoMachinesColor[0] == 'w');
8835             }
8836             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8837             if (firstMove && !bookHit) {
8838                 firstMove = FALSE;
8839                 if (cps->other->useColors) {
8840                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8841                 }
8842                 SendToProgram("go\n", cps->other);
8843             }
8844             cps->other->maybeThinking = TRUE;
8845         }
8846
8847         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8848
8849         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8850
8851         if (!pausing && appData.ringBellAfterMoves) {
8852             if(!roar) RingBell();
8853         }
8854
8855         /*
8856          * Reenable menu items that were disabled while
8857          * machine was thinking
8858          */
8859         if (gameMode != TwoMachinesPlay)
8860             SetUserThinkingEnables();
8861
8862         // [HGM] book: after book hit opponent has received move and is now in force mode
8863         // force the book reply into it, and then fake that it outputted this move by jumping
8864         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8865         if(bookHit) {
8866                 static char bookMove[MSG_SIZ]; // a bit generous?
8867
8868                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8869                 strcat(bookMove, bookHit);
8870                 message = bookMove;
8871                 cps = cps->other;
8872                 programStats.nodes = programStats.depth = programStats.time =
8873                 programStats.score = programStats.got_only_move = 0;
8874                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8875
8876                 if(cps->lastPing != cps->lastPong) {
8877                     savedMessage = message; // args for deferred call
8878                     savedState = cps;
8879                     ScheduleDelayedEvent(DeferredBookMove, 10);
8880                     return;
8881                 }
8882                 goto FakeBookMove;
8883         }
8884
8885         return;
8886     }
8887
8888     /* Set special modes for chess engines.  Later something general
8889      *  could be added here; for now there is just one kludge feature,
8890      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8891      *  when "xboard" is given as an interactive command.
8892      */
8893     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8894         cps->useSigint = FALSE;
8895         cps->useSigterm = FALSE;
8896     }
8897     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8898       ParseFeatures(message+8, cps);
8899       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8900     }
8901
8902     if (!strncmp(message, "setup ", 6) && 
8903         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8904           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8905                                         ) { // [HGM] allow first engine to define opening position
8906       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8907       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8908       *buf = NULLCHAR;
8909       if(sscanf(message, "setup (%s", buf) == 1) {
8910         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8911         ASSIGN(appData.pieceToCharTable, buf);
8912       }
8913       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8914       if(dummy >= 3) {
8915         while(message[s] && message[s++] != ' ');
8916         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8917            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8918             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8919             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8920           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8921           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8922           startedFromSetupPosition = FALSE;
8923         }
8924       }
8925       if(startedFromSetupPosition) return;
8926       ParseFEN(boards[0], &dummy, message+s, FALSE);
8927       DrawPosition(TRUE, boards[0]);
8928       CopyBoard(initialPosition, boards[0]);
8929       startedFromSetupPosition = TRUE;
8930       return;
8931     }
8932     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8933       ChessSquare piece = WhitePawn;
8934       char *p=buf2;
8935       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8936       piece += CharToPiece(*p) - WhitePawn;
8937       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8938       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8939       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8940       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8941       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8942       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8943                                                && gameInfo.variant != VariantGreat
8944                                                && gameInfo.variant != VariantFairy    ) return;
8945       if(piece < EmptySquare) {
8946         pieceDefs = TRUE;
8947         ASSIGN(pieceDesc[piece], buf1);
8948         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8949       }
8950       return;
8951     }
8952     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8953      * want this, I was asked to put it in, and obliged.
8954      */
8955     if (!strncmp(message, "setboard ", 9)) {
8956         Board initial_position;
8957
8958         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8959
8960         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8961             DisplayError(_("Bad FEN received from engine"), 0);
8962             return ;
8963         } else {
8964            Reset(TRUE, FALSE);
8965            CopyBoard(boards[0], initial_position);
8966            initialRulePlies = FENrulePlies;
8967            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8968            else gameMode = MachinePlaysBlack;
8969            DrawPosition(FALSE, boards[currentMove]);
8970         }
8971         return;
8972     }
8973
8974     /*
8975      * Look for communication commands
8976      */
8977     if (!strncmp(message, "telluser ", 9)) {
8978         if(message[9] == '\\' && message[10] == '\\')
8979             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8980         PlayTellSound();
8981         DisplayNote(message + 9);
8982         return;
8983     }
8984     if (!strncmp(message, "tellusererror ", 14)) {
8985         cps->userError = 1;
8986         if(message[14] == '\\' && message[15] == '\\')
8987             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8988         PlayTellSound();
8989         DisplayError(message + 14, 0);
8990         return;
8991     }
8992     if (!strncmp(message, "tellopponent ", 13)) {
8993       if (appData.icsActive) {
8994         if (loggedOn) {
8995           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8996           SendToICS(buf1);
8997         }
8998       } else {
8999         DisplayNote(message + 13);
9000       }
9001       return;
9002     }
9003     if (!strncmp(message, "tellothers ", 11)) {
9004       if (appData.icsActive) {
9005         if (loggedOn) {
9006           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9007           SendToICS(buf1);
9008         }
9009       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9010       return;
9011     }
9012     if (!strncmp(message, "tellall ", 8)) {
9013       if (appData.icsActive) {
9014         if (loggedOn) {
9015           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9016           SendToICS(buf1);
9017         }
9018       } else {
9019         DisplayNote(message + 8);
9020       }
9021       return;
9022     }
9023     if (strncmp(message, "warning", 7) == 0) {
9024         /* Undocumented feature, use tellusererror in new code */
9025         DisplayError(message, 0);
9026         return;
9027     }
9028     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9029         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9030         strcat(realname, " query");
9031         AskQuestion(realname, buf2, buf1, cps->pr);
9032         return;
9033     }
9034     /* Commands from the engine directly to ICS.  We don't allow these to be
9035      *  sent until we are logged on. Crafty kibitzes have been known to
9036      *  interfere with the login process.
9037      */
9038     if (loggedOn) {
9039         if (!strncmp(message, "tellics ", 8)) {
9040             SendToICS(message + 8);
9041             SendToICS("\n");
9042             return;
9043         }
9044         if (!strncmp(message, "tellicsnoalias ", 15)) {
9045             SendToICS(ics_prefix);
9046             SendToICS(message + 15);
9047             SendToICS("\n");
9048             return;
9049         }
9050         /* The following are for backward compatibility only */
9051         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9052             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9053             SendToICS(ics_prefix);
9054             SendToICS(message);
9055             SendToICS("\n");
9056             return;
9057         }
9058     }
9059     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9060         if(initPing == cps->lastPong) {
9061             if(gameInfo.variant == VariantUnknown) {
9062                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9063                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9064                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9065             }
9066             initPing = -1;
9067         }
9068         return;
9069     }
9070     if(!strncmp(message, "highlight ", 10)) {
9071         if(appData.testLegality && appData.markers) return;
9072         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9073         return;
9074     }
9075     if(!strncmp(message, "click ", 6)) {
9076         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9077         if(appData.testLegality || !appData.oneClick) return;
9078         sscanf(message+6, "%c%d%c", &f, &y, &c);
9079         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9080         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9081         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9082         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9083         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9084         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9085             LeftClick(Release, lastLeftX, lastLeftY);
9086         controlKey  = (c == ',');
9087         LeftClick(Press, x, y);
9088         LeftClick(Release, x, y);
9089         first.highlight = f;
9090         return;
9091     }
9092     /*
9093      * If the move is illegal, cancel it and redraw the board.
9094      * Also deal with other error cases.  Matching is rather loose
9095      * here to accommodate engines written before the spec.
9096      */
9097     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9098         strncmp(message, "Error", 5) == 0) {
9099         if (StrStr(message, "name") ||
9100             StrStr(message, "rating") || StrStr(message, "?") ||
9101             StrStr(message, "result") || StrStr(message, "board") ||
9102             StrStr(message, "bk") || StrStr(message, "computer") ||
9103             StrStr(message, "variant") || StrStr(message, "hint") ||
9104             StrStr(message, "random") || StrStr(message, "depth") ||
9105             StrStr(message, "accepted")) {
9106             return;
9107         }
9108         if (StrStr(message, "protover")) {
9109           /* Program is responding to input, so it's apparently done
9110              initializing, and this error message indicates it is
9111              protocol version 1.  So we don't need to wait any longer
9112              for it to initialize and send feature commands. */
9113           FeatureDone(cps, 1);
9114           cps->protocolVersion = 1;
9115           return;
9116         }
9117         cps->maybeThinking = FALSE;
9118
9119         if (StrStr(message, "draw")) {
9120             /* Program doesn't have "draw" command */
9121             cps->sendDrawOffers = 0;
9122             return;
9123         }
9124         if (cps->sendTime != 1 &&
9125             (StrStr(message, "time") || StrStr(message, "otim"))) {
9126           /* Program apparently doesn't have "time" or "otim" command */
9127           cps->sendTime = 0;
9128           return;
9129         }
9130         if (StrStr(message, "analyze")) {
9131             cps->analysisSupport = FALSE;
9132             cps->analyzing = FALSE;
9133 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9134             EditGameEvent(); // [HGM] try to preserve loaded game
9135             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9136             DisplayError(buf2, 0);
9137             return;
9138         }
9139         if (StrStr(message, "(no matching move)st")) {
9140           /* Special kludge for GNU Chess 4 only */
9141           cps->stKludge = TRUE;
9142           SendTimeControl(cps, movesPerSession, timeControl,
9143                           timeIncrement, appData.searchDepth,
9144                           searchTime);
9145           return;
9146         }
9147         if (StrStr(message, "(no matching move)sd")) {
9148           /* Special kludge for GNU Chess 4 only */
9149           cps->sdKludge = TRUE;
9150           SendTimeControl(cps, movesPerSession, timeControl,
9151                           timeIncrement, appData.searchDepth,
9152                           searchTime);
9153           return;
9154         }
9155         if (!StrStr(message, "llegal")) {
9156             return;
9157         }
9158         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9159             gameMode == IcsIdle) return;
9160         if (forwardMostMove <= backwardMostMove) return;
9161         if (pausing) PauseEvent();
9162       if(appData.forceIllegal) {
9163             // [HGM] illegal: machine refused move; force position after move into it
9164           SendToProgram("force\n", cps);
9165           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9166                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9167                 // when black is to move, while there might be nothing on a2 or black
9168                 // might already have the move. So send the board as if white has the move.
9169                 // But first we must change the stm of the engine, as it refused the last move
9170                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9171                 if(WhiteOnMove(forwardMostMove)) {
9172                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9173                     SendBoard(cps, forwardMostMove); // kludgeless board
9174                 } else {
9175                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9176                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9177                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9178                 }
9179           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9180             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9181                  gameMode == TwoMachinesPlay)
9182               SendToProgram("go\n", cps);
9183             return;
9184       } else
9185         if (gameMode == PlayFromGameFile) {
9186             /* Stop reading this game file */
9187             gameMode = EditGame;
9188             ModeHighlight();
9189         }
9190         /* [HGM] illegal-move claim should forfeit game when Xboard */
9191         /* only passes fully legal moves                            */
9192         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9193             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9194                                 "False illegal-move claim", GE_XBOARD );
9195             return; // do not take back move we tested as valid
9196         }
9197         currentMove = forwardMostMove-1;
9198         DisplayMove(currentMove-1); /* before DisplayMoveError */
9199         SwitchClocks(forwardMostMove-1); // [HGM] race
9200         DisplayBothClocks();
9201         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9202                 parseList[currentMove], _(cps->which));
9203         DisplayMoveError(buf1);
9204         DrawPosition(FALSE, boards[currentMove]);
9205
9206         SetUserThinkingEnables();
9207         return;
9208     }
9209     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9210         /* Program has a broken "time" command that
9211            outputs a string not ending in newline.
9212            Don't use it. */
9213         cps->sendTime = 0;
9214     }
9215     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9216         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9217             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9218     }
9219
9220     /*
9221      * If chess program startup fails, exit with an error message.
9222      * Attempts to recover here are futile. [HGM] Well, we try anyway
9223      */
9224     if ((StrStr(message, "unknown host") != NULL)
9225         || (StrStr(message, "No remote directory") != NULL)
9226         || (StrStr(message, "not found") != NULL)
9227         || (StrStr(message, "No such file") != NULL)
9228         || (StrStr(message, "can't alloc") != NULL)
9229         || (StrStr(message, "Permission denied") != NULL)) {
9230
9231         cps->maybeThinking = FALSE;
9232         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9233                 _(cps->which), cps->program, cps->host, message);
9234         RemoveInputSource(cps->isr);
9235         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9236             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9237             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9238         }
9239         return;
9240     }
9241
9242     /*
9243      * Look for hint output
9244      */
9245     if (sscanf(message, "Hint: %s", buf1) == 1) {
9246         if (cps == &first && hintRequested) {
9247             hintRequested = FALSE;
9248             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9249                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9250                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9251                                     PosFlags(forwardMostMove),
9252                                     fromY, fromX, toY, toX, promoChar, buf1);
9253                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9254                 DisplayInformation(buf2);
9255             } else {
9256                 /* Hint move could not be parsed!? */
9257               snprintf(buf2, sizeof(buf2),
9258                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9259                         buf1, _(cps->which));
9260                 DisplayError(buf2, 0);
9261             }
9262         } else {
9263           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9264         }
9265         return;
9266     }
9267
9268     /*
9269      * Ignore other messages if game is not in progress
9270      */
9271     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9272         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9273
9274     /*
9275      * look for win, lose, draw, or draw offer
9276      */
9277     if (strncmp(message, "1-0", 3) == 0) {
9278         char *p, *q, *r = "";
9279         p = strchr(message, '{');
9280         if (p) {
9281             q = strchr(p, '}');
9282             if (q) {
9283                 *q = NULLCHAR;
9284                 r = p + 1;
9285             }
9286         }
9287         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9288         return;
9289     } else if (strncmp(message, "0-1", 3) == 0) {
9290         char *p, *q, *r = "";
9291         p = strchr(message, '{');
9292         if (p) {
9293             q = strchr(p, '}');
9294             if (q) {
9295                 *q = NULLCHAR;
9296                 r = p + 1;
9297             }
9298         }
9299         /* Kludge for Arasan 4.1 bug */
9300         if (strcmp(r, "Black resigns") == 0) {
9301             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9302             return;
9303         }
9304         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9305         return;
9306     } else if (strncmp(message, "1/2", 3) == 0) {
9307         char *p, *q, *r = "";
9308         p = strchr(message, '{');
9309         if (p) {
9310             q = strchr(p, '}');
9311             if (q) {
9312                 *q = NULLCHAR;
9313                 r = p + 1;
9314             }
9315         }
9316
9317         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9318         return;
9319
9320     } else if (strncmp(message, "White resign", 12) == 0) {
9321         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9322         return;
9323     } else if (strncmp(message, "Black resign", 12) == 0) {
9324         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9325         return;
9326     } else if (strncmp(message, "White matches", 13) == 0 ||
9327                strncmp(message, "Black matches", 13) == 0   ) {
9328         /* [HGM] ignore GNUShogi noises */
9329         return;
9330     } else if (strncmp(message, "White", 5) == 0 &&
9331                message[5] != '(' &&
9332                StrStr(message, "Black") == NULL) {
9333         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9334         return;
9335     } else if (strncmp(message, "Black", 5) == 0 &&
9336                message[5] != '(') {
9337         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9338         return;
9339     } else if (strcmp(message, "resign") == 0 ||
9340                strcmp(message, "computer resigns") == 0) {
9341         switch (gameMode) {
9342           case MachinePlaysBlack:
9343           case IcsPlayingBlack:
9344             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9345             break;
9346           case MachinePlaysWhite:
9347           case IcsPlayingWhite:
9348             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9349             break;
9350           case TwoMachinesPlay:
9351             if (cps->twoMachinesColor[0] == 'w')
9352               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9353             else
9354               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9355             break;
9356           default:
9357             /* can't happen */
9358             break;
9359         }
9360         return;
9361     } else if (strncmp(message, "opponent mates", 14) == 0) {
9362         switch (gameMode) {
9363           case MachinePlaysBlack:
9364           case IcsPlayingBlack:
9365             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9366             break;
9367           case MachinePlaysWhite:
9368           case IcsPlayingWhite:
9369             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9370             break;
9371           case TwoMachinesPlay:
9372             if (cps->twoMachinesColor[0] == 'w')
9373               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9374             else
9375               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9376             break;
9377           default:
9378             /* can't happen */
9379             break;
9380         }
9381         return;
9382     } else if (strncmp(message, "computer mates", 14) == 0) {
9383         switch (gameMode) {
9384           case MachinePlaysBlack:
9385           case IcsPlayingBlack:
9386             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9387             break;
9388           case MachinePlaysWhite:
9389           case IcsPlayingWhite:
9390             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9391             break;
9392           case TwoMachinesPlay:
9393             if (cps->twoMachinesColor[0] == 'w')
9394               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9395             else
9396               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9397             break;
9398           default:
9399             /* can't happen */
9400             break;
9401         }
9402         return;
9403     } else if (strncmp(message, "checkmate", 9) == 0) {
9404         if (WhiteOnMove(forwardMostMove)) {
9405             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9406         } else {
9407             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9408         }
9409         return;
9410     } else if (strstr(message, "Draw") != NULL ||
9411                strstr(message, "game is a draw") != NULL) {
9412         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9413         return;
9414     } else if (strstr(message, "offer") != NULL &&
9415                strstr(message, "draw") != NULL) {
9416 #if ZIPPY
9417         if (appData.zippyPlay && first.initDone) {
9418             /* Relay offer to ICS */
9419             SendToICS(ics_prefix);
9420             SendToICS("draw\n");
9421         }
9422 #endif
9423         cps->offeredDraw = 2; /* valid until this engine moves twice */
9424         if (gameMode == TwoMachinesPlay) {
9425             if (cps->other->offeredDraw) {
9426                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9427             /* [HGM] in two-machine mode we delay relaying draw offer      */
9428             /* until after we also have move, to see if it is really claim */
9429             }
9430         } else if (gameMode == MachinePlaysWhite ||
9431                    gameMode == MachinePlaysBlack) {
9432           if (userOfferedDraw) {
9433             DisplayInformation(_("Machine accepts your draw offer"));
9434             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9435           } else {
9436             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9437           }
9438         }
9439     }
9440
9441
9442     /*
9443      * Look for thinking output
9444      */
9445     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9446           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9447                                 ) {
9448         int plylev, mvleft, mvtot, curscore, time;
9449         char mvname[MOVE_LEN];
9450         u64 nodes; // [DM]
9451         char plyext;
9452         int ignore = FALSE;
9453         int prefixHint = FALSE;
9454         mvname[0] = NULLCHAR;
9455
9456         switch (gameMode) {
9457           case MachinePlaysBlack:
9458           case IcsPlayingBlack:
9459             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9460             break;
9461           case MachinePlaysWhite:
9462           case IcsPlayingWhite:
9463             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9464             break;
9465           case AnalyzeMode:
9466           case AnalyzeFile:
9467             break;
9468           case IcsObserving: /* [DM] icsEngineAnalyze */
9469             if (!appData.icsEngineAnalyze) ignore = TRUE;
9470             break;
9471           case TwoMachinesPlay:
9472             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9473                 ignore = TRUE;
9474             }
9475             break;
9476           default:
9477             ignore = TRUE;
9478             break;
9479         }
9480
9481         if (!ignore) {
9482             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9483             buf1[0] = NULLCHAR;
9484             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9485                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9486
9487                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9488                     nodes += u64Const(0x100000000);
9489
9490                 if (plyext != ' ' && plyext != '\t') {
9491                     time *= 100;
9492                 }
9493
9494                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9495                 if( cps->scoreIsAbsolute &&
9496                     ( gameMode == MachinePlaysBlack ||
9497                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9498                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9499                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9500                      !WhiteOnMove(currentMove)
9501                     ) )
9502                 {
9503                     curscore = -curscore;
9504                 }
9505
9506                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9507
9508                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9509                         char buf[MSG_SIZ];
9510                         FILE *f;
9511                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9512                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9513                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9514                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9515                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9516                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9517                                 fclose(f);
9518                         }
9519                         else
9520                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9521                           DisplayError(_("failed writing PV"), 0);
9522                 }
9523
9524                 tempStats.depth = plylev;
9525                 tempStats.nodes = nodes;
9526                 tempStats.time = time;
9527                 tempStats.score = curscore;
9528                 tempStats.got_only_move = 0;
9529
9530                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9531                         int ticklen;
9532
9533                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9534                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9535                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9536                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9537                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9538                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9539                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9540                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9541                 }
9542
9543                 /* Buffer overflow protection */
9544                 if (pv[0] != NULLCHAR) {
9545                     if (strlen(pv) >= sizeof(tempStats.movelist)
9546                         && appData.debugMode) {
9547                         fprintf(debugFP,
9548                                 "PV is too long; using the first %u bytes.\n",
9549                                 (unsigned) sizeof(tempStats.movelist) - 1);
9550                     }
9551
9552                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9553                 } else {
9554                     sprintf(tempStats.movelist, " no PV\n");
9555                 }
9556
9557                 if (tempStats.seen_stat) {
9558                     tempStats.ok_to_send = 1;
9559                 }
9560
9561                 if (strchr(tempStats.movelist, '(') != NULL) {
9562                     tempStats.line_is_book = 1;
9563                     tempStats.nr_moves = 0;
9564                     tempStats.moves_left = 0;
9565                 } else {
9566                     tempStats.line_is_book = 0;
9567                 }
9568
9569                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9570                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9571
9572                 SendProgramStatsToFrontend( cps, &tempStats );
9573
9574                 /*
9575                     [AS] Protect the thinkOutput buffer from overflow... this
9576                     is only useful if buf1 hasn't overflowed first!
9577                 */
9578                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9579                          plylev,
9580                          (gameMode == TwoMachinesPlay ?
9581                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9582                          ((double) curscore) / 100.0,
9583                          prefixHint ? lastHint : "",
9584                          prefixHint ? " " : "" );
9585
9586                 if( buf1[0] != NULLCHAR ) {
9587                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9588
9589                     if( strlen(pv) > max_len ) {
9590                         if( appData.debugMode) {
9591                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9592                         }
9593                         pv[max_len+1] = '\0';
9594                     }
9595
9596                     strcat( thinkOutput, pv);
9597                 }
9598
9599                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9600                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9601                     DisplayMove(currentMove - 1);
9602                 }
9603                 return;
9604
9605             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9606                 /* crafty (9.25+) says "(only move) <move>"
9607                  * if there is only 1 legal move
9608                  */
9609                 sscanf(p, "(only move) %s", buf1);
9610                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9611                 sprintf(programStats.movelist, "%s (only move)", buf1);
9612                 programStats.depth = 1;
9613                 programStats.nr_moves = 1;
9614                 programStats.moves_left = 1;
9615                 programStats.nodes = 1;
9616                 programStats.time = 1;
9617                 programStats.got_only_move = 1;
9618
9619                 /* Not really, but we also use this member to
9620                    mean "line isn't going to change" (Crafty
9621                    isn't searching, so stats won't change) */
9622                 programStats.line_is_book = 1;
9623
9624                 SendProgramStatsToFrontend( cps, &programStats );
9625
9626                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9627                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9628                     DisplayMove(currentMove - 1);
9629                 }
9630                 return;
9631             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9632                               &time, &nodes, &plylev, &mvleft,
9633                               &mvtot, mvname) >= 5) {
9634                 /* The stat01: line is from Crafty (9.29+) in response
9635                    to the "." command */
9636                 programStats.seen_stat = 1;
9637                 cps->maybeThinking = TRUE;
9638
9639                 if (programStats.got_only_move || !appData.periodicUpdates)
9640                   return;
9641
9642                 programStats.depth = plylev;
9643                 programStats.time = time;
9644                 programStats.nodes = nodes;
9645                 programStats.moves_left = mvleft;
9646                 programStats.nr_moves = mvtot;
9647                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9648                 programStats.ok_to_send = 1;
9649                 programStats.movelist[0] = '\0';
9650
9651                 SendProgramStatsToFrontend( cps, &programStats );
9652
9653                 return;
9654
9655             } else if (strncmp(message,"++",2) == 0) {
9656                 /* Crafty 9.29+ outputs this */
9657                 programStats.got_fail = 2;
9658                 return;
9659
9660             } else if (strncmp(message,"--",2) == 0) {
9661                 /* Crafty 9.29+ outputs this */
9662                 programStats.got_fail = 1;
9663                 return;
9664
9665             } else if (thinkOutput[0] != NULLCHAR &&
9666                        strncmp(message, "    ", 4) == 0) {
9667                 unsigned message_len;
9668
9669                 p = message;
9670                 while (*p && *p == ' ') p++;
9671
9672                 message_len = strlen( p );
9673
9674                 /* [AS] Avoid buffer overflow */
9675                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9676                     strcat(thinkOutput, " ");
9677                     strcat(thinkOutput, p);
9678                 }
9679
9680                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9681                     strcat(programStats.movelist, " ");
9682                     strcat(programStats.movelist, p);
9683                 }
9684
9685                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9686                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9687                     DisplayMove(currentMove - 1);
9688                 }
9689                 return;
9690             }
9691         }
9692         else {
9693             buf1[0] = NULLCHAR;
9694
9695             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9696                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9697             {
9698                 ChessProgramStats cpstats;
9699
9700                 if (plyext != ' ' && plyext != '\t') {
9701                     time *= 100;
9702                 }
9703
9704                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9705                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9706                     curscore = -curscore;
9707                 }
9708
9709                 cpstats.depth = plylev;
9710                 cpstats.nodes = nodes;
9711                 cpstats.time = time;
9712                 cpstats.score = curscore;
9713                 cpstats.got_only_move = 0;
9714                 cpstats.movelist[0] = '\0';
9715
9716                 if (buf1[0] != NULLCHAR) {
9717                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9718                 }
9719
9720                 cpstats.ok_to_send = 0;
9721                 cpstats.line_is_book = 0;
9722                 cpstats.nr_moves = 0;
9723                 cpstats.moves_left = 0;
9724
9725                 SendProgramStatsToFrontend( cps, &cpstats );
9726             }
9727         }
9728     }
9729 }
9730
9731
9732 /* Parse a game score from the character string "game", and
9733    record it as the history of the current game.  The game
9734    score is NOT assumed to start from the standard position.
9735    The display is not updated in any way.
9736    */
9737 void
9738 ParseGameHistory (char *game)
9739 {
9740     ChessMove moveType;
9741     int fromX, fromY, toX, toY, boardIndex;
9742     char promoChar;
9743     char *p, *q;
9744     char buf[MSG_SIZ];
9745
9746     if (appData.debugMode)
9747       fprintf(debugFP, "Parsing game history: %s\n", game);
9748
9749     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9750     gameInfo.site = StrSave(appData.icsHost);
9751     gameInfo.date = PGNDate();
9752     gameInfo.round = StrSave("-");
9753
9754     /* Parse out names of players */
9755     while (*game == ' ') game++;
9756     p = buf;
9757     while (*game != ' ') *p++ = *game++;
9758     *p = NULLCHAR;
9759     gameInfo.white = StrSave(buf);
9760     while (*game == ' ') game++;
9761     p = buf;
9762     while (*game != ' ' && *game != '\n') *p++ = *game++;
9763     *p = NULLCHAR;
9764     gameInfo.black = StrSave(buf);
9765
9766     /* Parse moves */
9767     boardIndex = blackPlaysFirst ? 1 : 0;
9768     yynewstr(game);
9769     for (;;) {
9770         yyboardindex = boardIndex;
9771         moveType = (ChessMove) Myylex();
9772         switch (moveType) {
9773           case IllegalMove:             /* maybe suicide chess, etc. */
9774   if (appData.debugMode) {
9775     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9776     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9777     setbuf(debugFP, NULL);
9778   }
9779           case WhitePromotion:
9780           case BlackPromotion:
9781           case WhiteNonPromotion:
9782           case BlackNonPromotion:
9783           case NormalMove:
9784           case FirstLeg:
9785           case WhiteCapturesEnPassant:
9786           case BlackCapturesEnPassant:
9787           case WhiteKingSideCastle:
9788           case WhiteQueenSideCastle:
9789           case BlackKingSideCastle:
9790           case BlackQueenSideCastle:
9791           case WhiteKingSideCastleWild:
9792           case WhiteQueenSideCastleWild:
9793           case BlackKingSideCastleWild:
9794           case BlackQueenSideCastleWild:
9795           /* PUSH Fabien */
9796           case WhiteHSideCastleFR:
9797           case WhiteASideCastleFR:
9798           case BlackHSideCastleFR:
9799           case BlackASideCastleFR:
9800           /* POP Fabien */
9801             fromX = currentMoveString[0] - AAA;
9802             fromY = currentMoveString[1] - ONE;
9803             toX = currentMoveString[2] - AAA;
9804             toY = currentMoveString[3] - ONE;
9805             promoChar = currentMoveString[4];
9806             break;
9807           case WhiteDrop:
9808           case BlackDrop:
9809             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9810             fromX = moveType == WhiteDrop ?
9811               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9812             (int) CharToPiece(ToLower(currentMoveString[0]));
9813             fromY = DROP_RANK;
9814             toX = currentMoveString[2] - AAA;
9815             toY = currentMoveString[3] - ONE;
9816             promoChar = NULLCHAR;
9817             break;
9818           case AmbiguousMove:
9819             /* bug? */
9820             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9821   if (appData.debugMode) {
9822     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9823     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9824     setbuf(debugFP, NULL);
9825   }
9826             DisplayError(buf, 0);
9827             return;
9828           case ImpossibleMove:
9829             /* bug? */
9830             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9831   if (appData.debugMode) {
9832     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9833     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9834     setbuf(debugFP, NULL);
9835   }
9836             DisplayError(buf, 0);
9837             return;
9838           case EndOfFile:
9839             if (boardIndex < backwardMostMove) {
9840                 /* Oops, gap.  How did that happen? */
9841                 DisplayError(_("Gap in move list"), 0);
9842                 return;
9843             }
9844             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9845             if (boardIndex > forwardMostMove) {
9846                 forwardMostMove = boardIndex;
9847             }
9848             return;
9849           case ElapsedTime:
9850             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9851                 strcat(parseList[boardIndex-1], " ");
9852                 strcat(parseList[boardIndex-1], yy_text);
9853             }
9854             continue;
9855           case Comment:
9856           case PGNTag:
9857           case NAG:
9858           default:
9859             /* ignore */
9860             continue;
9861           case WhiteWins:
9862           case BlackWins:
9863           case GameIsDrawn:
9864           case GameUnfinished:
9865             if (gameMode == IcsExamining) {
9866                 if (boardIndex < backwardMostMove) {
9867                     /* Oops, gap.  How did that happen? */
9868                     return;
9869                 }
9870                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9871                 return;
9872             }
9873             gameInfo.result = moveType;
9874             p = strchr(yy_text, '{');
9875             if (p == NULL) p = strchr(yy_text, '(');
9876             if (p == NULL) {
9877                 p = yy_text;
9878                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9879             } else {
9880                 q = strchr(p, *p == '{' ? '}' : ')');
9881                 if (q != NULL) *q = NULLCHAR;
9882                 p++;
9883             }
9884             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9885             gameInfo.resultDetails = StrSave(p);
9886             continue;
9887         }
9888         if (boardIndex >= forwardMostMove &&
9889             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9890             backwardMostMove = blackPlaysFirst ? 1 : 0;
9891             return;
9892         }
9893         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9894                                  fromY, fromX, toY, toX, promoChar,
9895                                  parseList[boardIndex]);
9896         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9897         /* currentMoveString is set as a side-effect of yylex */
9898         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9899         strcat(moveList[boardIndex], "\n");
9900         boardIndex++;
9901         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9902         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9903           case MT_NONE:
9904           case MT_STALEMATE:
9905           default:
9906             break;
9907           case MT_CHECK:
9908             if(!IS_SHOGI(gameInfo.variant))
9909                 strcat(parseList[boardIndex - 1], "+");
9910             break;
9911           case MT_CHECKMATE:
9912           case MT_STAINMATE:
9913             strcat(parseList[boardIndex - 1], "#");
9914             break;
9915         }
9916     }
9917 }
9918
9919
9920 /* Apply a move to the given board  */
9921 void
9922 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9923 {
9924   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9925   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9926
9927     /* [HGM] compute & store e.p. status and castling rights for new position */
9928     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9929
9930       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9931       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9932       board[EP_STATUS] = EP_NONE;
9933       board[EP_FILE] = board[EP_RANK] = 100;
9934
9935   if (fromY == DROP_RANK) {
9936         /* must be first */
9937         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9938             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9939             return;
9940         }
9941         piece = board[toY][toX] = (ChessSquare) fromX;
9942   } else {
9943 //      ChessSquare victim;
9944       int i;
9945
9946       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9947 //           victim = board[killY][killX],
9948            killed = board[killY][killX],
9949            board[killY][killX] = EmptySquare,
9950            board[EP_STATUS] = EP_CAPTURE;
9951
9952       if( board[toY][toX] != EmptySquare ) {
9953            board[EP_STATUS] = EP_CAPTURE;
9954            if( (fromX != toX || fromY != toY) && // not igui!
9955                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9956                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9957                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9958            }
9959       }
9960
9961       pawn = board[fromY][fromX];
9962       if( pawn == WhiteLance || pawn == BlackLance ) {
9963            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9964                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9965                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9966            }
9967       }
9968       if( pawn == WhitePawn ) {
9969            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9970                board[EP_STATUS] = EP_PAWN_MOVE;
9971            if( toY-fromY>=2) {
9972                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9973                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9974                         gameInfo.variant != VariantBerolina || toX < fromX)
9975                       board[EP_STATUS] = toX | berolina;
9976                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9977                         gameInfo.variant != VariantBerolina || toX > fromX)
9978                       board[EP_STATUS] = toX;
9979            }
9980       } else
9981       if( pawn == BlackPawn ) {
9982            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9983                board[EP_STATUS] = EP_PAWN_MOVE;
9984            if( toY-fromY<= -2) {
9985                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9986                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9987                         gameInfo.variant != VariantBerolina || toX < fromX)
9988                       board[EP_STATUS] = toX | berolina;
9989                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9990                         gameInfo.variant != VariantBerolina || toX > fromX)
9991                       board[EP_STATUS] = toX;
9992            }
9993        }
9994
9995        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9996        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9997        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9998        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9999
10000        for(i=0; i<nrCastlingRights; i++) {
10001            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10002               board[CASTLING][i] == toX   && castlingRank[i] == toY
10003              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10004        }
10005
10006        if(gameInfo.variant == VariantSChess) { // update virginity
10007            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10008            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10009            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10010            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10011        }
10012
10013      if (fromX == toX && fromY == toY) return;
10014
10015      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10016      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10017      if(gameInfo.variant == VariantKnightmate)
10018          king += (int) WhiteUnicorn - (int) WhiteKing;
10019
10020     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10021        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10022         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10023         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10024         board[EP_STATUS] = EP_NONE; // capture was fake!
10025     } else
10026     /* Code added by Tord: */
10027     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10028     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10029         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10030       board[EP_STATUS] = EP_NONE; // capture was fake!
10031       board[fromY][fromX] = EmptySquare;
10032       board[toY][toX] = EmptySquare;
10033       if((toX > fromX) != (piece == WhiteRook)) {
10034         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10035       } else {
10036         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10037       }
10038     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10039                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10040       board[EP_STATUS] = EP_NONE;
10041       board[fromY][fromX] = EmptySquare;
10042       board[toY][toX] = EmptySquare;
10043       if((toX > fromX) != (piece == BlackRook)) {
10044         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10045       } else {
10046         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10047       }
10048     /* End of code added by Tord */
10049
10050     } else if (board[fromY][fromX] == king
10051         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10052         && toY == fromY && toX > fromX+1) {
10053         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10054         board[fromY][toX-1] = board[fromY][rookX];
10055         board[fromY][rookX] = EmptySquare;
10056         board[fromY][fromX] = EmptySquare;
10057         board[toY][toX] = king;
10058     } else if (board[fromY][fromX] == king
10059         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10060                && toY == fromY && toX < fromX-1) {
10061         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10062         board[fromY][toX+1] = board[fromY][rookX];
10063         board[fromY][rookX] = EmptySquare;
10064         board[fromY][fromX] = EmptySquare;
10065         board[toY][toX] = king;
10066     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10067                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10068                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10069                ) {
10070         /* white pawn promotion */
10071         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10072         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10073             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10074         board[fromY][fromX] = EmptySquare;
10075     } else if ((fromY >= BOARD_HEIGHT>>1)
10076                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10077                && (toX != fromX)
10078                && gameInfo.variant != VariantXiangqi
10079                && gameInfo.variant != VariantBerolina
10080                && (pawn == WhitePawn)
10081                && (board[toY][toX] == EmptySquare)) {
10082         board[fromY][fromX] = EmptySquare;
10083         board[toY][toX] = piece;
10084         if(toY == epRank - 128 + 1)
10085             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10086         else
10087             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10088     } else if ((fromY == BOARD_HEIGHT-4)
10089                && (toX == fromX)
10090                && gameInfo.variant == VariantBerolina
10091                && (board[fromY][fromX] == WhitePawn)
10092                && (board[toY][toX] == EmptySquare)) {
10093         board[fromY][fromX] = EmptySquare;
10094         board[toY][toX] = WhitePawn;
10095         if(oldEP & EP_BEROLIN_A) {
10096                 captured = board[fromY][fromX-1];
10097                 board[fromY][fromX-1] = EmptySquare;
10098         }else{  captured = board[fromY][fromX+1];
10099                 board[fromY][fromX+1] = EmptySquare;
10100         }
10101     } else if (board[fromY][fromX] == king
10102         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10103                && toY == fromY && toX > fromX+1) {
10104         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10105         board[fromY][toX-1] = board[fromY][rookX];
10106         board[fromY][rookX] = EmptySquare;
10107         board[fromY][fromX] = EmptySquare;
10108         board[toY][toX] = king;
10109     } else if (board[fromY][fromX] == king
10110         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10111                && toY == fromY && toX < fromX-1) {
10112         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10113         board[fromY][toX+1] = board[fromY][rookX];
10114         board[fromY][rookX] = EmptySquare;
10115         board[fromY][fromX] = EmptySquare;
10116         board[toY][toX] = king;
10117     } else if (fromY == 7 && fromX == 3
10118                && board[fromY][fromX] == BlackKing
10119                && toY == 7 && toX == 5) {
10120         board[fromY][fromX] = EmptySquare;
10121         board[toY][toX] = BlackKing;
10122         board[fromY][7] = EmptySquare;
10123         board[toY][4] = BlackRook;
10124     } else if (fromY == 7 && fromX == 3
10125                && board[fromY][fromX] == BlackKing
10126                && toY == 7 && toX == 1) {
10127         board[fromY][fromX] = EmptySquare;
10128         board[toY][toX] = BlackKing;
10129         board[fromY][0] = EmptySquare;
10130         board[toY][2] = BlackRook;
10131     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10132                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10133                && toY < promoRank && promoChar
10134                ) {
10135         /* black pawn promotion */
10136         board[toY][toX] = CharToPiece(ToLower(promoChar));
10137         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10138             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10139         board[fromY][fromX] = EmptySquare;
10140     } else if ((fromY < BOARD_HEIGHT>>1)
10141                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10142                && (toX != fromX)
10143                && gameInfo.variant != VariantXiangqi
10144                && gameInfo.variant != VariantBerolina
10145                && (pawn == BlackPawn)
10146                && (board[toY][toX] == EmptySquare)) {
10147         board[fromY][fromX] = EmptySquare;
10148         board[toY][toX] = piece;
10149         if(toY == epRank - 128 - 1)
10150             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10151         else
10152             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10153     } else if ((fromY == 3)
10154                && (toX == fromX)
10155                && gameInfo.variant == VariantBerolina
10156                && (board[fromY][fromX] == BlackPawn)
10157                && (board[toY][toX] == EmptySquare)) {
10158         board[fromY][fromX] = EmptySquare;
10159         board[toY][toX] = BlackPawn;
10160         if(oldEP & EP_BEROLIN_A) {
10161                 captured = board[fromY][fromX-1];
10162                 board[fromY][fromX-1] = EmptySquare;
10163         }else{  captured = board[fromY][fromX+1];
10164                 board[fromY][fromX+1] = EmptySquare;
10165         }
10166     } else {
10167         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10168         board[fromY][fromX] = EmptySquare;
10169         board[toY][toX] = piece;
10170     }
10171   }
10172
10173     if (gameInfo.holdingsWidth != 0) {
10174
10175       /* !!A lot more code needs to be written to support holdings  */
10176       /* [HGM] OK, so I have written it. Holdings are stored in the */
10177       /* penultimate board files, so they are automaticlly stored   */
10178       /* in the game history.                                       */
10179       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10180                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10181         /* Delete from holdings, by decreasing count */
10182         /* and erasing image if necessary            */
10183         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10184         if(p < (int) BlackPawn) { /* white drop */
10185              p -= (int)WhitePawn;
10186                  p = PieceToNumber((ChessSquare)p);
10187              if(p >= gameInfo.holdingsSize) p = 0;
10188              if(--board[p][BOARD_WIDTH-2] <= 0)
10189                   board[p][BOARD_WIDTH-1] = EmptySquare;
10190              if((int)board[p][BOARD_WIDTH-2] < 0)
10191                         board[p][BOARD_WIDTH-2] = 0;
10192         } else {                  /* black drop */
10193              p -= (int)BlackPawn;
10194                  p = PieceToNumber((ChessSquare)p);
10195              if(p >= gameInfo.holdingsSize) p = 0;
10196              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10197                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10198              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10199                         board[BOARD_HEIGHT-1-p][1] = 0;
10200         }
10201       }
10202       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10203           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10204         /* [HGM] holdings: Add to holdings, if holdings exist */
10205         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10206                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10207                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10208         }
10209         p = (int) captured;
10210         if (p >= (int) BlackPawn) {
10211           p -= (int)BlackPawn;
10212           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10213                   /* Restore shogi-promoted piece to its original  first */
10214                   captured = (ChessSquare) (DEMOTED captured);
10215                   p = DEMOTED p;
10216           }
10217           p = PieceToNumber((ChessSquare)p);
10218           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10219           board[p][BOARD_WIDTH-2]++;
10220           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10221         } else {
10222           p -= (int)WhitePawn;
10223           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10224                   captured = (ChessSquare) (DEMOTED captured);
10225                   p = DEMOTED p;
10226           }
10227           p = PieceToNumber((ChessSquare)p);
10228           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10229           board[BOARD_HEIGHT-1-p][1]++;
10230           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10231         }
10232       }
10233     } else if (gameInfo.variant == VariantAtomic) {
10234       if (captured != EmptySquare) {
10235         int y, x;
10236         for (y = toY-1; y <= toY+1; y++) {
10237           for (x = toX-1; x <= toX+1; x++) {
10238             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10239                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10240               board[y][x] = EmptySquare;
10241             }
10242           }
10243         }
10244         board[toY][toX] = EmptySquare;
10245       }
10246     }
10247
10248     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10249         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10250     } else
10251     if(promoChar == '+') {
10252         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10253         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10254         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10255           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10256     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10257         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10258         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10259            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10260         board[toY][toX] = newPiece;
10261     }
10262     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10263                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10264         // [HGM] superchess: take promotion piece out of holdings
10265         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10266         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10267             if(!--board[k][BOARD_WIDTH-2])
10268                 board[k][BOARD_WIDTH-1] = EmptySquare;
10269         } else {
10270             if(!--board[BOARD_HEIGHT-1-k][1])
10271                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10272         }
10273     }
10274 }
10275
10276 /* Updates forwardMostMove */
10277 void
10278 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10279 {
10280     int x = toX, y = toY;
10281     char *s = parseList[forwardMostMove];
10282     ChessSquare p = boards[forwardMostMove][toY][toX];
10283 //    forwardMostMove++; // [HGM] bare: moved downstream
10284
10285     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10286     (void) CoordsToAlgebraic(boards[forwardMostMove],
10287                              PosFlags(forwardMostMove),
10288                              fromY, fromX, y, x, promoChar,
10289                              s);
10290     if(killX >= 0 && killY >= 0)
10291         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10292
10293     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10294         int timeLeft; static int lastLoadFlag=0; int king, piece;
10295         piece = boards[forwardMostMove][fromY][fromX];
10296         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10297         if(gameInfo.variant == VariantKnightmate)
10298             king += (int) WhiteUnicorn - (int) WhiteKing;
10299         if(forwardMostMove == 0) {
10300             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10301                 fprintf(serverMoves, "%s;", UserName());
10302             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10303                 fprintf(serverMoves, "%s;", second.tidy);
10304             fprintf(serverMoves, "%s;", first.tidy);
10305             if(gameMode == MachinePlaysWhite)
10306                 fprintf(serverMoves, "%s;", UserName());
10307             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10308                 fprintf(serverMoves, "%s;", second.tidy);
10309         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10310         lastLoadFlag = loadFlag;
10311         // print base move
10312         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10313         // print castling suffix
10314         if( toY == fromY && piece == king ) {
10315             if(toX-fromX > 1)
10316                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10317             if(fromX-toX >1)
10318                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10319         }
10320         // e.p. suffix
10321         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10322              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10323              boards[forwardMostMove][toY][toX] == EmptySquare
10324              && fromX != toX && fromY != toY)
10325                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10326         // promotion suffix
10327         if(promoChar != NULLCHAR) {
10328             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10329                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10330                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10331             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10332         }
10333         if(!loadFlag) {
10334                 char buf[MOVE_LEN*2], *p; int len;
10335             fprintf(serverMoves, "/%d/%d",
10336                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10337             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10338             else                      timeLeft = blackTimeRemaining/1000;
10339             fprintf(serverMoves, "/%d", timeLeft);
10340                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10341                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10342                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10343                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10344             fprintf(serverMoves, "/%s", buf);
10345         }
10346         fflush(serverMoves);
10347     }
10348
10349     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10350         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10351       return;
10352     }
10353     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10354     if (commentList[forwardMostMove+1] != NULL) {
10355         free(commentList[forwardMostMove+1]);
10356         commentList[forwardMostMove+1] = NULL;
10357     }
10358     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10359     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10360     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10361     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10362     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10363     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10364     adjustedClock = FALSE;
10365     gameInfo.result = GameUnfinished;
10366     if (gameInfo.resultDetails != NULL) {
10367         free(gameInfo.resultDetails);
10368         gameInfo.resultDetails = NULL;
10369     }
10370     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10371                               moveList[forwardMostMove - 1]);
10372     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10373       case MT_NONE:
10374       case MT_STALEMATE:
10375       default:
10376         break;
10377       case MT_CHECK:
10378         if(!IS_SHOGI(gameInfo.variant))
10379             strcat(parseList[forwardMostMove - 1], "+");
10380         break;
10381       case MT_CHECKMATE:
10382       case MT_STAINMATE:
10383         strcat(parseList[forwardMostMove - 1], "#");
10384         break;
10385     }
10386 }
10387
10388 /* Updates currentMove if not pausing */
10389 void
10390 ShowMove (int fromX, int fromY, int toX, int toY)
10391 {
10392     int instant = (gameMode == PlayFromGameFile) ?
10393         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10394     if(appData.noGUI) return;
10395     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10396         if (!instant) {
10397             if (forwardMostMove == currentMove + 1) {
10398                 AnimateMove(boards[forwardMostMove - 1],
10399                             fromX, fromY, toX, toY);
10400             }
10401         }
10402         currentMove = forwardMostMove;
10403     }
10404
10405     killX = killY = -1; // [HGM] lion: used up
10406
10407     if (instant) return;
10408
10409     DisplayMove(currentMove - 1);
10410     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10411             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10412                 SetHighlights(fromX, fromY, toX, toY);
10413             }
10414     }
10415     DrawPosition(FALSE, boards[currentMove]);
10416     DisplayBothClocks();
10417     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10418 }
10419
10420 void
10421 SendEgtPath (ChessProgramState *cps)
10422 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10423         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10424
10425         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10426
10427         while(*p) {
10428             char c, *q = name+1, *r, *s;
10429
10430             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10431             while(*p && *p != ',') *q++ = *p++;
10432             *q++ = ':'; *q = 0;
10433             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10434                 strcmp(name, ",nalimov:") == 0 ) {
10435                 // take nalimov path from the menu-changeable option first, if it is defined
10436               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10437                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10438             } else
10439             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10440                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10441                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10442                 s = r = StrStr(s, ":") + 1; // beginning of path info
10443                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10444                 c = *r; *r = 0;             // temporarily null-terminate path info
10445                     *--q = 0;               // strip of trailig ':' from name
10446                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10447                 *r = c;
10448                 SendToProgram(buf,cps);     // send egtbpath command for this format
10449             }
10450             if(*p == ',') p++; // read away comma to position for next format name
10451         }
10452 }
10453
10454 static int
10455 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10456 {
10457       int width = 8, height = 8, holdings = 0;             // most common sizes
10458       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10459       // correct the deviations default for each variant
10460       if( v == VariantXiangqi ) width = 9,  height = 10;
10461       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10462       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10463       if( v == VariantCapablanca || v == VariantCapaRandom ||
10464           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10465                                 width = 10;
10466       if( v == VariantCourier ) width = 12;
10467       if( v == VariantSuper )                            holdings = 8;
10468       if( v == VariantGreat )   width = 10,              holdings = 8;
10469       if( v == VariantSChess )                           holdings = 7;
10470       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10471       if( v == VariantChuChess) width = 10, height = 10;
10472       if( v == VariantChu )     width = 12, height = 12;
10473       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10474              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10475              holdingsSize >= 0 && holdingsSize != holdings;
10476 }
10477
10478 char variantError[MSG_SIZ];
10479
10480 char *
10481 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10482 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10483       char *p, *variant = VariantName(v);
10484       static char b[MSG_SIZ];
10485       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10486            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10487                                                holdingsSize, variant); // cook up sized variant name
10488            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10489            if(StrStr(list, b) == NULL) {
10490                // specific sized variant not known, check if general sizing allowed
10491                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10492                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10493                             boardWidth, boardHeight, holdingsSize, engine);
10494                    return NULL;
10495                }
10496                /* [HGM] here we really should compare with the maximum supported board size */
10497            }
10498       } else snprintf(b, MSG_SIZ,"%s", variant);
10499       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10500       p = StrStr(list, b);
10501       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10502       if(p == NULL) {
10503           // occurs not at all in list, or only as sub-string
10504           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10505           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10506               int l = strlen(variantError);
10507               char *q;
10508               while(p != list && p[-1] != ',') p--;
10509               q = strchr(p, ',');
10510               if(q) *q = NULLCHAR;
10511               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10512               if(q) *q= ',';
10513           }
10514           return NULL;
10515       }
10516       return b;
10517 }
10518
10519 void
10520 InitChessProgram (ChessProgramState *cps, int setup)
10521 /* setup needed to setup FRC opening position */
10522 {
10523     char buf[MSG_SIZ], *b;
10524     if (appData.noChessProgram) return;
10525     hintRequested = FALSE;
10526     bookRequested = FALSE;
10527
10528     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10529     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10530     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10531     if(cps->memSize) { /* [HGM] memory */
10532       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10533         SendToProgram(buf, cps);
10534     }
10535     SendEgtPath(cps); /* [HGM] EGT */
10536     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10537       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10538         SendToProgram(buf, cps);
10539     }
10540
10541     setboardSpoiledMachineBlack = FALSE;
10542     SendToProgram(cps->initString, cps);
10543     if (gameInfo.variant != VariantNormal &&
10544         gameInfo.variant != VariantLoadable
10545         /* [HGM] also send variant if board size non-standard */
10546         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10547
10548       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10549                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10550       if (b == NULL) {
10551         VariantClass v;
10552         char c, *q = cps->variants, *p = strchr(q, ',');
10553         if(p) *p = NULLCHAR;
10554         v = StringToVariant(q);
10555         DisplayError(variantError, 0);
10556         if(v != VariantUnknown && cps == &first) {
10557             int w, h, s;
10558             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10559                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10560             ASSIGN(appData.variant, q);
10561             Reset(TRUE, FALSE);
10562         }
10563         if(p) *p = ',';
10564         return;
10565       }
10566
10567       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10568       SendToProgram(buf, cps);
10569     }
10570     currentlyInitializedVariant = gameInfo.variant;
10571
10572     /* [HGM] send opening position in FRC to first engine */
10573     if(setup) {
10574           SendToProgram("force\n", cps);
10575           SendBoard(cps, 0);
10576           /* engine is now in force mode! Set flag to wake it up after first move. */
10577           setboardSpoiledMachineBlack = 1;
10578     }
10579
10580     if (cps->sendICS) {
10581       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10582       SendToProgram(buf, cps);
10583     }
10584     cps->maybeThinking = FALSE;
10585     cps->offeredDraw = 0;
10586     if (!appData.icsActive) {
10587         SendTimeControl(cps, movesPerSession, timeControl,
10588                         timeIncrement, appData.searchDepth,
10589                         searchTime);
10590     }
10591     if (appData.showThinking
10592         // [HGM] thinking: four options require thinking output to be sent
10593         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10594                                 ) {
10595         SendToProgram("post\n", cps);
10596     }
10597     SendToProgram("hard\n", cps);
10598     if (!appData.ponderNextMove) {
10599         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10600            it without being sure what state we are in first.  "hard"
10601            is not a toggle, so that one is OK.
10602          */
10603         SendToProgram("easy\n", cps);
10604     }
10605     if (cps->usePing) {
10606       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10607       SendToProgram(buf, cps);
10608     }
10609     cps->initDone = TRUE;
10610     ClearEngineOutputPane(cps == &second);
10611 }
10612
10613
10614 void
10615 ResendOptions (ChessProgramState *cps)
10616 { // send the stored value of the options
10617   int i;
10618   char buf[MSG_SIZ];
10619   Option *opt = cps->option;
10620   for(i=0; i<cps->nrOptions; i++, opt++) {
10621       switch(opt->type) {
10622         case Spin:
10623         case Slider:
10624         case CheckBox:
10625             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10626           break;
10627         case ComboBox:
10628           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10629           break;
10630         default:
10631             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10632           break;
10633         case Button:
10634         case SaveButton:
10635           continue;
10636       }
10637       SendToProgram(buf, cps);
10638   }
10639 }
10640
10641 void
10642 StartChessProgram (ChessProgramState *cps)
10643 {
10644     char buf[MSG_SIZ];
10645     int err;
10646
10647     if (appData.noChessProgram) return;
10648     cps->initDone = FALSE;
10649
10650     if (strcmp(cps->host, "localhost") == 0) {
10651         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10652     } else if (*appData.remoteShell == NULLCHAR) {
10653         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10654     } else {
10655         if (*appData.remoteUser == NULLCHAR) {
10656           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10657                     cps->program);
10658         } else {
10659           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10660                     cps->host, appData.remoteUser, cps->program);
10661         }
10662         err = StartChildProcess(buf, "", &cps->pr);
10663     }
10664
10665     if (err != 0) {
10666       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10667         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10668         if(cps != &first) return;
10669         appData.noChessProgram = TRUE;
10670         ThawUI();
10671         SetNCPMode();
10672 //      DisplayFatalError(buf, err, 1);
10673 //      cps->pr = NoProc;
10674 //      cps->isr = NULL;
10675         return;
10676     }
10677
10678     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10679     if (cps->protocolVersion > 1) {
10680       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10681       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10682         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10683         cps->comboCnt = 0;  //                and values of combo boxes
10684       }
10685       SendToProgram(buf, cps);
10686       if(cps->reload) ResendOptions(cps);
10687     } else {
10688       SendToProgram("xboard\n", cps);
10689     }
10690 }
10691
10692 void
10693 TwoMachinesEventIfReady P((void))
10694 {
10695   static int curMess = 0;
10696   if (first.lastPing != first.lastPong) {
10697     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10698     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10699     return;
10700   }
10701   if (second.lastPing != second.lastPong) {
10702     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10703     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10704     return;
10705   }
10706   DisplayMessage("", ""); curMess = 0;
10707   TwoMachinesEvent();
10708 }
10709
10710 char *
10711 MakeName (char *template)
10712 {
10713     time_t clock;
10714     struct tm *tm;
10715     static char buf[MSG_SIZ];
10716     char *p = buf;
10717     int i;
10718
10719     clock = time((time_t *)NULL);
10720     tm = localtime(&clock);
10721
10722     while(*p++ = *template++) if(p[-1] == '%') {
10723         switch(*template++) {
10724           case 0:   *p = 0; return buf;
10725           case 'Y': i = tm->tm_year+1900; break;
10726           case 'y': i = tm->tm_year-100; break;
10727           case 'M': i = tm->tm_mon+1; break;
10728           case 'd': i = tm->tm_mday; break;
10729           case 'h': i = tm->tm_hour; break;
10730           case 'm': i = tm->tm_min; break;
10731           case 's': i = tm->tm_sec; break;
10732           default:  i = 0;
10733         }
10734         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10735     }
10736     return buf;
10737 }
10738
10739 int
10740 CountPlayers (char *p)
10741 {
10742     int n = 0;
10743     while(p = strchr(p, '\n')) p++, n++; // count participants
10744     return n;
10745 }
10746
10747 FILE *
10748 WriteTourneyFile (char *results, FILE *f)
10749 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10750     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10751     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10752         // create a file with tournament description
10753         fprintf(f, "-participants {%s}\n", appData.participants);
10754         fprintf(f, "-seedBase %d\n", appData.seedBase);
10755         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10756         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10757         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10758         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10759         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10760         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10761         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10762         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10763         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10764         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10765         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10766         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10767         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10768         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10769         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10770         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10771         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10772         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10773         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10774         fprintf(f, "-smpCores %d\n", appData.smpCores);
10775         if(searchTime > 0)
10776                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10777         else {
10778                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10779                 fprintf(f, "-tc %s\n", appData.timeControl);
10780                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10781         }
10782         fprintf(f, "-results \"%s\"\n", results);
10783     }
10784     return f;
10785 }
10786
10787 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10788
10789 void
10790 Substitute (char *participants, int expunge)
10791 {
10792     int i, changed, changes=0, nPlayers=0;
10793     char *p, *q, *r, buf[MSG_SIZ];
10794     if(participants == NULL) return;
10795     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10796     r = p = participants; q = appData.participants;
10797     while(*p && *p == *q) {
10798         if(*p == '\n') r = p+1, nPlayers++;
10799         p++; q++;
10800     }
10801     if(*p) { // difference
10802         while(*p && *p++ != '\n');
10803         while(*q && *q++ != '\n');
10804       changed = nPlayers;
10805         changes = 1 + (strcmp(p, q) != 0);
10806     }
10807     if(changes == 1) { // a single engine mnemonic was changed
10808         q = r; while(*q) nPlayers += (*q++ == '\n');
10809         p = buf; while(*r && (*p = *r++) != '\n') p++;
10810         *p = NULLCHAR;
10811         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10812         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10813         if(mnemonic[i]) { // The substitute is valid
10814             FILE *f;
10815             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10816                 flock(fileno(f), LOCK_EX);
10817                 ParseArgsFromFile(f);
10818                 fseek(f, 0, SEEK_SET);
10819                 FREE(appData.participants); appData.participants = participants;
10820                 if(expunge) { // erase results of replaced engine
10821                     int len = strlen(appData.results), w, b, dummy;
10822                     for(i=0; i<len; i++) {
10823                         Pairing(i, nPlayers, &w, &b, &dummy);
10824                         if((w == changed || b == changed) && appData.results[i] == '*') {
10825                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10826                             fclose(f);
10827                             return;
10828                         }
10829                     }
10830                     for(i=0; i<len; i++) {
10831                         Pairing(i, nPlayers, &w, &b, &dummy);
10832                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10833                     }
10834                 }
10835                 WriteTourneyFile(appData.results, f);
10836                 fclose(f); // release lock
10837                 return;
10838             }
10839         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10840     }
10841     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10842     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10843     free(participants);
10844     return;
10845 }
10846
10847 int
10848 CheckPlayers (char *participants)
10849 {
10850         int i;
10851         char buf[MSG_SIZ], *p;
10852         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10853         while(p = strchr(participants, '\n')) {
10854             *p = NULLCHAR;
10855             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10856             if(!mnemonic[i]) {
10857                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10858                 *p = '\n';
10859                 DisplayError(buf, 0);
10860                 return 1;
10861             }
10862             *p = '\n';
10863             participants = p + 1;
10864         }
10865         return 0;
10866 }
10867
10868 int
10869 CreateTourney (char *name)
10870 {
10871         FILE *f;
10872         if(matchMode && strcmp(name, appData.tourneyFile)) {
10873              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10874         }
10875         if(name[0] == NULLCHAR) {
10876             if(appData.participants[0])
10877                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10878             return 0;
10879         }
10880         f = fopen(name, "r");
10881         if(f) { // file exists
10882             ASSIGN(appData.tourneyFile, name);
10883             ParseArgsFromFile(f); // parse it
10884         } else {
10885             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10886             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10887                 DisplayError(_("Not enough participants"), 0);
10888                 return 0;
10889             }
10890             if(CheckPlayers(appData.participants)) return 0;
10891             ASSIGN(appData.tourneyFile, name);
10892             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10893             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10894         }
10895         fclose(f);
10896         appData.noChessProgram = FALSE;
10897         appData.clockMode = TRUE;
10898         SetGNUMode();
10899         return 1;
10900 }
10901
10902 int
10903 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10904 {
10905     char buf[MSG_SIZ], *p, *q;
10906     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10907     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10908     skip = !all && group[0]; // if group requested, we start in skip mode
10909     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10910         p = names; q = buf; header = 0;
10911         while(*p && *p != '\n') *q++ = *p++;
10912         *q = 0;
10913         if(*p == '\n') p++;
10914         if(buf[0] == '#') {
10915             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10916             depth++; // we must be entering a new group
10917             if(all) continue; // suppress printing group headers when complete list requested
10918             header = 1;
10919             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10920         }
10921         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10922         if(engineList[i]) free(engineList[i]);
10923         engineList[i] = strdup(buf);
10924         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10925         if(engineMnemonic[i]) free(engineMnemonic[i]);
10926         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10927             strcat(buf, " (");
10928             sscanf(q + 8, "%s", buf + strlen(buf));
10929             strcat(buf, ")");
10930         }
10931         engineMnemonic[i] = strdup(buf);
10932         i++;
10933     }
10934     engineList[i] = engineMnemonic[i] = NULL;
10935     return i;
10936 }
10937
10938 // following implemented as macro to avoid type limitations
10939 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10940
10941 void
10942 SwapEngines (int n)
10943 {   // swap settings for first engine and other engine (so far only some selected options)
10944     int h;
10945     char *p;
10946     if(n == 0) return;
10947     SWAP(directory, p)
10948     SWAP(chessProgram, p)
10949     SWAP(isUCI, h)
10950     SWAP(hasOwnBookUCI, h)
10951     SWAP(protocolVersion, h)
10952     SWAP(reuse, h)
10953     SWAP(scoreIsAbsolute, h)
10954     SWAP(timeOdds, h)
10955     SWAP(logo, p)
10956     SWAP(pgnName, p)
10957     SWAP(pvSAN, h)
10958     SWAP(engOptions, p)
10959     SWAP(engInitString, p)
10960     SWAP(computerString, p)
10961     SWAP(features, p)
10962     SWAP(fenOverride, p)
10963     SWAP(NPS, h)
10964     SWAP(accumulateTC, h)
10965     SWAP(drawDepth, h)
10966     SWAP(host, p)
10967     SWAP(pseudo, h)
10968 }
10969
10970 int
10971 GetEngineLine (char *s, int n)
10972 {
10973     int i;
10974     char buf[MSG_SIZ];
10975     extern char *icsNames;
10976     if(!s || !*s) return 0;
10977     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10978     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10979     if(!mnemonic[i]) return 0;
10980     if(n == 11) return 1; // just testing if there was a match
10981     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10982     if(n == 1) SwapEngines(n);
10983     ParseArgsFromString(buf);
10984     if(n == 1) SwapEngines(n);
10985     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10986         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10987         ParseArgsFromString(buf);
10988     }
10989     return 1;
10990 }
10991
10992 int
10993 SetPlayer (int player, char *p)
10994 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10995     int i;
10996     char buf[MSG_SIZ], *engineName;
10997     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10998     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10999     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11000     if(mnemonic[i]) {
11001         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11002         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11003         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11004         ParseArgsFromString(buf);
11005     } else { // no engine with this nickname is installed!
11006         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11007         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11008         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11009         ModeHighlight();
11010         DisplayError(buf, 0);
11011         return 0;
11012     }
11013     free(engineName);
11014     return i;
11015 }
11016
11017 char *recentEngines;
11018
11019 void
11020 RecentEngineEvent (int nr)
11021 {
11022     int n;
11023 //    SwapEngines(1); // bump first to second
11024 //    ReplaceEngine(&second, 1); // and load it there
11025     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11026     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11027     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11028         ReplaceEngine(&first, 0);
11029         FloatToFront(&appData.recentEngineList, command[n]);
11030     }
11031 }
11032
11033 int
11034 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11035 {   // determine players from game number
11036     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11037
11038     if(appData.tourneyType == 0) {
11039         roundsPerCycle = (nPlayers - 1) | 1;
11040         pairingsPerRound = nPlayers / 2;
11041     } else if(appData.tourneyType > 0) {
11042         roundsPerCycle = nPlayers - appData.tourneyType;
11043         pairingsPerRound = appData.tourneyType;
11044     }
11045     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11046     gamesPerCycle = gamesPerRound * roundsPerCycle;
11047     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11048     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11049     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11050     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11051     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11052     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11053
11054     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11055     if(appData.roundSync) *syncInterval = gamesPerRound;
11056
11057     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11058
11059     if(appData.tourneyType == 0) {
11060         if(curPairing == (nPlayers-1)/2 ) {
11061             *whitePlayer = curRound;
11062             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11063         } else {
11064             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11065             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11066             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11067             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11068         }
11069     } else if(appData.tourneyType > 1) {
11070         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11071         *whitePlayer = curRound + appData.tourneyType;
11072     } else if(appData.tourneyType > 0) {
11073         *whitePlayer = curPairing;
11074         *blackPlayer = curRound + appData.tourneyType;
11075     }
11076
11077     // take care of white/black alternation per round.
11078     // For cycles and games this is already taken care of by default, derived from matchGame!
11079     return curRound & 1;
11080 }
11081
11082 int
11083 NextTourneyGame (int nr, int *swapColors)
11084 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11085     char *p, *q;
11086     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11087     FILE *tf;
11088     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11089     tf = fopen(appData.tourneyFile, "r");
11090     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11091     ParseArgsFromFile(tf); fclose(tf);
11092     InitTimeControls(); // TC might be altered from tourney file
11093
11094     nPlayers = CountPlayers(appData.participants); // count participants
11095     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11096     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11097
11098     if(syncInterval) {
11099         p = q = appData.results;
11100         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11101         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11102             DisplayMessage(_("Waiting for other game(s)"),"");
11103             waitingForGame = TRUE;
11104             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11105             return 0;
11106         }
11107         waitingForGame = FALSE;
11108     }
11109
11110     if(appData.tourneyType < 0) {
11111         if(nr>=0 && !pairingReceived) {
11112             char buf[1<<16];
11113             if(pairing.pr == NoProc) {
11114                 if(!appData.pairingEngine[0]) {
11115                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11116                     return 0;
11117                 }
11118                 StartChessProgram(&pairing); // starts the pairing engine
11119             }
11120             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11121             SendToProgram(buf, &pairing);
11122             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11123             SendToProgram(buf, &pairing);
11124             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11125         }
11126         pairingReceived = 0;                              // ... so we continue here
11127         *swapColors = 0;
11128         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11129         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11130         matchGame = 1; roundNr = nr / syncInterval + 1;
11131     }
11132
11133     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11134
11135     // redefine engines, engine dir, etc.
11136     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11137     if(first.pr == NoProc) {
11138       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11139       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11140     }
11141     if(second.pr == NoProc) {
11142       SwapEngines(1);
11143       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11144       SwapEngines(1);         // and make that valid for second engine by swapping
11145       InitEngine(&second, 1);
11146     }
11147     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11148     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11149     return OK;
11150 }
11151
11152 void
11153 NextMatchGame ()
11154 {   // performs game initialization that does not invoke engines, and then tries to start the game
11155     int res, firstWhite, swapColors = 0;
11156     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11157     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
11158         char buf[MSG_SIZ];
11159         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11160         if(strcmp(buf, currentDebugFile)) { // name has changed
11161             FILE *f = fopen(buf, "w");
11162             if(f) { // if opening the new file failed, just keep using the old one
11163                 ASSIGN(currentDebugFile, buf);
11164                 fclose(debugFP);
11165                 debugFP = f;
11166             }
11167             if(appData.serverFileName) {
11168                 if(serverFP) fclose(serverFP);
11169                 serverFP = fopen(appData.serverFileName, "w");
11170                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11171                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11172             }
11173         }
11174     }
11175     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11176     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11177     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11178     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11179     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11180     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11181     Reset(FALSE, first.pr != NoProc);
11182     res = LoadGameOrPosition(matchGame); // setup game
11183     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11184     if(!res) return; // abort when bad game/pos file
11185     TwoMachinesEvent();
11186 }
11187
11188 void
11189 UserAdjudicationEvent (int result)
11190 {
11191     ChessMove gameResult = GameIsDrawn;
11192
11193     if( result > 0 ) {
11194         gameResult = WhiteWins;
11195     }
11196     else if( result < 0 ) {
11197         gameResult = BlackWins;
11198     }
11199
11200     if( gameMode == TwoMachinesPlay ) {
11201         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11202     }
11203 }
11204
11205
11206 // [HGM] save: calculate checksum of game to make games easily identifiable
11207 int
11208 StringCheckSum (char *s)
11209 {
11210         int i = 0;
11211         if(s==NULL) return 0;
11212         while(*s) i = i*259 + *s++;
11213         return i;
11214 }
11215
11216 int
11217 GameCheckSum ()
11218 {
11219         int i, sum=0;
11220         for(i=backwardMostMove; i<forwardMostMove; i++) {
11221                 sum += pvInfoList[i].depth;
11222                 sum += StringCheckSum(parseList[i]);
11223                 sum += StringCheckSum(commentList[i]);
11224                 sum *= 261;
11225         }
11226         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11227         return sum + StringCheckSum(commentList[i]);
11228 } // end of save patch
11229
11230 void
11231 GameEnds (ChessMove result, char *resultDetails, int whosays)
11232 {
11233     GameMode nextGameMode;
11234     int isIcsGame;
11235     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11236
11237     if(endingGame) return; /* [HGM] crash: forbid recursion */
11238     endingGame = 1;
11239     if(twoBoards) { // [HGM] dual: switch back to one board
11240         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11241         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11242     }
11243     if (appData.debugMode) {
11244       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11245               result, resultDetails ? resultDetails : "(null)", whosays);
11246     }
11247
11248     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11249
11250     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11251
11252     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11253         /* If we are playing on ICS, the server decides when the
11254            game is over, but the engine can offer to draw, claim
11255            a draw, or resign.
11256          */
11257 #if ZIPPY
11258         if (appData.zippyPlay && first.initDone) {
11259             if (result == GameIsDrawn) {
11260                 /* In case draw still needs to be claimed */
11261                 SendToICS(ics_prefix);
11262                 SendToICS("draw\n");
11263             } else if (StrCaseStr(resultDetails, "resign")) {
11264                 SendToICS(ics_prefix);
11265                 SendToICS("resign\n");
11266             }
11267         }
11268 #endif
11269         endingGame = 0; /* [HGM] crash */
11270         return;
11271     }
11272
11273     /* If we're loading the game from a file, stop */
11274     if (whosays == GE_FILE) {
11275       (void) StopLoadGameTimer();
11276       gameFileFP = NULL;
11277     }
11278
11279     /* Cancel draw offers */
11280     first.offeredDraw = second.offeredDraw = 0;
11281
11282     /* If this is an ICS game, only ICS can really say it's done;
11283        if not, anyone can. */
11284     isIcsGame = (gameMode == IcsPlayingWhite ||
11285                  gameMode == IcsPlayingBlack ||
11286                  gameMode == IcsObserving    ||
11287                  gameMode == IcsExamining);
11288
11289     if (!isIcsGame || whosays == GE_ICS) {
11290         /* OK -- not an ICS game, or ICS said it was done */
11291         StopClocks();
11292         if (!isIcsGame && !appData.noChessProgram)
11293           SetUserThinkingEnables();
11294
11295         /* [HGM] if a machine claims the game end we verify this claim */
11296         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11297             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11298                 char claimer;
11299                 ChessMove trueResult = (ChessMove) -1;
11300
11301                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11302                                             first.twoMachinesColor[0] :
11303                                             second.twoMachinesColor[0] ;
11304
11305                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11306                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11307                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11308                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11309                 } else
11310                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11311                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11312                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11313                 } else
11314                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11315                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11316                 }
11317
11318                 // now verify win claims, but not in drop games, as we don't understand those yet
11319                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11320                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11321                     (result == WhiteWins && claimer == 'w' ||
11322                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11323                       if (appData.debugMode) {
11324                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11325                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11326                       }
11327                       if(result != trueResult) {
11328                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11329                               result = claimer == 'w' ? BlackWins : WhiteWins;
11330                               resultDetails = buf;
11331                       }
11332                 } else
11333                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11334                     && (forwardMostMove <= backwardMostMove ||
11335                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11336                         (claimer=='b')==(forwardMostMove&1))
11337                                                                                   ) {
11338                       /* [HGM] verify: draws that were not flagged are false claims */
11339                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11340                       result = claimer == 'w' ? BlackWins : WhiteWins;
11341                       resultDetails = buf;
11342                 }
11343                 /* (Claiming a loss is accepted no questions asked!) */
11344             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11345                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11346                 result = GameUnfinished;
11347                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11348             }
11349             /* [HGM] bare: don't allow bare King to win */
11350             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11351                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11352                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11353                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11354                && result != GameIsDrawn)
11355             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11356                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11357                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11358                         if(p >= 0 && p <= (int)WhiteKing) k++;
11359                 }
11360                 if (appData.debugMode) {
11361                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11362                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11363                 }
11364                 if(k <= 1) {
11365                         result = GameIsDrawn;
11366                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11367                         resultDetails = buf;
11368                 }
11369             }
11370         }
11371
11372
11373         if(serverMoves != NULL && !loadFlag) { char c = '=';
11374             if(result==WhiteWins) c = '+';
11375             if(result==BlackWins) c = '-';
11376             if(resultDetails != NULL)
11377                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11378         }
11379         if (resultDetails != NULL) {
11380             gameInfo.result = result;
11381             gameInfo.resultDetails = StrSave(resultDetails);
11382
11383             /* display last move only if game was not loaded from file */
11384             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11385                 DisplayMove(currentMove - 1);
11386
11387             if (forwardMostMove != 0) {
11388                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11389                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11390                                                                 ) {
11391                     if (*appData.saveGameFile != NULLCHAR) {
11392                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11393                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11394                         else
11395                         SaveGameToFile(appData.saveGameFile, TRUE);
11396                     } else if (appData.autoSaveGames) {
11397                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11398                     }
11399                     if (*appData.savePositionFile != NULLCHAR) {
11400                         SavePositionToFile(appData.savePositionFile);
11401                     }
11402                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11403                 }
11404             }
11405
11406             /* Tell program how game ended in case it is learning */
11407             /* [HGM] Moved this to after saving the PGN, just in case */
11408             /* engine died and we got here through time loss. In that */
11409             /* case we will get a fatal error writing the pipe, which */
11410             /* would otherwise lose us the PGN.                       */
11411             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11412             /* output during GameEnds should never be fatal anymore   */
11413             if (gameMode == MachinePlaysWhite ||
11414                 gameMode == MachinePlaysBlack ||
11415                 gameMode == TwoMachinesPlay ||
11416                 gameMode == IcsPlayingWhite ||
11417                 gameMode == IcsPlayingBlack ||
11418                 gameMode == BeginningOfGame) {
11419                 char buf[MSG_SIZ];
11420                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11421                         resultDetails);
11422                 if (first.pr != NoProc) {
11423                     SendToProgram(buf, &first);
11424                 }
11425                 if (second.pr != NoProc &&
11426                     gameMode == TwoMachinesPlay) {
11427                     SendToProgram(buf, &second);
11428                 }
11429             }
11430         }
11431
11432         if (appData.icsActive) {
11433             if (appData.quietPlay &&
11434                 (gameMode == IcsPlayingWhite ||
11435                  gameMode == IcsPlayingBlack)) {
11436                 SendToICS(ics_prefix);
11437                 SendToICS("set shout 1\n");
11438             }
11439             nextGameMode = IcsIdle;
11440             ics_user_moved = FALSE;
11441             /* clean up premove.  It's ugly when the game has ended and the
11442              * premove highlights are still on the board.
11443              */
11444             if (gotPremove) {
11445               gotPremove = FALSE;
11446               ClearPremoveHighlights();
11447               DrawPosition(FALSE, boards[currentMove]);
11448             }
11449             if (whosays == GE_ICS) {
11450                 switch (result) {
11451                 case WhiteWins:
11452                     if (gameMode == IcsPlayingWhite)
11453                         PlayIcsWinSound();
11454                     else if(gameMode == IcsPlayingBlack)
11455                         PlayIcsLossSound();
11456                     break;
11457                 case BlackWins:
11458                     if (gameMode == IcsPlayingBlack)
11459                         PlayIcsWinSound();
11460                     else if(gameMode == IcsPlayingWhite)
11461                         PlayIcsLossSound();
11462                     break;
11463                 case GameIsDrawn:
11464                     PlayIcsDrawSound();
11465                     break;
11466                 default:
11467                     PlayIcsUnfinishedSound();
11468                 }
11469             }
11470             if(appData.quitNext) { ExitEvent(0); return; }
11471         } else if (gameMode == EditGame ||
11472                    gameMode == PlayFromGameFile ||
11473                    gameMode == AnalyzeMode ||
11474                    gameMode == AnalyzeFile) {
11475             nextGameMode = gameMode;
11476         } else {
11477             nextGameMode = EndOfGame;
11478         }
11479         pausing = FALSE;
11480         ModeHighlight();
11481     } else {
11482         nextGameMode = gameMode;
11483     }
11484
11485     if (appData.noChessProgram) {
11486         gameMode = nextGameMode;
11487         ModeHighlight();
11488         endingGame = 0; /* [HGM] crash */
11489         return;
11490     }
11491
11492     if (first.reuse) {
11493         /* Put first chess program into idle state */
11494         if (first.pr != NoProc &&
11495             (gameMode == MachinePlaysWhite ||
11496              gameMode == MachinePlaysBlack ||
11497              gameMode == TwoMachinesPlay ||
11498              gameMode == IcsPlayingWhite ||
11499              gameMode == IcsPlayingBlack ||
11500              gameMode == BeginningOfGame)) {
11501             SendToProgram("force\n", &first);
11502             if (first.usePing) {
11503               char buf[MSG_SIZ];
11504               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11505               SendToProgram(buf, &first);
11506             }
11507         }
11508     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11509         /* Kill off first chess program */
11510         if (first.isr != NULL)
11511           RemoveInputSource(first.isr);
11512         first.isr = NULL;
11513
11514         if (first.pr != NoProc) {
11515             ExitAnalyzeMode();
11516             DoSleep( appData.delayBeforeQuit );
11517             SendToProgram("quit\n", &first);
11518             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11519             first.reload = TRUE;
11520         }
11521         first.pr = NoProc;
11522     }
11523     if (second.reuse) {
11524         /* Put second chess program into idle state */
11525         if (second.pr != NoProc &&
11526             gameMode == TwoMachinesPlay) {
11527             SendToProgram("force\n", &second);
11528             if (second.usePing) {
11529               char buf[MSG_SIZ];
11530               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11531               SendToProgram(buf, &second);
11532             }
11533         }
11534     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11535         /* Kill off second chess program */
11536         if (second.isr != NULL)
11537           RemoveInputSource(second.isr);
11538         second.isr = NULL;
11539
11540         if (second.pr != NoProc) {
11541             DoSleep( appData.delayBeforeQuit );
11542             SendToProgram("quit\n", &second);
11543             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11544             second.reload = TRUE;
11545         }
11546         second.pr = NoProc;
11547     }
11548
11549     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11550         char resChar = '=';
11551         switch (result) {
11552         case WhiteWins:
11553           resChar = '+';
11554           if (first.twoMachinesColor[0] == 'w') {
11555             first.matchWins++;
11556           } else {
11557             second.matchWins++;
11558           }
11559           break;
11560         case BlackWins:
11561           resChar = '-';
11562           if (first.twoMachinesColor[0] == 'b') {
11563             first.matchWins++;
11564           } else {
11565             second.matchWins++;
11566           }
11567           break;
11568         case GameUnfinished:
11569           resChar = ' ';
11570         default:
11571           break;
11572         }
11573
11574         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11575         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11576             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11577             ReserveGame(nextGame, resChar); // sets nextGame
11578             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11579             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11580         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11581
11582         if (nextGame <= appData.matchGames && !abortMatch) {
11583             gameMode = nextGameMode;
11584             matchGame = nextGame; // this will be overruled in tourney mode!
11585             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11586             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11587             endingGame = 0; /* [HGM] crash */
11588             return;
11589         } else {
11590             gameMode = nextGameMode;
11591             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11592                      first.tidy, second.tidy,
11593                      first.matchWins, second.matchWins,
11594                      appData.matchGames - (first.matchWins + second.matchWins));
11595             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11596             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11597             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11598             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11599                 first.twoMachinesColor = "black\n";
11600                 second.twoMachinesColor = "white\n";
11601             } else {
11602                 first.twoMachinesColor = "white\n";
11603                 second.twoMachinesColor = "black\n";
11604             }
11605         }
11606     }
11607     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11608         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11609       ExitAnalyzeMode();
11610     gameMode = nextGameMode;
11611     ModeHighlight();
11612     endingGame = 0;  /* [HGM] crash */
11613     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11614         if(matchMode == TRUE) { // match through command line: exit with or without popup
11615             if(ranking) {
11616                 ToNrEvent(forwardMostMove);
11617                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11618                 else ExitEvent(0);
11619             } else DisplayFatalError(buf, 0, 0);
11620         } else { // match through menu; just stop, with or without popup
11621             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11622             ModeHighlight();
11623             if(ranking){
11624                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11625             } else DisplayNote(buf);
11626       }
11627       if(ranking) free(ranking);
11628     }
11629 }
11630
11631 /* Assumes program was just initialized (initString sent).
11632    Leaves program in force mode. */
11633 void
11634 FeedMovesToProgram (ChessProgramState *cps, int upto)
11635 {
11636     int i;
11637
11638     if (appData.debugMode)
11639       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11640               startedFromSetupPosition ? "position and " : "",
11641               backwardMostMove, upto, cps->which);
11642     if(currentlyInitializedVariant != gameInfo.variant) {
11643       char buf[MSG_SIZ];
11644         // [HGM] variantswitch: make engine aware of new variant
11645         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11646                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11647                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11648         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11649         SendToProgram(buf, cps);
11650         currentlyInitializedVariant = gameInfo.variant;
11651     }
11652     SendToProgram("force\n", cps);
11653     if (startedFromSetupPosition) {
11654         SendBoard(cps, backwardMostMove);
11655     if (appData.debugMode) {
11656         fprintf(debugFP, "feedMoves\n");
11657     }
11658     }
11659     for (i = backwardMostMove; i < upto; i++) {
11660         SendMoveToProgram(i, cps);
11661     }
11662 }
11663
11664
11665 int
11666 ResurrectChessProgram ()
11667 {
11668      /* The chess program may have exited.
11669         If so, restart it and feed it all the moves made so far. */
11670     static int doInit = 0;
11671
11672     if (appData.noChessProgram) return 1;
11673
11674     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11675         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11676         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11677         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11678     } else {
11679         if (first.pr != NoProc) return 1;
11680         StartChessProgram(&first);
11681     }
11682     InitChessProgram(&first, FALSE);
11683     FeedMovesToProgram(&first, currentMove);
11684
11685     if (!first.sendTime) {
11686         /* can't tell gnuchess what its clock should read,
11687            so we bow to its notion. */
11688         ResetClocks();
11689         timeRemaining[0][currentMove] = whiteTimeRemaining;
11690         timeRemaining[1][currentMove] = blackTimeRemaining;
11691     }
11692
11693     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11694                 appData.icsEngineAnalyze) && first.analysisSupport) {
11695       SendToProgram("analyze\n", &first);
11696       first.analyzing = TRUE;
11697     }
11698     return 1;
11699 }
11700
11701 /*
11702  * Button procedures
11703  */
11704 void
11705 Reset (int redraw, int init)
11706 {
11707     int i;
11708
11709     if (appData.debugMode) {
11710         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11711                 redraw, init, gameMode);
11712     }
11713     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11714     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11715     CleanupTail(); // [HGM] vari: delete any stored variations
11716     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11717     pausing = pauseExamInvalid = FALSE;
11718     startedFromSetupPosition = blackPlaysFirst = FALSE;
11719     firstMove = TRUE;
11720     whiteFlag = blackFlag = FALSE;
11721     userOfferedDraw = FALSE;
11722     hintRequested = bookRequested = FALSE;
11723     first.maybeThinking = FALSE;
11724     second.maybeThinking = FALSE;
11725     first.bookSuspend = FALSE; // [HGM] book
11726     second.bookSuspend = FALSE;
11727     thinkOutput[0] = NULLCHAR;
11728     lastHint[0] = NULLCHAR;
11729     ClearGameInfo(&gameInfo);
11730     gameInfo.variant = StringToVariant(appData.variant);
11731     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11732     ics_user_moved = ics_clock_paused = FALSE;
11733     ics_getting_history = H_FALSE;
11734     ics_gamenum = -1;
11735     white_holding[0] = black_holding[0] = NULLCHAR;
11736     ClearProgramStats();
11737     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11738
11739     ResetFrontEnd();
11740     ClearHighlights();
11741     flipView = appData.flipView;
11742     ClearPremoveHighlights();
11743     gotPremove = FALSE;
11744     alarmSounded = FALSE;
11745     killX = killY = -1; // [HGM] lion
11746
11747     GameEnds(EndOfFile, NULL, GE_PLAYER);
11748     if(appData.serverMovesName != NULL) {
11749         /* [HGM] prepare to make moves file for broadcasting */
11750         clock_t t = clock();
11751         if(serverMoves != NULL) fclose(serverMoves);
11752         serverMoves = fopen(appData.serverMovesName, "r");
11753         if(serverMoves != NULL) {
11754             fclose(serverMoves);
11755             /* delay 15 sec before overwriting, so all clients can see end */
11756             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11757         }
11758         serverMoves = fopen(appData.serverMovesName, "w");
11759     }
11760
11761     ExitAnalyzeMode();
11762     gameMode = BeginningOfGame;
11763     ModeHighlight();
11764     if(appData.icsActive) gameInfo.variant = VariantNormal;
11765     currentMove = forwardMostMove = backwardMostMove = 0;
11766     MarkTargetSquares(1);
11767     InitPosition(redraw);
11768     for (i = 0; i < MAX_MOVES; i++) {
11769         if (commentList[i] != NULL) {
11770             free(commentList[i]);
11771             commentList[i] = NULL;
11772         }
11773     }
11774     ResetClocks();
11775     timeRemaining[0][0] = whiteTimeRemaining;
11776     timeRemaining[1][0] = blackTimeRemaining;
11777
11778     if (first.pr == NoProc) {
11779         StartChessProgram(&first);
11780     }
11781     if (init) {
11782             InitChessProgram(&first, startedFromSetupPosition);
11783     }
11784     DisplayTitle("");
11785     DisplayMessage("", "");
11786     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11787     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11788     ClearMap();        // [HGM] exclude: invalidate map
11789 }
11790
11791 void
11792 AutoPlayGameLoop ()
11793 {
11794     for (;;) {
11795         if (!AutoPlayOneMove())
11796           return;
11797         if (matchMode || appData.timeDelay == 0)
11798           continue;
11799         if (appData.timeDelay < 0)
11800           return;
11801         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11802         break;
11803     }
11804 }
11805
11806 void
11807 AnalyzeNextGame()
11808 {
11809     ReloadGame(1); // next game
11810 }
11811
11812 int
11813 AutoPlayOneMove ()
11814 {
11815     int fromX, fromY, toX, toY;
11816
11817     if (appData.debugMode) {
11818       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11819     }
11820
11821     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11822       return FALSE;
11823
11824     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11825       pvInfoList[currentMove].depth = programStats.depth;
11826       pvInfoList[currentMove].score = programStats.score;
11827       pvInfoList[currentMove].time  = 0;
11828       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11829       else { // append analysis of final position as comment
11830         char buf[MSG_SIZ];
11831         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11832         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11833       }
11834       programStats.depth = 0;
11835     }
11836
11837     if (currentMove >= forwardMostMove) {
11838       if(gameMode == AnalyzeFile) {
11839           if(appData.loadGameIndex == -1) {
11840             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11841           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11842           } else {
11843           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11844         }
11845       }
11846 //      gameMode = EndOfGame;
11847 //      ModeHighlight();
11848
11849       /* [AS] Clear current move marker at the end of a game */
11850       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11851
11852       return FALSE;
11853     }
11854
11855     toX = moveList[currentMove][2] - AAA;
11856     toY = moveList[currentMove][3] - ONE;
11857
11858     if (moveList[currentMove][1] == '@') {
11859         if (appData.highlightLastMove) {
11860             SetHighlights(-1, -1, toX, toY);
11861         }
11862     } else {
11863         int viaX = moveList[currentMove][5] - AAA;
11864         int viaY = moveList[currentMove][6] - ONE;
11865         fromX = moveList[currentMove][0] - AAA;
11866         fromY = moveList[currentMove][1] - ONE;
11867
11868         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11869
11870         if(moveList[currentMove][4] == ';') { // multi-leg
11871             ChessSquare piece = boards[currentMove][viaY][viaX];
11872             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11873             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11874             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11875             boards[currentMove][viaY][viaX] = piece;
11876         } else
11877         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11878
11879         if (appData.highlightLastMove) {
11880             SetHighlights(fromX, fromY, toX, toY);
11881         }
11882     }
11883     DisplayMove(currentMove);
11884     SendMoveToProgram(currentMove++, &first);
11885     DisplayBothClocks();
11886     DrawPosition(FALSE, boards[currentMove]);
11887     // [HGM] PV info: always display, routine tests if empty
11888     DisplayComment(currentMove - 1, commentList[currentMove]);
11889     return TRUE;
11890 }
11891
11892
11893 int
11894 LoadGameOneMove (ChessMove readAhead)
11895 {
11896     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11897     char promoChar = NULLCHAR;
11898     ChessMove moveType;
11899     char move[MSG_SIZ];
11900     char *p, *q;
11901
11902     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11903         gameMode != AnalyzeMode && gameMode != Training) {
11904         gameFileFP = NULL;
11905         return FALSE;
11906     }
11907
11908     yyboardindex = forwardMostMove;
11909     if (readAhead != EndOfFile) {
11910       moveType = readAhead;
11911     } else {
11912       if (gameFileFP == NULL)
11913           return FALSE;
11914       moveType = (ChessMove) Myylex();
11915     }
11916
11917     done = FALSE;
11918     switch (moveType) {
11919       case Comment:
11920         if (appData.debugMode)
11921           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11922         p = yy_text;
11923
11924         /* append the comment but don't display it */
11925         AppendComment(currentMove, p, FALSE);
11926         return TRUE;
11927
11928       case WhiteCapturesEnPassant:
11929       case BlackCapturesEnPassant:
11930       case WhitePromotion:
11931       case BlackPromotion:
11932       case WhiteNonPromotion:
11933       case BlackNonPromotion:
11934       case NormalMove:
11935       case FirstLeg:
11936       case WhiteKingSideCastle:
11937       case WhiteQueenSideCastle:
11938       case BlackKingSideCastle:
11939       case BlackQueenSideCastle:
11940       case WhiteKingSideCastleWild:
11941       case WhiteQueenSideCastleWild:
11942       case BlackKingSideCastleWild:
11943       case BlackQueenSideCastleWild:
11944       /* PUSH Fabien */
11945       case WhiteHSideCastleFR:
11946       case WhiteASideCastleFR:
11947       case BlackHSideCastleFR:
11948       case BlackASideCastleFR:
11949       /* POP Fabien */
11950         if (appData.debugMode)
11951           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11952         fromX = currentMoveString[0] - AAA;
11953         fromY = currentMoveString[1] - ONE;
11954         toX = currentMoveString[2] - AAA;
11955         toY = currentMoveString[3] - ONE;
11956         promoChar = currentMoveString[4];
11957         if(promoChar == ';') promoChar = NULLCHAR;
11958         break;
11959
11960       case WhiteDrop:
11961       case BlackDrop:
11962         if (appData.debugMode)
11963           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11964         fromX = moveType == WhiteDrop ?
11965           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11966         (int) CharToPiece(ToLower(currentMoveString[0]));
11967         fromY = DROP_RANK;
11968         toX = currentMoveString[2] - AAA;
11969         toY = currentMoveString[3] - ONE;
11970         break;
11971
11972       case WhiteWins:
11973       case BlackWins:
11974       case GameIsDrawn:
11975       case GameUnfinished:
11976         if (appData.debugMode)
11977           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11978         p = strchr(yy_text, '{');
11979         if (p == NULL) p = strchr(yy_text, '(');
11980         if (p == NULL) {
11981             p = yy_text;
11982             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11983         } else {
11984             q = strchr(p, *p == '{' ? '}' : ')');
11985             if (q != NULL) *q = NULLCHAR;
11986             p++;
11987         }
11988         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11989         GameEnds(moveType, p, GE_FILE);
11990         done = TRUE;
11991         if (cmailMsgLoaded) {
11992             ClearHighlights();
11993             flipView = WhiteOnMove(currentMove);
11994             if (moveType == GameUnfinished) flipView = !flipView;
11995             if (appData.debugMode)
11996               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11997         }
11998         break;
11999
12000       case EndOfFile:
12001         if (appData.debugMode)
12002           fprintf(debugFP, "Parser hit end of file\n");
12003         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12004           case MT_NONE:
12005           case MT_CHECK:
12006             break;
12007           case MT_CHECKMATE:
12008           case MT_STAINMATE:
12009             if (WhiteOnMove(currentMove)) {
12010                 GameEnds(BlackWins, "Black mates", GE_FILE);
12011             } else {
12012                 GameEnds(WhiteWins, "White mates", GE_FILE);
12013             }
12014             break;
12015           case MT_STALEMATE:
12016             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12017             break;
12018         }
12019         done = TRUE;
12020         break;
12021
12022       case MoveNumberOne:
12023         if (lastLoadGameStart == GNUChessGame) {
12024             /* GNUChessGames have numbers, but they aren't move numbers */
12025             if (appData.debugMode)
12026               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12027                       yy_text, (int) moveType);
12028             return LoadGameOneMove(EndOfFile); /* tail recursion */
12029         }
12030         /* else fall thru */
12031
12032       case XBoardGame:
12033       case GNUChessGame:
12034       case PGNTag:
12035         /* Reached start of next game in file */
12036         if (appData.debugMode)
12037           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12038         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12039           case MT_NONE:
12040           case MT_CHECK:
12041             break;
12042           case MT_CHECKMATE:
12043           case MT_STAINMATE:
12044             if (WhiteOnMove(currentMove)) {
12045                 GameEnds(BlackWins, "Black mates", GE_FILE);
12046             } else {
12047                 GameEnds(WhiteWins, "White mates", GE_FILE);
12048             }
12049             break;
12050           case MT_STALEMATE:
12051             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12052             break;
12053         }
12054         done = TRUE;
12055         break;
12056
12057       case PositionDiagram:     /* should not happen; ignore */
12058       case ElapsedTime:         /* ignore */
12059       case NAG:                 /* ignore */
12060         if (appData.debugMode)
12061           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12062                   yy_text, (int) moveType);
12063         return LoadGameOneMove(EndOfFile); /* tail recursion */
12064
12065       case IllegalMove:
12066         if (appData.testLegality) {
12067             if (appData.debugMode)
12068               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12069             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12070                     (forwardMostMove / 2) + 1,
12071                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12072             DisplayError(move, 0);
12073             done = TRUE;
12074         } else {
12075             if (appData.debugMode)
12076               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12077                       yy_text, currentMoveString);
12078             if(currentMoveString[1] == '@') {
12079                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12080                 fromY = DROP_RANK;
12081             } else {
12082                 fromX = currentMoveString[0] - AAA;
12083                 fromY = currentMoveString[1] - ONE;
12084             }
12085             toX = currentMoveString[2] - AAA;
12086             toY = currentMoveString[3] - ONE;
12087             promoChar = currentMoveString[4];
12088         }
12089         break;
12090
12091       case AmbiguousMove:
12092         if (appData.debugMode)
12093           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12094         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12095                 (forwardMostMove / 2) + 1,
12096                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12097         DisplayError(move, 0);
12098         done = TRUE;
12099         break;
12100
12101       default:
12102       case ImpossibleMove:
12103         if (appData.debugMode)
12104           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12105         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12106                 (forwardMostMove / 2) + 1,
12107                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12108         DisplayError(move, 0);
12109         done = TRUE;
12110         break;
12111     }
12112
12113     if (done) {
12114         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12115             DrawPosition(FALSE, boards[currentMove]);
12116             DisplayBothClocks();
12117             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12118               DisplayComment(currentMove - 1, commentList[currentMove]);
12119         }
12120         (void) StopLoadGameTimer();
12121         gameFileFP = NULL;
12122         cmailOldMove = forwardMostMove;
12123         return FALSE;
12124     } else {
12125         /* currentMoveString is set as a side-effect of yylex */
12126
12127         thinkOutput[0] = NULLCHAR;
12128         MakeMove(fromX, fromY, toX, toY, promoChar);
12129         killX = killY = -1; // [HGM] lion: used up
12130         currentMove = forwardMostMove;
12131         return TRUE;
12132     }
12133 }
12134
12135 /* Load the nth game from the given file */
12136 int
12137 LoadGameFromFile (char *filename, int n, char *title, int useList)
12138 {
12139     FILE *f;
12140     char buf[MSG_SIZ];
12141
12142     if (strcmp(filename, "-") == 0) {
12143         f = stdin;
12144         title = "stdin";
12145     } else {
12146         f = fopen(filename, "rb");
12147         if (f == NULL) {
12148           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12149             DisplayError(buf, errno);
12150             return FALSE;
12151         }
12152     }
12153     if (fseek(f, 0, 0) == -1) {
12154         /* f is not seekable; probably a pipe */
12155         useList = FALSE;
12156     }
12157     if (useList && n == 0) {
12158         int error = GameListBuild(f);
12159         if (error) {
12160             DisplayError(_("Cannot build game list"), error);
12161         } else if (!ListEmpty(&gameList) &&
12162                    ((ListGame *) gameList.tailPred)->number > 1) {
12163             GameListPopUp(f, title);
12164             return TRUE;
12165         }
12166         GameListDestroy();
12167         n = 1;
12168     }
12169     if (n == 0) n = 1;
12170     return LoadGame(f, n, title, FALSE);
12171 }
12172
12173
12174 void
12175 MakeRegisteredMove ()
12176 {
12177     int fromX, fromY, toX, toY;
12178     char promoChar;
12179     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12180         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12181           case CMAIL_MOVE:
12182           case CMAIL_DRAW:
12183             if (appData.debugMode)
12184               fprintf(debugFP, "Restoring %s for game %d\n",
12185                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12186
12187             thinkOutput[0] = NULLCHAR;
12188             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12189             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12190             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12191             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12192             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12193             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12194             MakeMove(fromX, fromY, toX, toY, promoChar);
12195             ShowMove(fromX, fromY, toX, toY);
12196
12197             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12198               case MT_NONE:
12199               case MT_CHECK:
12200                 break;
12201
12202               case MT_CHECKMATE:
12203               case MT_STAINMATE:
12204                 if (WhiteOnMove(currentMove)) {
12205                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12206                 } else {
12207                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12208                 }
12209                 break;
12210
12211               case MT_STALEMATE:
12212                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12213                 break;
12214             }
12215
12216             break;
12217
12218           case CMAIL_RESIGN:
12219             if (WhiteOnMove(currentMove)) {
12220                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12221             } else {
12222                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12223             }
12224             break;
12225
12226           case CMAIL_ACCEPT:
12227             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12228             break;
12229
12230           default:
12231             break;
12232         }
12233     }
12234
12235     return;
12236 }
12237
12238 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12239 int
12240 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12241 {
12242     int retVal;
12243
12244     if (gameNumber > nCmailGames) {
12245         DisplayError(_("No more games in this message"), 0);
12246         return FALSE;
12247     }
12248     if (f == lastLoadGameFP) {
12249         int offset = gameNumber - lastLoadGameNumber;
12250         if (offset == 0) {
12251             cmailMsg[0] = NULLCHAR;
12252             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12253                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12254                 nCmailMovesRegistered--;
12255             }
12256             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12257             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12258                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12259             }
12260         } else {
12261             if (! RegisterMove()) return FALSE;
12262         }
12263     }
12264
12265     retVal = LoadGame(f, gameNumber, title, useList);
12266
12267     /* Make move registered during previous look at this game, if any */
12268     MakeRegisteredMove();
12269
12270     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12271         commentList[currentMove]
12272           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12273         DisplayComment(currentMove - 1, commentList[currentMove]);
12274     }
12275
12276     return retVal;
12277 }
12278
12279 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12280 int
12281 ReloadGame (int offset)
12282 {
12283     int gameNumber = lastLoadGameNumber + offset;
12284     if (lastLoadGameFP == NULL) {
12285         DisplayError(_("No game has been loaded yet"), 0);
12286         return FALSE;
12287     }
12288     if (gameNumber <= 0) {
12289         DisplayError(_("Can't back up any further"), 0);
12290         return FALSE;
12291     }
12292     if (cmailMsgLoaded) {
12293         return CmailLoadGame(lastLoadGameFP, gameNumber,
12294                              lastLoadGameTitle, lastLoadGameUseList);
12295     } else {
12296         return LoadGame(lastLoadGameFP, gameNumber,
12297                         lastLoadGameTitle, lastLoadGameUseList);
12298     }
12299 }
12300
12301 int keys[EmptySquare+1];
12302
12303 int
12304 PositionMatches (Board b1, Board b2)
12305 {
12306     int r, f, sum=0;
12307     switch(appData.searchMode) {
12308         case 1: return CompareWithRights(b1, b2);
12309         case 2:
12310             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12312             }
12313             return TRUE;
12314         case 3:
12315             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12316               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12317                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12318             }
12319             return sum==0;
12320         case 4:
12321             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12322                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12323             }
12324             return sum==0;
12325     }
12326     return TRUE;
12327 }
12328
12329 #define Q_PROMO  4
12330 #define Q_EP     3
12331 #define Q_BCASTL 2
12332 #define Q_WCASTL 1
12333
12334 int pieceList[256], quickBoard[256];
12335 ChessSquare pieceType[256] = { EmptySquare };
12336 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12337 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12338 int soughtTotal, turn;
12339 Boolean epOK, flipSearch;
12340
12341 typedef struct {
12342     unsigned char piece, to;
12343 } Move;
12344
12345 #define DSIZE (250000)
12346
12347 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12348 Move *moveDatabase = initialSpace;
12349 unsigned int movePtr, dataSize = DSIZE;
12350
12351 int
12352 MakePieceList (Board board, int *counts)
12353 {
12354     int r, f, n=Q_PROMO, total=0;
12355     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12356     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12357         int sq = f + (r<<4);
12358         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12359             quickBoard[sq] = ++n;
12360             pieceList[n] = sq;
12361             pieceType[n] = board[r][f];
12362             counts[board[r][f]]++;
12363             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12364             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12365             total++;
12366         }
12367     }
12368     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12369     return total;
12370 }
12371
12372 void
12373 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12374 {
12375     int sq = fromX + (fromY<<4);
12376     int piece = quickBoard[sq], rook;
12377     quickBoard[sq] = 0;
12378     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12379     if(piece == pieceList[1] && fromY == toY) {
12380       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12381         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12382         moveDatabase[movePtr++].piece = Q_WCASTL;
12383         quickBoard[sq] = piece;
12384         piece = quickBoard[from]; quickBoard[from] = 0;
12385         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12386       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12387         quickBoard[sq] = 0; // remove Rook
12388         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12389         moveDatabase[movePtr++].piece = Q_WCASTL;
12390         quickBoard[sq] = pieceList[1]; // put King
12391         piece = rook;
12392         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12393       }
12394     } else
12395     if(piece == pieceList[2] && fromY == toY) {
12396       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12397         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12398         moveDatabase[movePtr++].piece = Q_BCASTL;
12399         quickBoard[sq] = piece;
12400         piece = quickBoard[from]; quickBoard[from] = 0;
12401         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12402       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12403         quickBoard[sq] = 0; // remove Rook
12404         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12405         moveDatabase[movePtr++].piece = Q_BCASTL;
12406         quickBoard[sq] = pieceList[2]; // put King
12407         piece = rook;
12408         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12409       }
12410     } else
12411     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12412         quickBoard[(fromY<<4)+toX] = 0;
12413         moveDatabase[movePtr].piece = Q_EP;
12414         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12415         moveDatabase[movePtr].to = sq;
12416     } else
12417     if(promoPiece != pieceType[piece]) {
12418         moveDatabase[movePtr++].piece = Q_PROMO;
12419         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12420     }
12421     moveDatabase[movePtr].piece = piece;
12422     quickBoard[sq] = piece;
12423     movePtr++;
12424 }
12425
12426 int
12427 PackGame (Board board)
12428 {
12429     Move *newSpace = NULL;
12430     moveDatabase[movePtr].piece = 0; // terminate previous game
12431     if(movePtr > dataSize) {
12432         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12433         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12434         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12435         if(newSpace) {
12436             int i;
12437             Move *p = moveDatabase, *q = newSpace;
12438             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12439             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12440             moveDatabase = newSpace;
12441         } else { // calloc failed, we must be out of memory. Too bad...
12442             dataSize = 0; // prevent calloc events for all subsequent games
12443             return 0;     // and signal this one isn't cached
12444         }
12445     }
12446     movePtr++;
12447     MakePieceList(board, counts);
12448     return movePtr;
12449 }
12450
12451 int
12452 QuickCompare (Board board, int *minCounts, int *maxCounts)
12453 {   // compare according to search mode
12454     int r, f;
12455     switch(appData.searchMode)
12456     {
12457       case 1: // exact position match
12458         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12459         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12460             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12461         }
12462         break;
12463       case 2: // can have extra material on empty squares
12464         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12465             if(board[r][f] == EmptySquare) continue;
12466             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12467         }
12468         break;
12469       case 3: // material with exact Pawn structure
12470         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12471             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12472             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12473         } // fall through to material comparison
12474       case 4: // exact material
12475         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12476         break;
12477       case 6: // material range with given imbalance
12478         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12479         // fall through to range comparison
12480       case 5: // material range
12481         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12482     }
12483     return TRUE;
12484 }
12485
12486 int
12487 QuickScan (Board board, Move *move)
12488 {   // reconstruct game,and compare all positions in it
12489     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12490     do {
12491         int piece = move->piece;
12492         int to = move->to, from = pieceList[piece];
12493         if(found < 0) { // if already found just scan to game end for final piece count
12494           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12495            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12496            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12497                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12498             ) {
12499             static int lastCounts[EmptySquare+1];
12500             int i;
12501             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12502             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12503           } else stretch = 0;
12504           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12505           if(found >= 0 && !appData.minPieces) return found;
12506         }
12507         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12508           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12509           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12510             piece = (++move)->piece;
12511             from = pieceList[piece];
12512             counts[pieceType[piece]]--;
12513             pieceType[piece] = (ChessSquare) move->to;
12514             counts[move->to]++;
12515           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12516             counts[pieceType[quickBoard[to]]]--;
12517             quickBoard[to] = 0; total--;
12518             move++;
12519             continue;
12520           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12521             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12522             from  = pieceList[piece]; // so this must be King
12523             quickBoard[from] = 0;
12524             pieceList[piece] = to;
12525             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12526             quickBoard[from] = 0; // rook
12527             quickBoard[to] = piece;
12528             to = move->to; piece = move->piece;
12529             goto aftercastle;
12530           }
12531         }
12532         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12533         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12534         quickBoard[from] = 0;
12535       aftercastle:
12536         quickBoard[to] = piece;
12537         pieceList[piece] = to;
12538         cnt++; turn ^= 3;
12539         move++;
12540     } while(1);
12541 }
12542
12543 void
12544 InitSearch ()
12545 {
12546     int r, f;
12547     flipSearch = FALSE;
12548     CopyBoard(soughtBoard, boards[currentMove]);
12549     soughtTotal = MakePieceList(soughtBoard, maxSought);
12550     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12551     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12552     CopyBoard(reverseBoard, boards[currentMove]);
12553     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12554         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12555         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12556         reverseBoard[r][f] = piece;
12557     }
12558     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12559     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12560     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12561                  || (boards[currentMove][CASTLING][2] == NoRights ||
12562                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12563                  && (boards[currentMove][CASTLING][5] == NoRights ||
12564                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12565       ) {
12566         flipSearch = TRUE;
12567         CopyBoard(flipBoard, soughtBoard);
12568         CopyBoard(rotateBoard, reverseBoard);
12569         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12570             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12571             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12572         }
12573     }
12574     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12575     if(appData.searchMode >= 5) {
12576         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12577         MakePieceList(soughtBoard, minSought);
12578         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12579     }
12580     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12581         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12582 }
12583
12584 GameInfo dummyInfo;
12585 static int creatingBook;
12586
12587 int
12588 GameContainsPosition (FILE *f, ListGame *lg)
12589 {
12590     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12591     int fromX, fromY, toX, toY;
12592     char promoChar;
12593     static int initDone=FALSE;
12594
12595     // weed out games based on numerical tag comparison
12596     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12597     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12598     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12599     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12600     if(!initDone) {
12601         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12602         initDone = TRUE;
12603     }
12604     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12605     else CopyBoard(boards[scratch], initialPosition); // default start position
12606     if(lg->moves) {
12607         turn = btm + 1;
12608         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12609         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12610     }
12611     if(btm) plyNr++;
12612     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12613     fseek(f, lg->offset, 0);
12614     yynewfile(f);
12615     while(1) {
12616         yyboardindex = scratch;
12617         quickFlag = plyNr+1;
12618         next = Myylex();
12619         quickFlag = 0;
12620         switch(next) {
12621             case PGNTag:
12622                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12623             default:
12624                 continue;
12625
12626             case XBoardGame:
12627             case GNUChessGame:
12628                 if(plyNr) return -1; // after we have seen moves, this is for new game
12629               continue;
12630
12631             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12632             case ImpossibleMove:
12633             case WhiteWins: // game ends here with these four
12634             case BlackWins:
12635             case GameIsDrawn:
12636             case GameUnfinished:
12637                 return -1;
12638
12639             case IllegalMove:
12640                 if(appData.testLegality) return -1;
12641             case WhiteCapturesEnPassant:
12642             case BlackCapturesEnPassant:
12643             case WhitePromotion:
12644             case BlackPromotion:
12645             case WhiteNonPromotion:
12646             case BlackNonPromotion:
12647             case NormalMove:
12648             case FirstLeg:
12649             case WhiteKingSideCastle:
12650             case WhiteQueenSideCastle:
12651             case BlackKingSideCastle:
12652             case BlackQueenSideCastle:
12653             case WhiteKingSideCastleWild:
12654             case WhiteQueenSideCastleWild:
12655             case BlackKingSideCastleWild:
12656             case BlackQueenSideCastleWild:
12657             case WhiteHSideCastleFR:
12658             case WhiteASideCastleFR:
12659             case BlackHSideCastleFR:
12660             case BlackASideCastleFR:
12661                 fromX = currentMoveString[0] - AAA;
12662                 fromY = currentMoveString[1] - ONE;
12663                 toX = currentMoveString[2] - AAA;
12664                 toY = currentMoveString[3] - ONE;
12665                 promoChar = currentMoveString[4];
12666                 break;
12667             case WhiteDrop:
12668             case BlackDrop:
12669                 fromX = next == WhiteDrop ?
12670                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12671                   (int) CharToPiece(ToLower(currentMoveString[0]));
12672                 fromY = DROP_RANK;
12673                 toX = currentMoveString[2] - AAA;
12674                 toY = currentMoveString[3] - ONE;
12675                 promoChar = 0;
12676                 break;
12677         }
12678         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12679         plyNr++;
12680         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12681         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12682         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12683         if(appData.findMirror) {
12684             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12685             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12686         }
12687     }
12688 }
12689
12690 /* Load the nth game from open file f */
12691 int
12692 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12693 {
12694     ChessMove cm;
12695     char buf[MSG_SIZ];
12696     int gn = gameNumber;
12697     ListGame *lg = NULL;
12698     int numPGNTags = 0;
12699     int err, pos = -1;
12700     GameMode oldGameMode;
12701     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12702     char oldName[MSG_SIZ];
12703
12704     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12705
12706     if (appData.debugMode)
12707         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12708
12709     if (gameMode == Training )
12710         SetTrainingModeOff();
12711
12712     oldGameMode = gameMode;
12713     if (gameMode != BeginningOfGame) {
12714       Reset(FALSE, TRUE);
12715     }
12716     killX = killY = -1; // [HGM] lion: in case we did not Reset
12717
12718     gameFileFP = f;
12719     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12720         fclose(lastLoadGameFP);
12721     }
12722
12723     if (useList) {
12724         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12725
12726         if (lg) {
12727             fseek(f, lg->offset, 0);
12728             GameListHighlight(gameNumber);
12729             pos = lg->position;
12730             gn = 1;
12731         }
12732         else {
12733             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12734               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12735             else
12736             DisplayError(_("Game number out of range"), 0);
12737             return FALSE;
12738         }
12739     } else {
12740         GameListDestroy();
12741         if (fseek(f, 0, 0) == -1) {
12742             if (f == lastLoadGameFP ?
12743                 gameNumber == lastLoadGameNumber + 1 :
12744                 gameNumber == 1) {
12745                 gn = 1;
12746             } else {
12747                 DisplayError(_("Can't seek on game file"), 0);
12748                 return FALSE;
12749             }
12750         }
12751     }
12752     lastLoadGameFP = f;
12753     lastLoadGameNumber = gameNumber;
12754     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12755     lastLoadGameUseList = useList;
12756
12757     yynewfile(f);
12758
12759     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12760       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12761                 lg->gameInfo.black);
12762             DisplayTitle(buf);
12763     } else if (*title != NULLCHAR) {
12764         if (gameNumber > 1) {
12765           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12766             DisplayTitle(buf);
12767         } else {
12768             DisplayTitle(title);
12769         }
12770     }
12771
12772     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12773         gameMode = PlayFromGameFile;
12774         ModeHighlight();
12775     }
12776
12777     currentMove = forwardMostMove = backwardMostMove = 0;
12778     CopyBoard(boards[0], initialPosition);
12779     StopClocks();
12780
12781     /*
12782      * Skip the first gn-1 games in the file.
12783      * Also skip over anything that precedes an identifiable
12784      * start of game marker, to avoid being confused by
12785      * garbage at the start of the file.  Currently
12786      * recognized start of game markers are the move number "1",
12787      * the pattern "gnuchess .* game", the pattern
12788      * "^[#;%] [^ ]* game file", and a PGN tag block.
12789      * A game that starts with one of the latter two patterns
12790      * will also have a move number 1, possibly
12791      * following a position diagram.
12792      * 5-4-02: Let's try being more lenient and allowing a game to
12793      * start with an unnumbered move.  Does that break anything?
12794      */
12795     cm = lastLoadGameStart = EndOfFile;
12796     while (gn > 0) {
12797         yyboardindex = forwardMostMove;
12798         cm = (ChessMove) Myylex();
12799         switch (cm) {
12800           case EndOfFile:
12801             if (cmailMsgLoaded) {
12802                 nCmailGames = CMAIL_MAX_GAMES - gn;
12803             } else {
12804                 Reset(TRUE, TRUE);
12805                 DisplayError(_("Game not found in file"), 0);
12806             }
12807             return FALSE;
12808
12809           case GNUChessGame:
12810           case XBoardGame:
12811             gn--;
12812             lastLoadGameStart = cm;
12813             break;
12814
12815           case MoveNumberOne:
12816             switch (lastLoadGameStart) {
12817               case GNUChessGame:
12818               case XBoardGame:
12819               case PGNTag:
12820                 break;
12821               case MoveNumberOne:
12822               case EndOfFile:
12823                 gn--;           /* count this game */
12824                 lastLoadGameStart = cm;
12825                 break;
12826               default:
12827                 /* impossible */
12828                 break;
12829             }
12830             break;
12831
12832           case PGNTag:
12833             switch (lastLoadGameStart) {
12834               case GNUChessGame:
12835               case PGNTag:
12836               case MoveNumberOne:
12837               case EndOfFile:
12838                 gn--;           /* count this game */
12839                 lastLoadGameStart = cm;
12840                 break;
12841               case XBoardGame:
12842                 lastLoadGameStart = cm; /* game counted already */
12843                 break;
12844               default:
12845                 /* impossible */
12846                 break;
12847             }
12848             if (gn > 0) {
12849                 do {
12850                     yyboardindex = forwardMostMove;
12851                     cm = (ChessMove) Myylex();
12852                 } while (cm == PGNTag || cm == Comment);
12853             }
12854             break;
12855
12856           case WhiteWins:
12857           case BlackWins:
12858           case GameIsDrawn:
12859             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12860                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12861                     != CMAIL_OLD_RESULT) {
12862                     nCmailResults ++ ;
12863                     cmailResult[  CMAIL_MAX_GAMES
12864                                 - gn - 1] = CMAIL_OLD_RESULT;
12865                 }
12866             }
12867             break;
12868
12869           case NormalMove:
12870           case FirstLeg:
12871             /* Only a NormalMove can be at the start of a game
12872              * without a position diagram. */
12873             if (lastLoadGameStart == EndOfFile ) {
12874               gn--;
12875               lastLoadGameStart = MoveNumberOne;
12876             }
12877             break;
12878
12879           default:
12880             break;
12881         }
12882     }
12883
12884     if (appData.debugMode)
12885       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12886
12887     if (cm == XBoardGame) {
12888         /* Skip any header junk before position diagram and/or move 1 */
12889         for (;;) {
12890             yyboardindex = forwardMostMove;
12891             cm = (ChessMove) Myylex();
12892
12893             if (cm == EndOfFile ||
12894                 cm == GNUChessGame || cm == XBoardGame) {
12895                 /* Empty game; pretend end-of-file and handle later */
12896                 cm = EndOfFile;
12897                 break;
12898             }
12899
12900             if (cm == MoveNumberOne || cm == PositionDiagram ||
12901                 cm == PGNTag || cm == Comment)
12902               break;
12903         }
12904     } else if (cm == GNUChessGame) {
12905         if (gameInfo.event != NULL) {
12906             free(gameInfo.event);
12907         }
12908         gameInfo.event = StrSave(yy_text);
12909     }
12910
12911     startedFromSetupPosition = FALSE;
12912     while (cm == PGNTag) {
12913         if (appData.debugMode)
12914           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12915         err = ParsePGNTag(yy_text, &gameInfo);
12916         if (!err) numPGNTags++;
12917
12918         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12919         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12920             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12921             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12922             InitPosition(TRUE);
12923             oldVariant = gameInfo.variant;
12924             if (appData.debugMode)
12925               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12926         }
12927
12928
12929         if (gameInfo.fen != NULL) {
12930           Board initial_position;
12931           startedFromSetupPosition = TRUE;
12932           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12933             Reset(TRUE, TRUE);
12934             DisplayError(_("Bad FEN position in file"), 0);
12935             return FALSE;
12936           }
12937           CopyBoard(boards[0], initial_position);
12938           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12939             CopyBoard(initialPosition, initial_position);
12940           if (blackPlaysFirst) {
12941             currentMove = forwardMostMove = backwardMostMove = 1;
12942             CopyBoard(boards[1], initial_position);
12943             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12944             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12945             timeRemaining[0][1] = whiteTimeRemaining;
12946             timeRemaining[1][1] = blackTimeRemaining;
12947             if (commentList[0] != NULL) {
12948               commentList[1] = commentList[0];
12949               commentList[0] = NULL;
12950             }
12951           } else {
12952             currentMove = forwardMostMove = backwardMostMove = 0;
12953           }
12954           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12955           {   int i;
12956               initialRulePlies = FENrulePlies;
12957               for( i=0; i< nrCastlingRights; i++ )
12958                   initialRights[i] = initial_position[CASTLING][i];
12959           }
12960           yyboardindex = forwardMostMove;
12961           free(gameInfo.fen);
12962           gameInfo.fen = NULL;
12963         }
12964
12965         yyboardindex = forwardMostMove;
12966         cm = (ChessMove) Myylex();
12967
12968         /* Handle comments interspersed among the tags */
12969         while (cm == Comment) {
12970             char *p;
12971             if (appData.debugMode)
12972               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12973             p = yy_text;
12974             AppendComment(currentMove, p, FALSE);
12975             yyboardindex = forwardMostMove;
12976             cm = (ChessMove) Myylex();
12977         }
12978     }
12979
12980     /* don't rely on existence of Event tag since if game was
12981      * pasted from clipboard the Event tag may not exist
12982      */
12983     if (numPGNTags > 0){
12984         char *tags;
12985         if (gameInfo.variant == VariantNormal) {
12986           VariantClass v = StringToVariant(gameInfo.event);
12987           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12988           if(v < VariantShogi) gameInfo.variant = v;
12989         }
12990         if (!matchMode) {
12991           if( appData.autoDisplayTags ) {
12992             tags = PGNTags(&gameInfo);
12993             TagsPopUp(tags, CmailMsg());
12994             free(tags);
12995           }
12996         }
12997     } else {
12998         /* Make something up, but don't display it now */
12999         SetGameInfo();
13000         TagsPopDown();
13001     }
13002
13003     if (cm == PositionDiagram) {
13004         int i, j;
13005         char *p;
13006         Board initial_position;
13007
13008         if (appData.debugMode)
13009           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13010
13011         if (!startedFromSetupPosition) {
13012             p = yy_text;
13013             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13014               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13015                 switch (*p) {
13016                   case '{':
13017                   case '[':
13018                   case '-':
13019                   case ' ':
13020                   case '\t':
13021                   case '\n':
13022                   case '\r':
13023                     break;
13024                   default:
13025                     initial_position[i][j++] = CharToPiece(*p);
13026                     break;
13027                 }
13028             while (*p == ' ' || *p == '\t' ||
13029                    *p == '\n' || *p == '\r') p++;
13030
13031             if (strncmp(p, "black", strlen("black"))==0)
13032               blackPlaysFirst = TRUE;
13033             else
13034               blackPlaysFirst = FALSE;
13035             startedFromSetupPosition = TRUE;
13036
13037             CopyBoard(boards[0], initial_position);
13038             if (blackPlaysFirst) {
13039                 currentMove = forwardMostMove = backwardMostMove = 1;
13040                 CopyBoard(boards[1], initial_position);
13041                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13042                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13043                 timeRemaining[0][1] = whiteTimeRemaining;
13044                 timeRemaining[1][1] = blackTimeRemaining;
13045                 if (commentList[0] != NULL) {
13046                     commentList[1] = commentList[0];
13047                     commentList[0] = NULL;
13048                 }
13049             } else {
13050                 currentMove = forwardMostMove = backwardMostMove = 0;
13051             }
13052         }
13053         yyboardindex = forwardMostMove;
13054         cm = (ChessMove) Myylex();
13055     }
13056
13057   if(!creatingBook) {
13058     if (first.pr == NoProc) {
13059         StartChessProgram(&first);
13060     }
13061     InitChessProgram(&first, FALSE);
13062     if(gameInfo.variant == VariantUnknown && *oldName) {
13063         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13064         gameInfo.variant = v;
13065     }
13066     SendToProgram("force\n", &first);
13067     if (startedFromSetupPosition) {
13068         SendBoard(&first, forwardMostMove);
13069     if (appData.debugMode) {
13070         fprintf(debugFP, "Load Game\n");
13071     }
13072         DisplayBothClocks();
13073     }
13074   }
13075
13076     /* [HGM] server: flag to write setup moves in broadcast file as one */
13077     loadFlag = appData.suppressLoadMoves;
13078
13079     while (cm == Comment) {
13080         char *p;
13081         if (appData.debugMode)
13082           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13083         p = yy_text;
13084         AppendComment(currentMove, p, FALSE);
13085         yyboardindex = forwardMostMove;
13086         cm = (ChessMove) Myylex();
13087     }
13088
13089     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13090         cm == WhiteWins || cm == BlackWins ||
13091         cm == GameIsDrawn || cm == GameUnfinished) {
13092         DisplayMessage("", _("No moves in game"));
13093         if (cmailMsgLoaded) {
13094             if (appData.debugMode)
13095               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13096             ClearHighlights();
13097             flipView = FALSE;
13098         }
13099         DrawPosition(FALSE, boards[currentMove]);
13100         DisplayBothClocks();
13101         gameMode = EditGame;
13102         ModeHighlight();
13103         gameFileFP = NULL;
13104         cmailOldMove = 0;
13105         return TRUE;
13106     }
13107
13108     // [HGM] PV info: routine tests if comment empty
13109     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13110         DisplayComment(currentMove - 1, commentList[currentMove]);
13111     }
13112     if (!matchMode && appData.timeDelay != 0)
13113       DrawPosition(FALSE, boards[currentMove]);
13114
13115     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13116       programStats.ok_to_send = 1;
13117     }
13118
13119     /* if the first token after the PGN tags is a move
13120      * and not move number 1, retrieve it from the parser
13121      */
13122     if (cm != MoveNumberOne)
13123         LoadGameOneMove(cm);
13124
13125     /* load the remaining moves from the file */
13126     while (LoadGameOneMove(EndOfFile)) {
13127       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13128       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13129     }
13130
13131     /* rewind to the start of the game */
13132     currentMove = backwardMostMove;
13133
13134     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13135
13136     if (oldGameMode == AnalyzeFile) {
13137       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13138       AnalyzeFileEvent();
13139     } else
13140     if (oldGameMode == AnalyzeMode) {
13141       AnalyzeFileEvent();
13142     }
13143
13144     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13145         long int w, b; // [HGM] adjourn: restore saved clock times
13146         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13147         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13148             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13149             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13150         }
13151     }
13152
13153     if(creatingBook) return TRUE;
13154     if (!matchMode && pos > 0) {
13155         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13156     } else
13157     if (matchMode || appData.timeDelay == 0) {
13158       ToEndEvent();
13159     } else if (appData.timeDelay > 0) {
13160       AutoPlayGameLoop();
13161     }
13162
13163     if (appData.debugMode)
13164         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13165
13166     loadFlag = 0; /* [HGM] true game starts */
13167     return TRUE;
13168 }
13169
13170 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13171 int
13172 ReloadPosition (int offset)
13173 {
13174     int positionNumber = lastLoadPositionNumber + offset;
13175     if (lastLoadPositionFP == NULL) {
13176         DisplayError(_("No position has been loaded yet"), 0);
13177         return FALSE;
13178     }
13179     if (positionNumber <= 0) {
13180         DisplayError(_("Can't back up any further"), 0);
13181         return FALSE;
13182     }
13183     return LoadPosition(lastLoadPositionFP, positionNumber,
13184                         lastLoadPositionTitle);
13185 }
13186
13187 /* Load the nth position from the given file */
13188 int
13189 LoadPositionFromFile (char *filename, int n, char *title)
13190 {
13191     FILE *f;
13192     char buf[MSG_SIZ];
13193
13194     if (strcmp(filename, "-") == 0) {
13195         return LoadPosition(stdin, n, "stdin");
13196     } else {
13197         f = fopen(filename, "rb");
13198         if (f == NULL) {
13199             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13200             DisplayError(buf, errno);
13201             return FALSE;
13202         } else {
13203             return LoadPosition(f, n, title);
13204         }
13205     }
13206 }
13207
13208 /* Load the nth position from the given open file, and close it */
13209 int
13210 LoadPosition (FILE *f, int positionNumber, char *title)
13211 {
13212     char *p, line[MSG_SIZ];
13213     Board initial_position;
13214     int i, j, fenMode, pn;
13215
13216     if (gameMode == Training )
13217         SetTrainingModeOff();
13218
13219     if (gameMode != BeginningOfGame) {
13220         Reset(FALSE, TRUE);
13221     }
13222     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13223         fclose(lastLoadPositionFP);
13224     }
13225     if (positionNumber == 0) positionNumber = 1;
13226     lastLoadPositionFP = f;
13227     lastLoadPositionNumber = positionNumber;
13228     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13229     if (first.pr == NoProc && !appData.noChessProgram) {
13230       StartChessProgram(&first);
13231       InitChessProgram(&first, FALSE);
13232     }
13233     pn = positionNumber;
13234     if (positionNumber < 0) {
13235         /* Negative position number means to seek to that byte offset */
13236         if (fseek(f, -positionNumber, 0) == -1) {
13237             DisplayError(_("Can't seek on position file"), 0);
13238             return FALSE;
13239         };
13240         pn = 1;
13241     } else {
13242         if (fseek(f, 0, 0) == -1) {
13243             if (f == lastLoadPositionFP ?
13244                 positionNumber == lastLoadPositionNumber + 1 :
13245                 positionNumber == 1) {
13246                 pn = 1;
13247             } else {
13248                 DisplayError(_("Can't seek on position file"), 0);
13249                 return FALSE;
13250             }
13251         }
13252     }
13253     /* See if this file is FEN or old-style xboard */
13254     if (fgets(line, MSG_SIZ, f) == NULL) {
13255         DisplayError(_("Position not found in file"), 0);
13256         return FALSE;
13257     }
13258     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13259     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13260
13261     if (pn >= 2) {
13262         if (fenMode || line[0] == '#') pn--;
13263         while (pn > 0) {
13264             /* skip positions before number pn */
13265             if (fgets(line, MSG_SIZ, f) == NULL) {
13266                 Reset(TRUE, TRUE);
13267                 DisplayError(_("Position not found in file"), 0);
13268                 return FALSE;
13269             }
13270             if (fenMode || line[0] == '#') pn--;
13271         }
13272     }
13273
13274     if (fenMode) {
13275         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13276             DisplayError(_("Bad FEN position in file"), 0);
13277             return FALSE;
13278         }
13279     } else {
13280         (void) fgets(line, MSG_SIZ, f);
13281         (void) fgets(line, MSG_SIZ, f);
13282
13283         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13284             (void) fgets(line, MSG_SIZ, f);
13285             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13286                 if (*p == ' ')
13287                   continue;
13288                 initial_position[i][j++] = CharToPiece(*p);
13289             }
13290         }
13291
13292         blackPlaysFirst = FALSE;
13293         if (!feof(f)) {
13294             (void) fgets(line, MSG_SIZ, f);
13295             if (strncmp(line, "black", strlen("black"))==0)
13296               blackPlaysFirst = TRUE;
13297         }
13298     }
13299     startedFromSetupPosition = TRUE;
13300
13301     CopyBoard(boards[0], initial_position);
13302     if (blackPlaysFirst) {
13303         currentMove = forwardMostMove = backwardMostMove = 1;
13304         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13305         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13306         CopyBoard(boards[1], initial_position);
13307         DisplayMessage("", _("Black to play"));
13308     } else {
13309         currentMove = forwardMostMove = backwardMostMove = 0;
13310         DisplayMessage("", _("White to play"));
13311     }
13312     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13313     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13314         SendToProgram("force\n", &first);
13315         SendBoard(&first, forwardMostMove);
13316     }
13317     if (appData.debugMode) {
13318 int i, j;
13319   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13320   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13321         fprintf(debugFP, "Load Position\n");
13322     }
13323
13324     if (positionNumber > 1) {
13325       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13326         DisplayTitle(line);
13327     } else {
13328         DisplayTitle(title);
13329     }
13330     gameMode = EditGame;
13331     ModeHighlight();
13332     ResetClocks();
13333     timeRemaining[0][1] = whiteTimeRemaining;
13334     timeRemaining[1][1] = blackTimeRemaining;
13335     DrawPosition(FALSE, boards[currentMove]);
13336
13337     return TRUE;
13338 }
13339
13340
13341 void
13342 CopyPlayerNameIntoFileName (char **dest, char *src)
13343 {
13344     while (*src != NULLCHAR && *src != ',') {
13345         if (*src == ' ') {
13346             *(*dest)++ = '_';
13347             src++;
13348         } else {
13349             *(*dest)++ = *src++;
13350         }
13351     }
13352 }
13353
13354 char *
13355 DefaultFileName (char *ext)
13356 {
13357     static char def[MSG_SIZ];
13358     char *p;
13359
13360     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13361         p = def;
13362         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13363         *p++ = '-';
13364         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13365         *p++ = '.';
13366         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13367     } else {
13368         def[0] = NULLCHAR;
13369     }
13370     return def;
13371 }
13372
13373 /* Save the current game to the given file */
13374 int
13375 SaveGameToFile (char *filename, int append)
13376 {
13377     FILE *f;
13378     char buf[MSG_SIZ];
13379     int result, i, t,tot=0;
13380
13381     if (strcmp(filename, "-") == 0) {
13382         return SaveGame(stdout, 0, NULL);
13383     } else {
13384         for(i=0; i<10; i++) { // upto 10 tries
13385              f = fopen(filename, append ? "a" : "w");
13386              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13387              if(f || errno != 13) break;
13388              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13389              tot += t;
13390         }
13391         if (f == NULL) {
13392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13393             DisplayError(buf, errno);
13394             return FALSE;
13395         } else {
13396             safeStrCpy(buf, lastMsg, MSG_SIZ);
13397             DisplayMessage(_("Waiting for access to save file"), "");
13398             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13399             DisplayMessage(_("Saving game"), "");
13400             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13401             result = SaveGame(f, 0, NULL);
13402             DisplayMessage(buf, "");
13403             return result;
13404         }
13405     }
13406 }
13407
13408 char *
13409 SavePart (char *str)
13410 {
13411     static char buf[MSG_SIZ];
13412     char *p;
13413
13414     p = strchr(str, ' ');
13415     if (p == NULL) return str;
13416     strncpy(buf, str, p - str);
13417     buf[p - str] = NULLCHAR;
13418     return buf;
13419 }
13420
13421 #define PGN_MAX_LINE 75
13422
13423 #define PGN_SIDE_WHITE  0
13424 #define PGN_SIDE_BLACK  1
13425
13426 static int
13427 FindFirstMoveOutOfBook (int side)
13428 {
13429     int result = -1;
13430
13431     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13432         int index = backwardMostMove;
13433         int has_book_hit = 0;
13434
13435         if( (index % 2) != side ) {
13436             index++;
13437         }
13438
13439         while( index < forwardMostMove ) {
13440             /* Check to see if engine is in book */
13441             int depth = pvInfoList[index].depth;
13442             int score = pvInfoList[index].score;
13443             int in_book = 0;
13444
13445             if( depth <= 2 ) {
13446                 in_book = 1;
13447             }
13448             else if( score == 0 && depth == 63 ) {
13449                 in_book = 1; /* Zappa */
13450             }
13451             else if( score == 2 && depth == 99 ) {
13452                 in_book = 1; /* Abrok */
13453             }
13454
13455             has_book_hit += in_book;
13456
13457             if( ! in_book ) {
13458                 result = index;
13459
13460                 break;
13461             }
13462
13463             index += 2;
13464         }
13465     }
13466
13467     return result;
13468 }
13469
13470 void
13471 GetOutOfBookInfo (char * buf)
13472 {
13473     int oob[2];
13474     int i;
13475     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13476
13477     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13478     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13479
13480     *buf = '\0';
13481
13482     if( oob[0] >= 0 || oob[1] >= 0 ) {
13483         for( i=0; i<2; i++ ) {
13484             int idx = oob[i];
13485
13486             if( idx >= 0 ) {
13487                 if( i > 0 && oob[0] >= 0 ) {
13488                     strcat( buf, "   " );
13489                 }
13490
13491                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13492                 sprintf( buf+strlen(buf), "%s%.2f",
13493                     pvInfoList[idx].score >= 0 ? "+" : "",
13494                     pvInfoList[idx].score / 100.0 );
13495             }
13496         }
13497     }
13498 }
13499
13500 /* Save game in PGN style */
13501 static void
13502 SaveGamePGN2 (FILE *f)
13503 {
13504     int i, offset, linelen, newblock;
13505 //    char *movetext;
13506     char numtext[32];
13507     int movelen, numlen, blank;
13508     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13509
13510     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13511
13512     PrintPGNTags(f, &gameInfo);
13513
13514     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13515
13516     if (backwardMostMove > 0 || startedFromSetupPosition) {
13517         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13518         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13519         fprintf(f, "\n{--------------\n");
13520         PrintPosition(f, backwardMostMove);
13521         fprintf(f, "--------------}\n");
13522         free(fen);
13523     }
13524     else {
13525         /* [AS] Out of book annotation */
13526         if( appData.saveOutOfBookInfo ) {
13527             char buf[64];
13528
13529             GetOutOfBookInfo( buf );
13530
13531             if( buf[0] != '\0' ) {
13532                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13533             }
13534         }
13535
13536         fprintf(f, "\n");
13537     }
13538
13539     i = backwardMostMove;
13540     linelen = 0;
13541     newblock = TRUE;
13542
13543     while (i < forwardMostMove) {
13544         /* Print comments preceding this move */
13545         if (commentList[i] != NULL) {
13546             if (linelen > 0) fprintf(f, "\n");
13547             fprintf(f, "%s", commentList[i]);
13548             linelen = 0;
13549             newblock = TRUE;
13550         }
13551
13552         /* Format move number */
13553         if ((i % 2) == 0)
13554           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13555         else
13556           if (newblock)
13557             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13558           else
13559             numtext[0] = NULLCHAR;
13560
13561         numlen = strlen(numtext);
13562         newblock = FALSE;
13563
13564         /* Print move number */
13565         blank = linelen > 0 && numlen > 0;
13566         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13567             fprintf(f, "\n");
13568             linelen = 0;
13569             blank = 0;
13570         }
13571         if (blank) {
13572             fprintf(f, " ");
13573             linelen++;
13574         }
13575         fprintf(f, "%s", numtext);
13576         linelen += numlen;
13577
13578         /* Get move */
13579         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13580         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13581
13582         /* Print move */
13583         blank = linelen > 0 && movelen > 0;
13584         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13585             fprintf(f, "\n");
13586             linelen = 0;
13587             blank = 0;
13588         }
13589         if (blank) {
13590             fprintf(f, " ");
13591             linelen++;
13592         }
13593         fprintf(f, "%s", move_buffer);
13594         linelen += movelen;
13595
13596         /* [AS] Add PV info if present */
13597         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13598             /* [HGM] add time */
13599             char buf[MSG_SIZ]; int seconds;
13600
13601             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13602
13603             if( seconds <= 0)
13604               buf[0] = 0;
13605             else
13606               if( seconds < 30 )
13607                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13608               else
13609                 {
13610                   seconds = (seconds + 4)/10; // round to full seconds
13611                   if( seconds < 60 )
13612                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13613                   else
13614                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13615                 }
13616
13617             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13618                       pvInfoList[i].score >= 0 ? "+" : "",
13619                       pvInfoList[i].score / 100.0,
13620                       pvInfoList[i].depth,
13621                       buf );
13622
13623             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13624
13625             /* Print score/depth */
13626             blank = linelen > 0 && movelen > 0;
13627             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13628                 fprintf(f, "\n");
13629                 linelen = 0;
13630                 blank = 0;
13631             }
13632             if (blank) {
13633                 fprintf(f, " ");
13634                 linelen++;
13635             }
13636             fprintf(f, "%s", move_buffer);
13637             linelen += movelen;
13638         }
13639
13640         i++;
13641     }
13642
13643     /* Start a new line */
13644     if (linelen > 0) fprintf(f, "\n");
13645
13646     /* Print comments after last move */
13647     if (commentList[i] != NULL) {
13648         fprintf(f, "%s\n", commentList[i]);
13649     }
13650
13651     /* Print result */
13652     if (gameInfo.resultDetails != NULL &&
13653         gameInfo.resultDetails[0] != NULLCHAR) {
13654         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13655         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13656            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13657             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13658         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13659     } else {
13660         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13661     }
13662 }
13663
13664 /* Save game in PGN style and close the file */
13665 int
13666 SaveGamePGN (FILE *f)
13667 {
13668     SaveGamePGN2(f);
13669     fclose(f);
13670     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13671     return TRUE;
13672 }
13673
13674 /* Save game in old style and close the file */
13675 int
13676 SaveGameOldStyle (FILE *f)
13677 {
13678     int i, offset;
13679     time_t tm;
13680
13681     tm = time((time_t *) NULL);
13682
13683     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13684     PrintOpponents(f);
13685
13686     if (backwardMostMove > 0 || startedFromSetupPosition) {
13687         fprintf(f, "\n[--------------\n");
13688         PrintPosition(f, backwardMostMove);
13689         fprintf(f, "--------------]\n");
13690     } else {
13691         fprintf(f, "\n");
13692     }
13693
13694     i = backwardMostMove;
13695     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13696
13697     while (i < forwardMostMove) {
13698         if (commentList[i] != NULL) {
13699             fprintf(f, "[%s]\n", commentList[i]);
13700         }
13701
13702         if ((i % 2) == 1) {
13703             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13704             i++;
13705         } else {
13706             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13707             i++;
13708             if (commentList[i] != NULL) {
13709                 fprintf(f, "\n");
13710                 continue;
13711             }
13712             if (i >= forwardMostMove) {
13713                 fprintf(f, "\n");
13714                 break;
13715             }
13716             fprintf(f, "%s\n", parseList[i]);
13717             i++;
13718         }
13719     }
13720
13721     if (commentList[i] != NULL) {
13722         fprintf(f, "[%s]\n", commentList[i]);
13723     }
13724
13725     /* This isn't really the old style, but it's close enough */
13726     if (gameInfo.resultDetails != NULL &&
13727         gameInfo.resultDetails[0] != NULLCHAR) {
13728         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13729                 gameInfo.resultDetails);
13730     } else {
13731         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13732     }
13733
13734     fclose(f);
13735     return TRUE;
13736 }
13737
13738 /* Save the current game to open file f and close the file */
13739 int
13740 SaveGame (FILE *f, int dummy, char *dummy2)
13741 {
13742     if (gameMode == EditPosition) EditPositionDone(TRUE);
13743     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13744     if (appData.oldSaveStyle)
13745       return SaveGameOldStyle(f);
13746     else
13747       return SaveGamePGN(f);
13748 }
13749
13750 /* Save the current position to the given file */
13751 int
13752 SavePositionToFile (char *filename)
13753 {
13754     FILE *f;
13755     char buf[MSG_SIZ];
13756
13757     if (strcmp(filename, "-") == 0) {
13758         return SavePosition(stdout, 0, NULL);
13759     } else {
13760         f = fopen(filename, "a");
13761         if (f == NULL) {
13762             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13763             DisplayError(buf, errno);
13764             return FALSE;
13765         } else {
13766             safeStrCpy(buf, lastMsg, MSG_SIZ);
13767             DisplayMessage(_("Waiting for access to save file"), "");
13768             flock(fileno(f), LOCK_EX); // [HGM] lock
13769             DisplayMessage(_("Saving position"), "");
13770             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13771             SavePosition(f, 0, NULL);
13772             DisplayMessage(buf, "");
13773             return TRUE;
13774         }
13775     }
13776 }
13777
13778 /* Save the current position to the given open file and close the file */
13779 int
13780 SavePosition (FILE *f, int dummy, char *dummy2)
13781 {
13782     time_t tm;
13783     char *fen;
13784
13785     if (gameMode == EditPosition) EditPositionDone(TRUE);
13786     if (appData.oldSaveStyle) {
13787         tm = time((time_t *) NULL);
13788
13789         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13790         PrintOpponents(f);
13791         fprintf(f, "[--------------\n");
13792         PrintPosition(f, currentMove);
13793         fprintf(f, "--------------]\n");
13794     } else {
13795         fen = PositionToFEN(currentMove, NULL, 1);
13796         fprintf(f, "%s\n", fen);
13797         free(fen);
13798     }
13799     fclose(f);
13800     return TRUE;
13801 }
13802
13803 void
13804 ReloadCmailMsgEvent (int unregister)
13805 {
13806 #if !WIN32
13807     static char *inFilename = NULL;
13808     static char *outFilename;
13809     int i;
13810     struct stat inbuf, outbuf;
13811     int status;
13812
13813     /* Any registered moves are unregistered if unregister is set, */
13814     /* i.e. invoked by the signal handler */
13815     if (unregister) {
13816         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13817             cmailMoveRegistered[i] = FALSE;
13818             if (cmailCommentList[i] != NULL) {
13819                 free(cmailCommentList[i]);
13820                 cmailCommentList[i] = NULL;
13821             }
13822         }
13823         nCmailMovesRegistered = 0;
13824     }
13825
13826     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13827         cmailResult[i] = CMAIL_NOT_RESULT;
13828     }
13829     nCmailResults = 0;
13830
13831     if (inFilename == NULL) {
13832         /* Because the filenames are static they only get malloced once  */
13833         /* and they never get freed                                      */
13834         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13835         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13836
13837         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13838         sprintf(outFilename, "%s.out", appData.cmailGameName);
13839     }
13840
13841     status = stat(outFilename, &outbuf);
13842     if (status < 0) {
13843         cmailMailedMove = FALSE;
13844     } else {
13845         status = stat(inFilename, &inbuf);
13846         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13847     }
13848
13849     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13850        counts the games, notes how each one terminated, etc.
13851
13852        It would be nice to remove this kludge and instead gather all
13853        the information while building the game list.  (And to keep it
13854        in the game list nodes instead of having a bunch of fixed-size
13855        parallel arrays.)  Note this will require getting each game's
13856        termination from the PGN tags, as the game list builder does
13857        not process the game moves.  --mann
13858        */
13859     cmailMsgLoaded = TRUE;
13860     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13861
13862     /* Load first game in the file or popup game menu */
13863     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13864
13865 #endif /* !WIN32 */
13866     return;
13867 }
13868
13869 int
13870 RegisterMove ()
13871 {
13872     FILE *f;
13873     char string[MSG_SIZ];
13874
13875     if (   cmailMailedMove
13876         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13877         return TRUE;            /* Allow free viewing  */
13878     }
13879
13880     /* Unregister move to ensure that we don't leave RegisterMove        */
13881     /* with the move registered when the conditions for registering no   */
13882     /* longer hold                                                       */
13883     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13884         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13885         nCmailMovesRegistered --;
13886
13887         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13888           {
13889               free(cmailCommentList[lastLoadGameNumber - 1]);
13890               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13891           }
13892     }
13893
13894     if (cmailOldMove == -1) {
13895         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13896         return FALSE;
13897     }
13898
13899     if (currentMove > cmailOldMove + 1) {
13900         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13901         return FALSE;
13902     }
13903
13904     if (currentMove < cmailOldMove) {
13905         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13906         return FALSE;
13907     }
13908
13909     if (forwardMostMove > currentMove) {
13910         /* Silently truncate extra moves */
13911         TruncateGame();
13912     }
13913
13914     if (   (currentMove == cmailOldMove + 1)
13915         || (   (currentMove == cmailOldMove)
13916             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13917                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13918         if (gameInfo.result != GameUnfinished) {
13919             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13920         }
13921
13922         if (commentList[currentMove] != NULL) {
13923             cmailCommentList[lastLoadGameNumber - 1]
13924               = StrSave(commentList[currentMove]);
13925         }
13926         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13927
13928         if (appData.debugMode)
13929           fprintf(debugFP, "Saving %s for game %d\n",
13930                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13931
13932         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13933
13934         f = fopen(string, "w");
13935         if (appData.oldSaveStyle) {
13936             SaveGameOldStyle(f); /* also closes the file */
13937
13938             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13939             f = fopen(string, "w");
13940             SavePosition(f, 0, NULL); /* also closes the file */
13941         } else {
13942             fprintf(f, "{--------------\n");
13943             PrintPosition(f, currentMove);
13944             fprintf(f, "--------------}\n\n");
13945
13946             SaveGame(f, 0, NULL); /* also closes the file*/
13947         }
13948
13949         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13950         nCmailMovesRegistered ++;
13951     } else if (nCmailGames == 1) {
13952         DisplayError(_("You have not made a move yet"), 0);
13953         return FALSE;
13954     }
13955
13956     return TRUE;
13957 }
13958
13959 void
13960 MailMoveEvent ()
13961 {
13962 #if !WIN32
13963     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13964     FILE *commandOutput;
13965     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13966     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13967     int nBuffers;
13968     int i;
13969     int archived;
13970     char *arcDir;
13971
13972     if (! cmailMsgLoaded) {
13973         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13974         return;
13975     }
13976
13977     if (nCmailGames == nCmailResults) {
13978         DisplayError(_("No unfinished games"), 0);
13979         return;
13980     }
13981
13982 #if CMAIL_PROHIBIT_REMAIL
13983     if (cmailMailedMove) {
13984       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);
13985         DisplayError(msg, 0);
13986         return;
13987     }
13988 #endif
13989
13990     if (! (cmailMailedMove || RegisterMove())) return;
13991
13992     if (   cmailMailedMove
13993         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13994       snprintf(string, MSG_SIZ, partCommandString,
13995                appData.debugMode ? " -v" : "", appData.cmailGameName);
13996         commandOutput = popen(string, "r");
13997
13998         if (commandOutput == NULL) {
13999             DisplayError(_("Failed to invoke cmail"), 0);
14000         } else {
14001             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14002                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14003             }
14004             if (nBuffers > 1) {
14005                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14006                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14007                 nBytes = MSG_SIZ - 1;
14008             } else {
14009                 (void) memcpy(msg, buffer, nBytes);
14010             }
14011             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14012
14013             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14014                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14015
14016                 archived = TRUE;
14017                 for (i = 0; i < nCmailGames; i ++) {
14018                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14019                         archived = FALSE;
14020                     }
14021                 }
14022                 if (   archived
14023                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14024                         != NULL)) {
14025                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14026                            arcDir,
14027                            appData.cmailGameName,
14028                            gameInfo.date);
14029                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14030                     cmailMsgLoaded = FALSE;
14031                 }
14032             }
14033
14034             DisplayInformation(msg);
14035             pclose(commandOutput);
14036         }
14037     } else {
14038         if ((*cmailMsg) != '\0') {
14039             DisplayInformation(cmailMsg);
14040         }
14041     }
14042
14043     return;
14044 #endif /* !WIN32 */
14045 }
14046
14047 char *
14048 CmailMsg ()
14049 {
14050 #if WIN32
14051     return NULL;
14052 #else
14053     int  prependComma = 0;
14054     char number[5];
14055     char string[MSG_SIZ];       /* Space for game-list */
14056     int  i;
14057
14058     if (!cmailMsgLoaded) return "";
14059
14060     if (cmailMailedMove) {
14061       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14062     } else {
14063         /* Create a list of games left */
14064       snprintf(string, MSG_SIZ, "[");
14065         for (i = 0; i < nCmailGames; i ++) {
14066             if (! (   cmailMoveRegistered[i]
14067                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14068                 if (prependComma) {
14069                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14070                 } else {
14071                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14072                     prependComma = 1;
14073                 }
14074
14075                 strcat(string, number);
14076             }
14077         }
14078         strcat(string, "]");
14079
14080         if (nCmailMovesRegistered + nCmailResults == 0) {
14081             switch (nCmailGames) {
14082               case 1:
14083                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14084                 break;
14085
14086               case 2:
14087                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14088                 break;
14089
14090               default:
14091                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14092                          nCmailGames);
14093                 break;
14094             }
14095         } else {
14096             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14097               case 1:
14098                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14099                          string);
14100                 break;
14101
14102               case 0:
14103                 if (nCmailResults == nCmailGames) {
14104                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14105                 } else {
14106                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14107                 }
14108                 break;
14109
14110               default:
14111                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14112                          string);
14113             }
14114         }
14115     }
14116     return cmailMsg;
14117 #endif /* WIN32 */
14118 }
14119
14120 void
14121 ResetGameEvent ()
14122 {
14123     if (gameMode == Training)
14124       SetTrainingModeOff();
14125
14126     Reset(TRUE, TRUE);
14127     cmailMsgLoaded = FALSE;
14128     if (appData.icsActive) {
14129       SendToICS(ics_prefix);
14130       SendToICS("refresh\n");
14131     }
14132 }
14133
14134 void
14135 ExitEvent (int status)
14136 {
14137     exiting++;
14138     if (exiting > 2) {
14139       /* Give up on clean exit */
14140       exit(status);
14141     }
14142     if (exiting > 1) {
14143       /* Keep trying for clean exit */
14144       return;
14145     }
14146
14147     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14148     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14149
14150     if (telnetISR != NULL) {
14151       RemoveInputSource(telnetISR);
14152     }
14153     if (icsPR != NoProc) {
14154       DestroyChildProcess(icsPR, TRUE);
14155     }
14156
14157     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14158     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14159
14160     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14161     /* make sure this other one finishes before killing it!                  */
14162     if(endingGame) { int count = 0;
14163         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14164         while(endingGame && count++ < 10) DoSleep(1);
14165         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14166     }
14167
14168     /* Kill off chess programs */
14169     if (first.pr != NoProc) {
14170         ExitAnalyzeMode();
14171
14172         DoSleep( appData.delayBeforeQuit );
14173         SendToProgram("quit\n", &first);
14174         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14175     }
14176     if (second.pr != NoProc) {
14177         DoSleep( appData.delayBeforeQuit );
14178         SendToProgram("quit\n", &second);
14179         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14180     }
14181     if (first.isr != NULL) {
14182         RemoveInputSource(first.isr);
14183     }
14184     if (second.isr != NULL) {
14185         RemoveInputSource(second.isr);
14186     }
14187
14188     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14189     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14190
14191     ShutDownFrontEnd();
14192     exit(status);
14193 }
14194
14195 void
14196 PauseEngine (ChessProgramState *cps)
14197 {
14198     SendToProgram("pause\n", cps);
14199     cps->pause = 2;
14200 }
14201
14202 void
14203 UnPauseEngine (ChessProgramState *cps)
14204 {
14205     SendToProgram("resume\n", cps);
14206     cps->pause = 1;
14207 }
14208
14209 void
14210 PauseEvent ()
14211 {
14212     if (appData.debugMode)
14213         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14214     if (pausing) {
14215         pausing = FALSE;
14216         ModeHighlight();
14217         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14218             StartClocks();
14219             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14220                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14221                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14222             }
14223             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14224             HandleMachineMove(stashedInputMove, stalledEngine);
14225             stalledEngine = NULL;
14226             return;
14227         }
14228         if (gameMode == MachinePlaysWhite ||
14229             gameMode == TwoMachinesPlay   ||
14230             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14231             if(first.pause)  UnPauseEngine(&first);
14232             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14233             if(second.pause) UnPauseEngine(&second);
14234             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14235             StartClocks();
14236         } else {
14237             DisplayBothClocks();
14238         }
14239         if (gameMode == PlayFromGameFile) {
14240             if (appData.timeDelay >= 0)
14241                 AutoPlayGameLoop();
14242         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14243             Reset(FALSE, TRUE);
14244             SendToICS(ics_prefix);
14245             SendToICS("refresh\n");
14246         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14247             ForwardInner(forwardMostMove);
14248         }
14249         pauseExamInvalid = FALSE;
14250     } else {
14251         switch (gameMode) {
14252           default:
14253             return;
14254           case IcsExamining:
14255             pauseExamForwardMostMove = forwardMostMove;
14256             pauseExamInvalid = FALSE;
14257             /* fall through */
14258           case IcsObserving:
14259           case IcsPlayingWhite:
14260           case IcsPlayingBlack:
14261             pausing = TRUE;
14262             ModeHighlight();
14263             return;
14264           case PlayFromGameFile:
14265             (void) StopLoadGameTimer();
14266             pausing = TRUE;
14267             ModeHighlight();
14268             break;
14269           case BeginningOfGame:
14270             if (appData.icsActive) return;
14271             /* else fall through */
14272           case MachinePlaysWhite:
14273           case MachinePlaysBlack:
14274           case TwoMachinesPlay:
14275             if (forwardMostMove == 0)
14276               return;           /* don't pause if no one has moved */
14277             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14278                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14279                 if(onMove->pause) {           // thinking engine can be paused
14280                     PauseEngine(onMove);      // do it
14281                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14282                         PauseEngine(onMove->other);
14283                     else
14284                         SendToProgram("easy\n", onMove->other);
14285                     StopClocks();
14286                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14287             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14288                 if(first.pause) {
14289                     PauseEngine(&first);
14290                     StopClocks();
14291                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14292             } else { // human on move, pause pondering by either method
14293                 if(first.pause)
14294                     PauseEngine(&first);
14295                 else if(appData.ponderNextMove)
14296                     SendToProgram("easy\n", &first);
14297                 StopClocks();
14298             }
14299             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14300           case AnalyzeMode:
14301             pausing = TRUE;
14302             ModeHighlight();
14303             break;
14304         }
14305     }
14306 }
14307
14308 void
14309 EditCommentEvent ()
14310 {
14311     char title[MSG_SIZ];
14312
14313     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14314       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14315     } else {
14316       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14317                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14318                parseList[currentMove - 1]);
14319     }
14320
14321     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14322 }
14323
14324
14325 void
14326 EditTagsEvent ()
14327 {
14328     char *tags = PGNTags(&gameInfo);
14329     bookUp = FALSE;
14330     EditTagsPopUp(tags, NULL);
14331     free(tags);
14332 }
14333
14334 void
14335 ToggleSecond ()
14336 {
14337   if(second.analyzing) {
14338     SendToProgram("exit\n", &second);
14339     second.analyzing = FALSE;
14340   } else {
14341     if (second.pr == NoProc) StartChessProgram(&second);
14342     InitChessProgram(&second, FALSE);
14343     FeedMovesToProgram(&second, currentMove);
14344
14345     SendToProgram("analyze\n", &second);
14346     second.analyzing = TRUE;
14347   }
14348 }
14349
14350 /* Toggle ShowThinking */
14351 void
14352 ToggleShowThinking()
14353 {
14354   appData.showThinking = !appData.showThinking;
14355   ShowThinkingEvent();
14356 }
14357
14358 int
14359 AnalyzeModeEvent ()
14360 {
14361     char buf[MSG_SIZ];
14362
14363     if (!first.analysisSupport) {
14364       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14365       DisplayError(buf, 0);
14366       return 0;
14367     }
14368     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14369     if (appData.icsActive) {
14370         if (gameMode != IcsObserving) {
14371           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14372             DisplayError(buf, 0);
14373             /* secure check */
14374             if (appData.icsEngineAnalyze) {
14375                 if (appData.debugMode)
14376                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14377                 ExitAnalyzeMode();
14378                 ModeHighlight();
14379             }
14380             return 0;
14381         }
14382         /* if enable, user wants to disable icsEngineAnalyze */
14383         if (appData.icsEngineAnalyze) {
14384                 ExitAnalyzeMode();
14385                 ModeHighlight();
14386                 return 0;
14387         }
14388         appData.icsEngineAnalyze = TRUE;
14389         if (appData.debugMode)
14390             fprintf(debugFP, "ICS engine analyze starting... \n");
14391     }
14392
14393     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14394     if (appData.noChessProgram || gameMode == AnalyzeMode)
14395       return 0;
14396
14397     if (gameMode != AnalyzeFile) {
14398         if (!appData.icsEngineAnalyze) {
14399                EditGameEvent();
14400                if (gameMode != EditGame) return 0;
14401         }
14402         if (!appData.showThinking) ToggleShowThinking();
14403         ResurrectChessProgram();
14404         SendToProgram("analyze\n", &first);
14405         first.analyzing = TRUE;
14406         /*first.maybeThinking = TRUE;*/
14407         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14408         EngineOutputPopUp();
14409     }
14410     if (!appData.icsEngineAnalyze) {
14411         gameMode = AnalyzeMode;
14412         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14413     }
14414     pausing = FALSE;
14415     ModeHighlight();
14416     SetGameInfo();
14417
14418     StartAnalysisClock();
14419     GetTimeMark(&lastNodeCountTime);
14420     lastNodeCount = 0;
14421     return 1;
14422 }
14423
14424 void
14425 AnalyzeFileEvent ()
14426 {
14427     if (appData.noChessProgram || gameMode == AnalyzeFile)
14428       return;
14429
14430     if (!first.analysisSupport) {
14431       char buf[MSG_SIZ];
14432       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14433       DisplayError(buf, 0);
14434       return;
14435     }
14436
14437     if (gameMode != AnalyzeMode) {
14438         keepInfo = 1; // mere annotating should not alter PGN tags
14439         EditGameEvent();
14440         keepInfo = 0;
14441         if (gameMode != EditGame) return;
14442         if (!appData.showThinking) ToggleShowThinking();
14443         ResurrectChessProgram();
14444         SendToProgram("analyze\n", &first);
14445         first.analyzing = TRUE;
14446         /*first.maybeThinking = TRUE;*/
14447         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14448         EngineOutputPopUp();
14449     }
14450     gameMode = AnalyzeFile;
14451     pausing = FALSE;
14452     ModeHighlight();
14453
14454     StartAnalysisClock();
14455     GetTimeMark(&lastNodeCountTime);
14456     lastNodeCount = 0;
14457     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14458     AnalysisPeriodicEvent(1);
14459 }
14460
14461 void
14462 MachineWhiteEvent ()
14463 {
14464     char buf[MSG_SIZ];
14465     char *bookHit = NULL;
14466
14467     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14468       return;
14469
14470
14471     if (gameMode == PlayFromGameFile ||
14472         gameMode == TwoMachinesPlay  ||
14473         gameMode == Training         ||
14474         gameMode == AnalyzeMode      ||
14475         gameMode == EndOfGame)
14476         EditGameEvent();
14477
14478     if (gameMode == EditPosition)
14479         EditPositionDone(TRUE);
14480
14481     if (!WhiteOnMove(currentMove)) {
14482         DisplayError(_("It is not White's turn"), 0);
14483         return;
14484     }
14485
14486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14487       ExitAnalyzeMode();
14488
14489     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14490         gameMode == AnalyzeFile)
14491         TruncateGame();
14492
14493     ResurrectChessProgram();    /* in case it isn't running */
14494     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14495         gameMode = MachinePlaysWhite;
14496         ResetClocks();
14497     } else
14498     gameMode = MachinePlaysWhite;
14499     pausing = FALSE;
14500     ModeHighlight();
14501     SetGameInfo();
14502     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14503     DisplayTitle(buf);
14504     if (first.sendName) {
14505       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14506       SendToProgram(buf, &first);
14507     }
14508     if (first.sendTime) {
14509       if (first.useColors) {
14510         SendToProgram("black\n", &first); /*gnu kludge*/
14511       }
14512       SendTimeRemaining(&first, TRUE);
14513     }
14514     if (first.useColors) {
14515       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14516     }
14517     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14518     SetMachineThinkingEnables();
14519     first.maybeThinking = TRUE;
14520     StartClocks();
14521     firstMove = FALSE;
14522
14523     if (appData.autoFlipView && !flipView) {
14524       flipView = !flipView;
14525       DrawPosition(FALSE, NULL);
14526       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14527     }
14528
14529     if(bookHit) { // [HGM] book: simulate book reply
14530         static char bookMove[MSG_SIZ]; // a bit generous?
14531
14532         programStats.nodes = programStats.depth = programStats.time =
14533         programStats.score = programStats.got_only_move = 0;
14534         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14535
14536         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14537         strcat(bookMove, bookHit);
14538         HandleMachineMove(bookMove, &first);
14539     }
14540 }
14541
14542 void
14543 MachineBlackEvent ()
14544 {
14545   char buf[MSG_SIZ];
14546   char *bookHit = NULL;
14547
14548     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14549         return;
14550
14551
14552     if (gameMode == PlayFromGameFile ||
14553         gameMode == TwoMachinesPlay  ||
14554         gameMode == Training         ||
14555         gameMode == AnalyzeMode      ||
14556         gameMode == EndOfGame)
14557         EditGameEvent();
14558
14559     if (gameMode == EditPosition)
14560         EditPositionDone(TRUE);
14561
14562     if (WhiteOnMove(currentMove)) {
14563         DisplayError(_("It is not Black's turn"), 0);
14564         return;
14565     }
14566
14567     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14568       ExitAnalyzeMode();
14569
14570     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14571         gameMode == AnalyzeFile)
14572         TruncateGame();
14573
14574     ResurrectChessProgram();    /* in case it isn't running */
14575     gameMode = MachinePlaysBlack;
14576     pausing = FALSE;
14577     ModeHighlight();
14578     SetGameInfo();
14579     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14580     DisplayTitle(buf);
14581     if (first.sendName) {
14582       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14583       SendToProgram(buf, &first);
14584     }
14585     if (first.sendTime) {
14586       if (first.useColors) {
14587         SendToProgram("white\n", &first); /*gnu kludge*/
14588       }
14589       SendTimeRemaining(&first, FALSE);
14590     }
14591     if (first.useColors) {
14592       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14593     }
14594     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14595     SetMachineThinkingEnables();
14596     first.maybeThinking = TRUE;
14597     StartClocks();
14598
14599     if (appData.autoFlipView && flipView) {
14600       flipView = !flipView;
14601       DrawPosition(FALSE, NULL);
14602       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14603     }
14604     if(bookHit) { // [HGM] book: simulate book reply
14605         static char bookMove[MSG_SIZ]; // a bit generous?
14606
14607         programStats.nodes = programStats.depth = programStats.time =
14608         programStats.score = programStats.got_only_move = 0;
14609         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14610
14611         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14612         strcat(bookMove, bookHit);
14613         HandleMachineMove(bookMove, &first);
14614     }
14615 }
14616
14617
14618 void
14619 DisplayTwoMachinesTitle ()
14620 {
14621     char buf[MSG_SIZ];
14622     if (appData.matchGames > 0) {
14623         if(appData.tourneyFile[0]) {
14624           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14625                    gameInfo.white, _("vs."), gameInfo.black,
14626                    nextGame+1, appData.matchGames+1,
14627                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14628         } else
14629         if (first.twoMachinesColor[0] == 'w') {
14630           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14631                    gameInfo.white, _("vs."),  gameInfo.black,
14632                    first.matchWins, second.matchWins,
14633                    matchGame - 1 - (first.matchWins + second.matchWins));
14634         } else {
14635           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14636                    gameInfo.white, _("vs."), gameInfo.black,
14637                    second.matchWins, first.matchWins,
14638                    matchGame - 1 - (first.matchWins + second.matchWins));
14639         }
14640     } else {
14641       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14642     }
14643     DisplayTitle(buf);
14644 }
14645
14646 void
14647 SettingsMenuIfReady ()
14648 {
14649   if (second.lastPing != second.lastPong) {
14650     DisplayMessage("", _("Waiting for second chess program"));
14651     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14652     return;
14653   }
14654   ThawUI();
14655   DisplayMessage("", "");
14656   SettingsPopUp(&second);
14657 }
14658
14659 int
14660 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14661 {
14662     char buf[MSG_SIZ];
14663     if (cps->pr == NoProc) {
14664         StartChessProgram(cps);
14665         if (cps->protocolVersion == 1) {
14666           retry();
14667           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14668         } else {
14669           /* kludge: allow timeout for initial "feature" command */
14670           if(retry != TwoMachinesEventIfReady) FreezeUI();
14671           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14672           DisplayMessage("", buf);
14673           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14674         }
14675         return 1;
14676     }
14677     return 0;
14678 }
14679
14680 void
14681 TwoMachinesEvent P((void))
14682 {
14683     int i;
14684     char buf[MSG_SIZ];
14685     ChessProgramState *onmove;
14686     char *bookHit = NULL;
14687     static int stalling = 0;
14688     TimeMark now;
14689     long wait;
14690
14691     if (appData.noChessProgram) return;
14692
14693     switch (gameMode) {
14694       case TwoMachinesPlay:
14695         return;
14696       case MachinePlaysWhite:
14697       case MachinePlaysBlack:
14698         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14699             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14700             return;
14701         }
14702         /* fall through */
14703       case BeginningOfGame:
14704       case PlayFromGameFile:
14705       case EndOfGame:
14706         EditGameEvent();
14707         if (gameMode != EditGame) return;
14708         break;
14709       case EditPosition:
14710         EditPositionDone(TRUE);
14711         break;
14712       case AnalyzeMode:
14713       case AnalyzeFile:
14714         ExitAnalyzeMode();
14715         break;
14716       case EditGame:
14717       default:
14718         break;
14719     }
14720
14721 //    forwardMostMove = currentMove;
14722     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14723     startingEngine = TRUE;
14724
14725     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14726
14727     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14728     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14729       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14730       return;
14731     }
14732     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14733
14734     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14735                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14736         startingEngine = matchMode = FALSE;
14737         DisplayError("second engine does not play this", 0);
14738         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14739         EditGameEvent(); // switch back to EditGame mode
14740         return;
14741     }
14742
14743     if(!stalling) {
14744       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14745       SendToProgram("force\n", &second);
14746       stalling = 1;
14747       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14748       return;
14749     }
14750     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14751     if(appData.matchPause>10000 || appData.matchPause<10)
14752                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14753     wait = SubtractTimeMarks(&now, &pauseStart);
14754     if(wait < appData.matchPause) {
14755         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14756         return;
14757     }
14758     // we are now committed to starting the game
14759     stalling = 0;
14760     DisplayMessage("", "");
14761     if (startedFromSetupPosition) {
14762         SendBoard(&second, backwardMostMove);
14763     if (appData.debugMode) {
14764         fprintf(debugFP, "Two Machines\n");
14765     }
14766     }
14767     for (i = backwardMostMove; i < forwardMostMove; i++) {
14768         SendMoveToProgram(i, &second);
14769     }
14770
14771     gameMode = TwoMachinesPlay;
14772     pausing = startingEngine = FALSE;
14773     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14774     SetGameInfo();
14775     DisplayTwoMachinesTitle();
14776     firstMove = TRUE;
14777     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14778         onmove = &first;
14779     } else {
14780         onmove = &second;
14781     }
14782     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14783     SendToProgram(first.computerString, &first);
14784     if (first.sendName) {
14785       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14786       SendToProgram(buf, &first);
14787     }
14788     SendToProgram(second.computerString, &second);
14789     if (second.sendName) {
14790       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14791       SendToProgram(buf, &second);
14792     }
14793
14794     ResetClocks();
14795     if (!first.sendTime || !second.sendTime) {
14796         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14797         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14798     }
14799     if (onmove->sendTime) {
14800       if (onmove->useColors) {
14801         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14802       }
14803       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14804     }
14805     if (onmove->useColors) {
14806       SendToProgram(onmove->twoMachinesColor, onmove);
14807     }
14808     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14809 //    SendToProgram("go\n", onmove);
14810     onmove->maybeThinking = TRUE;
14811     SetMachineThinkingEnables();
14812
14813     StartClocks();
14814
14815     if(bookHit) { // [HGM] book: simulate book reply
14816         static char bookMove[MSG_SIZ]; // a bit generous?
14817
14818         programStats.nodes = programStats.depth = programStats.time =
14819         programStats.score = programStats.got_only_move = 0;
14820         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14821
14822         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14823         strcat(bookMove, bookHit);
14824         savedMessage = bookMove; // args for deferred call
14825         savedState = onmove;
14826         ScheduleDelayedEvent(DeferredBookMove, 1);
14827     }
14828 }
14829
14830 void
14831 TrainingEvent ()
14832 {
14833     if (gameMode == Training) {
14834       SetTrainingModeOff();
14835       gameMode = PlayFromGameFile;
14836       DisplayMessage("", _("Training mode off"));
14837     } else {
14838       gameMode = Training;
14839       animateTraining = appData.animate;
14840
14841       /* make sure we are not already at the end of the game */
14842       if (currentMove < forwardMostMove) {
14843         SetTrainingModeOn();
14844         DisplayMessage("", _("Training mode on"));
14845       } else {
14846         gameMode = PlayFromGameFile;
14847         DisplayError(_("Already at end of game"), 0);
14848       }
14849     }
14850     ModeHighlight();
14851 }
14852
14853 void
14854 IcsClientEvent ()
14855 {
14856     if (!appData.icsActive) return;
14857     switch (gameMode) {
14858       case IcsPlayingWhite:
14859       case IcsPlayingBlack:
14860       case IcsObserving:
14861       case IcsIdle:
14862       case BeginningOfGame:
14863       case IcsExamining:
14864         return;
14865
14866       case EditGame:
14867         break;
14868
14869       case EditPosition:
14870         EditPositionDone(TRUE);
14871         break;
14872
14873       case AnalyzeMode:
14874       case AnalyzeFile:
14875         ExitAnalyzeMode();
14876         break;
14877
14878       default:
14879         EditGameEvent();
14880         break;
14881     }
14882
14883     gameMode = IcsIdle;
14884     ModeHighlight();
14885     return;
14886 }
14887
14888 void
14889 EditGameEvent ()
14890 {
14891     int i;
14892
14893     switch (gameMode) {
14894       case Training:
14895         SetTrainingModeOff();
14896         break;
14897       case MachinePlaysWhite:
14898       case MachinePlaysBlack:
14899       case BeginningOfGame:
14900         SendToProgram("force\n", &first);
14901         SetUserThinkingEnables();
14902         break;
14903       case PlayFromGameFile:
14904         (void) StopLoadGameTimer();
14905         if (gameFileFP != NULL) {
14906             gameFileFP = NULL;
14907         }
14908         break;
14909       case EditPosition:
14910         EditPositionDone(TRUE);
14911         break;
14912       case AnalyzeMode:
14913       case AnalyzeFile:
14914         ExitAnalyzeMode();
14915         SendToProgram("force\n", &first);
14916         break;
14917       case TwoMachinesPlay:
14918         GameEnds(EndOfFile, NULL, GE_PLAYER);
14919         ResurrectChessProgram();
14920         SetUserThinkingEnables();
14921         break;
14922       case EndOfGame:
14923         ResurrectChessProgram();
14924         break;
14925       case IcsPlayingBlack:
14926       case IcsPlayingWhite:
14927         DisplayError(_("Warning: You are still playing a game"), 0);
14928         break;
14929       case IcsObserving:
14930         DisplayError(_("Warning: You are still observing a game"), 0);
14931         break;
14932       case IcsExamining:
14933         DisplayError(_("Warning: You are still examining a game"), 0);
14934         break;
14935       case IcsIdle:
14936         break;
14937       case EditGame:
14938       default:
14939         return;
14940     }
14941
14942     pausing = FALSE;
14943     StopClocks();
14944     first.offeredDraw = second.offeredDraw = 0;
14945
14946     if (gameMode == PlayFromGameFile) {
14947         whiteTimeRemaining = timeRemaining[0][currentMove];
14948         blackTimeRemaining = timeRemaining[1][currentMove];
14949         DisplayTitle("");
14950     }
14951
14952     if (gameMode == MachinePlaysWhite ||
14953         gameMode == MachinePlaysBlack ||
14954         gameMode == TwoMachinesPlay ||
14955         gameMode == EndOfGame) {
14956         i = forwardMostMove;
14957         while (i > currentMove) {
14958             SendToProgram("undo\n", &first);
14959             i--;
14960         }
14961         if(!adjustedClock) {
14962         whiteTimeRemaining = timeRemaining[0][currentMove];
14963         blackTimeRemaining = timeRemaining[1][currentMove];
14964         DisplayBothClocks();
14965         }
14966         if (whiteFlag || blackFlag) {
14967             whiteFlag = blackFlag = 0;
14968         }
14969         DisplayTitle("");
14970     }
14971
14972     gameMode = EditGame;
14973     ModeHighlight();
14974     SetGameInfo();
14975 }
14976
14977
14978 void
14979 EditPositionEvent ()
14980 {
14981     if (gameMode == EditPosition) {
14982         EditGameEvent();
14983         return;
14984     }
14985
14986     EditGameEvent();
14987     if (gameMode != EditGame) return;
14988
14989     gameMode = EditPosition;
14990     ModeHighlight();
14991     SetGameInfo();
14992     if (currentMove > 0)
14993       CopyBoard(boards[0], boards[currentMove]);
14994
14995     blackPlaysFirst = !WhiteOnMove(currentMove);
14996     ResetClocks();
14997     currentMove = forwardMostMove = backwardMostMove = 0;
14998     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14999     DisplayMove(-1);
15000     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15001 }
15002
15003 void
15004 ExitAnalyzeMode ()
15005 {
15006     /* [DM] icsEngineAnalyze - possible call from other functions */
15007     if (appData.icsEngineAnalyze) {
15008         appData.icsEngineAnalyze = FALSE;
15009
15010         DisplayMessage("",_("Close ICS engine analyze..."));
15011     }
15012     if (first.analysisSupport && first.analyzing) {
15013       SendToBoth("exit\n");
15014       first.analyzing = second.analyzing = FALSE;
15015     }
15016     thinkOutput[0] = NULLCHAR;
15017 }
15018
15019 void
15020 EditPositionDone (Boolean fakeRights)
15021 {
15022     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15023
15024     startedFromSetupPosition = TRUE;
15025     InitChessProgram(&first, FALSE);
15026     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15027       boards[0][EP_STATUS] = EP_NONE;
15028       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15029       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15030         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15031         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15032       } else boards[0][CASTLING][2] = NoRights;
15033       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15034         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15035         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15036       } else boards[0][CASTLING][5] = NoRights;
15037       if(gameInfo.variant == VariantSChess) {
15038         int i;
15039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15040           boards[0][VIRGIN][i] = 0;
15041           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15042           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15043         }
15044       }
15045     }
15046     SendToProgram("force\n", &first);
15047     if (blackPlaysFirst) {
15048         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15049         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15050         currentMove = forwardMostMove = backwardMostMove = 1;
15051         CopyBoard(boards[1], boards[0]);
15052     } else {
15053         currentMove = forwardMostMove = backwardMostMove = 0;
15054     }
15055     SendBoard(&first, forwardMostMove);
15056     if (appData.debugMode) {
15057         fprintf(debugFP, "EditPosDone\n");
15058     }
15059     DisplayTitle("");
15060     DisplayMessage("", "");
15061     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15062     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15063     gameMode = EditGame;
15064     ModeHighlight();
15065     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15066     ClearHighlights(); /* [AS] */
15067 }
15068
15069 /* Pause for `ms' milliseconds */
15070 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15071 void
15072 TimeDelay (long ms)
15073 {
15074     TimeMark m1, m2;
15075
15076     GetTimeMark(&m1);
15077     do {
15078         GetTimeMark(&m2);
15079     } while (SubtractTimeMarks(&m2, &m1) < ms);
15080 }
15081
15082 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15083 void
15084 SendMultiLineToICS (char *buf)
15085 {
15086     char temp[MSG_SIZ+1], *p;
15087     int len;
15088
15089     len = strlen(buf);
15090     if (len > MSG_SIZ)
15091       len = MSG_SIZ;
15092
15093     strncpy(temp, buf, len);
15094     temp[len] = 0;
15095
15096     p = temp;
15097     while (*p) {
15098         if (*p == '\n' || *p == '\r')
15099           *p = ' ';
15100         ++p;
15101     }
15102
15103     strcat(temp, "\n");
15104     SendToICS(temp);
15105     SendToPlayer(temp, strlen(temp));
15106 }
15107
15108 void
15109 SetWhiteToPlayEvent ()
15110 {
15111     if (gameMode == EditPosition) {
15112         blackPlaysFirst = FALSE;
15113         DisplayBothClocks();    /* works because currentMove is 0 */
15114     } else if (gameMode == IcsExamining) {
15115         SendToICS(ics_prefix);
15116         SendToICS("tomove white\n");
15117     }
15118 }
15119
15120 void
15121 SetBlackToPlayEvent ()
15122 {
15123     if (gameMode == EditPosition) {
15124         blackPlaysFirst = TRUE;
15125         currentMove = 1;        /* kludge */
15126         DisplayBothClocks();
15127         currentMove = 0;
15128     } else if (gameMode == IcsExamining) {
15129         SendToICS(ics_prefix);
15130         SendToICS("tomove black\n");
15131     }
15132 }
15133
15134 void
15135 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15136 {
15137     char buf[MSG_SIZ];
15138     ChessSquare piece = boards[0][y][x];
15139     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15140     static int lastVariant;
15141
15142     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15143
15144     switch (selection) {
15145       case ClearBoard:
15146         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15147         MarkTargetSquares(1);
15148         CopyBoard(currentBoard, boards[0]);
15149         CopyBoard(menuBoard, initialPosition);
15150         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15151             SendToICS(ics_prefix);
15152             SendToICS("bsetup clear\n");
15153         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15154             SendToICS(ics_prefix);
15155             SendToICS("clearboard\n");
15156         } else {
15157             int nonEmpty = 0;
15158             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15159                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15160                 for (y = 0; y < BOARD_HEIGHT; y++) {
15161                     if (gameMode == IcsExamining) {
15162                         if (boards[currentMove][y][x] != EmptySquare) {
15163                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15164                                     AAA + x, ONE + y);
15165                             SendToICS(buf);
15166                         }
15167                     } else if(boards[0][y][x] != DarkSquare) {
15168                         if(boards[0][y][x] != p) nonEmpty++;
15169                         boards[0][y][x] = p;
15170                     }
15171                 }
15172             }
15173             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15174                 int r;
15175                 for(r = 0; r < BOARD_HEIGHT; r++) {
15176                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15177                     ChessSquare p = menuBoard[r][x];
15178                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15179                   }
15180                 }
15181                 DisplayMessage("Clicking clock again restores position", "");
15182                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15183                 if(!nonEmpty) { // asked to clear an empty board
15184                     CopyBoard(boards[0], menuBoard);
15185                 } else
15186                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15187                     CopyBoard(boards[0], initialPosition);
15188                 } else
15189                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15190                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15191                     CopyBoard(boards[0], erasedBoard);
15192                 } else
15193                     CopyBoard(erasedBoard, currentBoard);
15194
15195             }
15196         }
15197         if (gameMode == EditPosition) {
15198             DrawPosition(FALSE, boards[0]);
15199         }
15200         break;
15201
15202       case WhitePlay:
15203         SetWhiteToPlayEvent();
15204         break;
15205
15206       case BlackPlay:
15207         SetBlackToPlayEvent();
15208         break;
15209
15210       case EmptySquare:
15211         if (gameMode == IcsExamining) {
15212             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15213             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15214             SendToICS(buf);
15215         } else {
15216             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15217                 if(x == BOARD_LEFT-2) {
15218                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15219                     boards[0][y][1] = 0;
15220                 } else
15221                 if(x == BOARD_RGHT+1) {
15222                     if(y >= gameInfo.holdingsSize) break;
15223                     boards[0][y][BOARD_WIDTH-2] = 0;
15224                 } else break;
15225             }
15226             boards[0][y][x] = EmptySquare;
15227             DrawPosition(FALSE, boards[0]);
15228         }
15229         break;
15230
15231       case PromotePiece:
15232         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15233            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15234             selection = (ChessSquare) (PROMOTED piece);
15235         } else if(piece == EmptySquare) selection = WhiteSilver;
15236         else selection = (ChessSquare)((int)piece - 1);
15237         goto defaultlabel;
15238
15239       case DemotePiece:
15240         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15241            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15242             selection = (ChessSquare) (DEMOTED piece);
15243         } else if(piece == EmptySquare) selection = BlackSilver;
15244         else selection = (ChessSquare)((int)piece + 1);
15245         goto defaultlabel;
15246
15247       case WhiteQueen:
15248       case BlackQueen:
15249         if(gameInfo.variant == VariantShatranj ||
15250            gameInfo.variant == VariantXiangqi  ||
15251            gameInfo.variant == VariantCourier  ||
15252            gameInfo.variant == VariantASEAN    ||
15253            gameInfo.variant == VariantMakruk     )
15254             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15255         goto defaultlabel;
15256
15257       case WhiteKing:
15258       case BlackKing:
15259         if(gameInfo.variant == VariantXiangqi)
15260             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15261         if(gameInfo.variant == VariantKnightmate)
15262             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15263       default:
15264         defaultlabel:
15265         if (gameMode == IcsExamining) {
15266             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15267             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15268                      PieceToChar(selection), AAA + x, ONE + y);
15269             SendToICS(buf);
15270         } else {
15271             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15272                 int n;
15273                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15274                     n = PieceToNumber(selection - BlackPawn);
15275                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15276                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15277                     boards[0][BOARD_HEIGHT-1-n][1]++;
15278                 } else
15279                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15280                     n = PieceToNumber(selection);
15281                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15282                     boards[0][n][BOARD_WIDTH-1] = selection;
15283                     boards[0][n][BOARD_WIDTH-2]++;
15284                 }
15285             } else
15286             boards[0][y][x] = selection;
15287             DrawPosition(TRUE, boards[0]);
15288             ClearHighlights();
15289             fromX = fromY = -1;
15290         }
15291         break;
15292     }
15293 }
15294
15295
15296 void
15297 DropMenuEvent (ChessSquare selection, int x, int y)
15298 {
15299     ChessMove moveType;
15300
15301     switch (gameMode) {
15302       case IcsPlayingWhite:
15303       case MachinePlaysBlack:
15304         if (!WhiteOnMove(currentMove)) {
15305             DisplayMoveError(_("It is Black's turn"));
15306             return;
15307         }
15308         moveType = WhiteDrop;
15309         break;
15310       case IcsPlayingBlack:
15311       case MachinePlaysWhite:
15312         if (WhiteOnMove(currentMove)) {
15313             DisplayMoveError(_("It is White's turn"));
15314             return;
15315         }
15316         moveType = BlackDrop;
15317         break;
15318       case EditGame:
15319         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15320         break;
15321       default:
15322         return;
15323     }
15324
15325     if (moveType == BlackDrop && selection < BlackPawn) {
15326       selection = (ChessSquare) ((int) selection
15327                                  + (int) BlackPawn - (int) WhitePawn);
15328     }
15329     if (boards[currentMove][y][x] != EmptySquare) {
15330         DisplayMoveError(_("That square is occupied"));
15331         return;
15332     }
15333
15334     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15335 }
15336
15337 void
15338 AcceptEvent ()
15339 {
15340     /* Accept a pending offer of any kind from opponent */
15341
15342     if (appData.icsActive) {
15343         SendToICS(ics_prefix);
15344         SendToICS("accept\n");
15345     } else if (cmailMsgLoaded) {
15346         if (currentMove == cmailOldMove &&
15347             commentList[cmailOldMove] != NULL &&
15348             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15349                    "Black offers a draw" : "White offers a draw")) {
15350             TruncateGame();
15351             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15352             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15353         } else {
15354             DisplayError(_("There is no pending offer on this move"), 0);
15355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15356         }
15357     } else {
15358         /* Not used for offers from chess program */
15359     }
15360 }
15361
15362 void
15363 DeclineEvent ()
15364 {
15365     /* Decline a pending offer of any kind from opponent */
15366
15367     if (appData.icsActive) {
15368         SendToICS(ics_prefix);
15369         SendToICS("decline\n");
15370     } else if (cmailMsgLoaded) {
15371         if (currentMove == cmailOldMove &&
15372             commentList[cmailOldMove] != NULL &&
15373             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15374                    "Black offers a draw" : "White offers a draw")) {
15375 #ifdef NOTDEF
15376             AppendComment(cmailOldMove, "Draw declined", TRUE);
15377             DisplayComment(cmailOldMove - 1, "Draw declined");
15378 #endif /*NOTDEF*/
15379         } else {
15380             DisplayError(_("There is no pending offer on this move"), 0);
15381         }
15382     } else {
15383         /* Not used for offers from chess program */
15384     }
15385 }
15386
15387 void
15388 RematchEvent ()
15389 {
15390     /* Issue ICS rematch command */
15391     if (appData.icsActive) {
15392         SendToICS(ics_prefix);
15393         SendToICS("rematch\n");
15394     }
15395 }
15396
15397 void
15398 CallFlagEvent ()
15399 {
15400     /* Call your opponent's flag (claim a win on time) */
15401     if (appData.icsActive) {
15402         SendToICS(ics_prefix);
15403         SendToICS("flag\n");
15404     } else {
15405         switch (gameMode) {
15406           default:
15407             return;
15408           case MachinePlaysWhite:
15409             if (whiteFlag) {
15410                 if (blackFlag)
15411                   GameEnds(GameIsDrawn, "Both players ran out of time",
15412                            GE_PLAYER);
15413                 else
15414                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15415             } else {
15416                 DisplayError(_("Your opponent is not out of time"), 0);
15417             }
15418             break;
15419           case MachinePlaysBlack:
15420             if (blackFlag) {
15421                 if (whiteFlag)
15422                   GameEnds(GameIsDrawn, "Both players ran out of time",
15423                            GE_PLAYER);
15424                 else
15425                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15426             } else {
15427                 DisplayError(_("Your opponent is not out of time"), 0);
15428             }
15429             break;
15430         }
15431     }
15432 }
15433
15434 void
15435 ClockClick (int which)
15436 {       // [HGM] code moved to back-end from winboard.c
15437         if(which) { // black clock
15438           if (gameMode == EditPosition || gameMode == IcsExamining) {
15439             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15440             SetBlackToPlayEvent();
15441           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15442                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15443           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15444           } else if (shiftKey) {
15445             AdjustClock(which, -1);
15446           } else if (gameMode == IcsPlayingWhite ||
15447                      gameMode == MachinePlaysBlack) {
15448             CallFlagEvent();
15449           }
15450         } else { // white clock
15451           if (gameMode == EditPosition || gameMode == IcsExamining) {
15452             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15453             SetWhiteToPlayEvent();
15454           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15455                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15456           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15457           } else if (shiftKey) {
15458             AdjustClock(which, -1);
15459           } else if (gameMode == IcsPlayingBlack ||
15460                    gameMode == MachinePlaysWhite) {
15461             CallFlagEvent();
15462           }
15463         }
15464 }
15465
15466 void
15467 DrawEvent ()
15468 {
15469     /* Offer draw or accept pending draw offer from opponent */
15470
15471     if (appData.icsActive) {
15472         /* Note: tournament rules require draw offers to be
15473            made after you make your move but before you punch
15474            your clock.  Currently ICS doesn't let you do that;
15475            instead, you immediately punch your clock after making
15476            a move, but you can offer a draw at any time. */
15477
15478         SendToICS(ics_prefix);
15479         SendToICS("draw\n");
15480         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15481     } else if (cmailMsgLoaded) {
15482         if (currentMove == cmailOldMove &&
15483             commentList[cmailOldMove] != NULL &&
15484             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15485                    "Black offers a draw" : "White offers a draw")) {
15486             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15487             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15488         } else if (currentMove == cmailOldMove + 1) {
15489             char *offer = WhiteOnMove(cmailOldMove) ?
15490               "White offers a draw" : "Black offers a draw";
15491             AppendComment(currentMove, offer, TRUE);
15492             DisplayComment(currentMove - 1, offer);
15493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15494         } else {
15495             DisplayError(_("You must make your move before offering a draw"), 0);
15496             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15497         }
15498     } else if (first.offeredDraw) {
15499         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15500     } else {
15501         if (first.sendDrawOffers) {
15502             SendToProgram("draw\n", &first);
15503             userOfferedDraw = TRUE;
15504         }
15505     }
15506 }
15507
15508 void
15509 AdjournEvent ()
15510 {
15511     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15512
15513     if (appData.icsActive) {
15514         SendToICS(ics_prefix);
15515         SendToICS("adjourn\n");
15516     } else {
15517         /* Currently GNU Chess doesn't offer or accept Adjourns */
15518     }
15519 }
15520
15521
15522 void
15523 AbortEvent ()
15524 {
15525     /* Offer Abort or accept pending Abort offer from opponent */
15526
15527     if (appData.icsActive) {
15528         SendToICS(ics_prefix);
15529         SendToICS("abort\n");
15530     } else {
15531         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15532     }
15533 }
15534
15535 void
15536 ResignEvent ()
15537 {
15538     /* Resign.  You can do this even if it's not your turn. */
15539
15540     if (appData.icsActive) {
15541         SendToICS(ics_prefix);
15542         SendToICS("resign\n");
15543     } else {
15544         switch (gameMode) {
15545           case MachinePlaysWhite:
15546             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15547             break;
15548           case MachinePlaysBlack:
15549             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15550             break;
15551           case EditGame:
15552             if (cmailMsgLoaded) {
15553                 TruncateGame();
15554                 if (WhiteOnMove(cmailOldMove)) {
15555                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15556                 } else {
15557                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15558                 }
15559                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15560             }
15561             break;
15562           default:
15563             break;
15564         }
15565     }
15566 }
15567
15568
15569 void
15570 StopObservingEvent ()
15571 {
15572     /* Stop observing current games */
15573     SendToICS(ics_prefix);
15574     SendToICS("unobserve\n");
15575 }
15576
15577 void
15578 StopExaminingEvent ()
15579 {
15580     /* Stop observing current game */
15581     SendToICS(ics_prefix);
15582     SendToICS("unexamine\n");
15583 }
15584
15585 void
15586 ForwardInner (int target)
15587 {
15588     int limit; int oldSeekGraphUp = seekGraphUp;
15589
15590     if (appData.debugMode)
15591         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15592                 target, currentMove, forwardMostMove);
15593
15594     if (gameMode == EditPosition)
15595       return;
15596
15597     seekGraphUp = FALSE;
15598     MarkTargetSquares(1);
15599     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15600
15601     if (gameMode == PlayFromGameFile && !pausing)
15602       PauseEvent();
15603
15604     if (gameMode == IcsExamining && pausing)
15605       limit = pauseExamForwardMostMove;
15606     else
15607       limit = forwardMostMove;
15608
15609     if (target > limit) target = limit;
15610
15611     if (target > 0 && moveList[target - 1][0]) {
15612         int fromX, fromY, toX, toY;
15613         toX = moveList[target - 1][2] - AAA;
15614         toY = moveList[target - 1][3] - ONE;
15615         if (moveList[target - 1][1] == '@') {
15616             if (appData.highlightLastMove) {
15617                 SetHighlights(-1, -1, toX, toY);
15618             }
15619         } else {
15620             int viaX = moveList[target - 1][5] - AAA;
15621             int viaY = moveList[target - 1][6] - ONE;
15622             fromX = moveList[target - 1][0] - AAA;
15623             fromY = moveList[target - 1][1] - ONE;
15624             if (target == currentMove + 1) {
15625                 if(moveList[target - 1][4] == ';') { // multi-leg
15626                     ChessSquare piece = boards[currentMove][viaY][viaX];
15627                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15628                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15629                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15630                     boards[currentMove][viaY][viaX] = piece;
15631                 } else
15632                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15633             }
15634             if (appData.highlightLastMove) {
15635                 SetHighlights(fromX, fromY, toX, toY);
15636             }
15637         }
15638     }
15639     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15640         gameMode == Training || gameMode == PlayFromGameFile ||
15641         gameMode == AnalyzeFile) {
15642         while (currentMove < target) {
15643             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15644             SendMoveToProgram(currentMove++, &first);
15645         }
15646     } else {
15647         currentMove = target;
15648     }
15649
15650     if (gameMode == EditGame || gameMode == EndOfGame) {
15651         whiteTimeRemaining = timeRemaining[0][currentMove];
15652         blackTimeRemaining = timeRemaining[1][currentMove];
15653     }
15654     DisplayBothClocks();
15655     DisplayMove(currentMove - 1);
15656     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15657     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15658     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15659         DisplayComment(currentMove - 1, commentList[currentMove]);
15660     }
15661     ClearMap(); // [HGM] exclude: invalidate map
15662 }
15663
15664
15665 void
15666 ForwardEvent ()
15667 {
15668     if (gameMode == IcsExamining && !pausing) {
15669         SendToICS(ics_prefix);
15670         SendToICS("forward\n");
15671     } else {
15672         ForwardInner(currentMove + 1);
15673     }
15674 }
15675
15676 void
15677 ToEndEvent ()
15678 {
15679     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15680         /* to optimze, we temporarily turn off analysis mode while we feed
15681          * the remaining moves to the engine. Otherwise we get analysis output
15682          * after each move.
15683          */
15684         if (first.analysisSupport) {
15685           SendToProgram("exit\nforce\n", &first);
15686           first.analyzing = FALSE;
15687         }
15688     }
15689
15690     if (gameMode == IcsExamining && !pausing) {
15691         SendToICS(ics_prefix);
15692         SendToICS("forward 999999\n");
15693     } else {
15694         ForwardInner(forwardMostMove);
15695     }
15696
15697     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15698         /* we have fed all the moves, so reactivate analysis mode */
15699         SendToProgram("analyze\n", &first);
15700         first.analyzing = TRUE;
15701         /*first.maybeThinking = TRUE;*/
15702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15703     }
15704 }
15705
15706 void
15707 BackwardInner (int target)
15708 {
15709     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15710
15711     if (appData.debugMode)
15712         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15713                 target, currentMove, forwardMostMove);
15714
15715     if (gameMode == EditPosition) return;
15716     seekGraphUp = FALSE;
15717     MarkTargetSquares(1);
15718     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15719     if (currentMove <= backwardMostMove) {
15720         ClearHighlights();
15721         DrawPosition(full_redraw, boards[currentMove]);
15722         return;
15723     }
15724     if (gameMode == PlayFromGameFile && !pausing)
15725       PauseEvent();
15726
15727     if (moveList[target][0]) {
15728         int fromX, fromY, toX, toY;
15729         toX = moveList[target][2] - AAA;
15730         toY = moveList[target][3] - ONE;
15731         if (moveList[target][1] == '@') {
15732             if (appData.highlightLastMove) {
15733                 SetHighlights(-1, -1, toX, toY);
15734             }
15735         } else {
15736             fromX = moveList[target][0] - AAA;
15737             fromY = moveList[target][1] - ONE;
15738             if (target == currentMove - 1) {
15739                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15740             }
15741             if (appData.highlightLastMove) {
15742                 SetHighlights(fromX, fromY, toX, toY);
15743             }
15744         }
15745     }
15746     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15747         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15748         while (currentMove > target) {
15749             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15750                 // null move cannot be undone. Reload program with move history before it.
15751                 int i;
15752                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15753                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15754                 }
15755                 SendBoard(&first, i);
15756               if(second.analyzing) SendBoard(&second, i);
15757                 for(currentMove=i; currentMove<target; currentMove++) {
15758                     SendMoveToProgram(currentMove, &first);
15759                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15760                 }
15761                 break;
15762             }
15763             SendToBoth("undo\n");
15764             currentMove--;
15765         }
15766     } else {
15767         currentMove = target;
15768     }
15769
15770     if (gameMode == EditGame || gameMode == EndOfGame) {
15771         whiteTimeRemaining = timeRemaining[0][currentMove];
15772         blackTimeRemaining = timeRemaining[1][currentMove];
15773     }
15774     DisplayBothClocks();
15775     DisplayMove(currentMove - 1);
15776     DrawPosition(full_redraw, boards[currentMove]);
15777     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15778     // [HGM] PV info: routine tests if comment empty
15779     DisplayComment(currentMove - 1, commentList[currentMove]);
15780     ClearMap(); // [HGM] exclude: invalidate map
15781 }
15782
15783 void
15784 BackwardEvent ()
15785 {
15786     if (gameMode == IcsExamining && !pausing) {
15787         SendToICS(ics_prefix);
15788         SendToICS("backward\n");
15789     } else {
15790         BackwardInner(currentMove - 1);
15791     }
15792 }
15793
15794 void
15795 ToStartEvent ()
15796 {
15797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15798         /* to optimize, we temporarily turn off analysis mode while we undo
15799          * all the moves. Otherwise we get analysis output after each undo.
15800          */
15801         if (first.analysisSupport) {
15802           SendToProgram("exit\nforce\n", &first);
15803           first.analyzing = FALSE;
15804         }
15805     }
15806
15807     if (gameMode == IcsExamining && !pausing) {
15808         SendToICS(ics_prefix);
15809         SendToICS("backward 999999\n");
15810     } else {
15811         BackwardInner(backwardMostMove);
15812     }
15813
15814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15815         /* we have fed all the moves, so reactivate analysis mode */
15816         SendToProgram("analyze\n", &first);
15817         first.analyzing = TRUE;
15818         /*first.maybeThinking = TRUE;*/
15819         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15820     }
15821 }
15822
15823 void
15824 ToNrEvent (int to)
15825 {
15826   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15827   if (to >= forwardMostMove) to = forwardMostMove;
15828   if (to <= backwardMostMove) to = backwardMostMove;
15829   if (to < currentMove) {
15830     BackwardInner(to);
15831   } else {
15832     ForwardInner(to);
15833   }
15834 }
15835
15836 void
15837 RevertEvent (Boolean annotate)
15838 {
15839     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15840         return;
15841     }
15842     if (gameMode != IcsExamining) {
15843         DisplayError(_("You are not examining a game"), 0);
15844         return;
15845     }
15846     if (pausing) {
15847         DisplayError(_("You can't revert while pausing"), 0);
15848         return;
15849     }
15850     SendToICS(ics_prefix);
15851     SendToICS("revert\n");
15852 }
15853
15854 void
15855 RetractMoveEvent ()
15856 {
15857     switch (gameMode) {
15858       case MachinePlaysWhite:
15859       case MachinePlaysBlack:
15860         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15861             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15862             return;
15863         }
15864         if (forwardMostMove < 2) return;
15865         currentMove = forwardMostMove = forwardMostMove - 2;
15866         whiteTimeRemaining = timeRemaining[0][currentMove];
15867         blackTimeRemaining = timeRemaining[1][currentMove];
15868         DisplayBothClocks();
15869         DisplayMove(currentMove - 1);
15870         ClearHighlights();/*!! could figure this out*/
15871         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15872         SendToProgram("remove\n", &first);
15873         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15874         break;
15875
15876       case BeginningOfGame:
15877       default:
15878         break;
15879
15880       case IcsPlayingWhite:
15881       case IcsPlayingBlack:
15882         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15883             SendToICS(ics_prefix);
15884             SendToICS("takeback 2\n");
15885         } else {
15886             SendToICS(ics_prefix);
15887             SendToICS("takeback 1\n");
15888         }
15889         break;
15890     }
15891 }
15892
15893 void
15894 MoveNowEvent ()
15895 {
15896     ChessProgramState *cps;
15897
15898     switch (gameMode) {
15899       case MachinePlaysWhite:
15900         if (!WhiteOnMove(forwardMostMove)) {
15901             DisplayError(_("It is your turn"), 0);
15902             return;
15903         }
15904         cps = &first;
15905         break;
15906       case MachinePlaysBlack:
15907         if (WhiteOnMove(forwardMostMove)) {
15908             DisplayError(_("It is your turn"), 0);
15909             return;
15910         }
15911         cps = &first;
15912         break;
15913       case TwoMachinesPlay:
15914         if (WhiteOnMove(forwardMostMove) ==
15915             (first.twoMachinesColor[0] == 'w')) {
15916             cps = &first;
15917         } else {
15918             cps = &second;
15919         }
15920         break;
15921       case BeginningOfGame:
15922       default:
15923         return;
15924     }
15925     SendToProgram("?\n", cps);
15926 }
15927
15928 void
15929 TruncateGameEvent ()
15930 {
15931     EditGameEvent();
15932     if (gameMode != EditGame) return;
15933     TruncateGame();
15934 }
15935
15936 void
15937 TruncateGame ()
15938 {
15939     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15940     if (forwardMostMove > currentMove) {
15941         if (gameInfo.resultDetails != NULL) {
15942             free(gameInfo.resultDetails);
15943             gameInfo.resultDetails = NULL;
15944             gameInfo.result = GameUnfinished;
15945         }
15946         forwardMostMove = currentMove;
15947         HistorySet(parseList, backwardMostMove, forwardMostMove,
15948                    currentMove-1);
15949     }
15950 }
15951
15952 void
15953 HintEvent ()
15954 {
15955     if (appData.noChessProgram) return;
15956     switch (gameMode) {
15957       case MachinePlaysWhite:
15958         if (WhiteOnMove(forwardMostMove)) {
15959             DisplayError(_("Wait until your turn."), 0);
15960             return;
15961         }
15962         break;
15963       case BeginningOfGame:
15964       case MachinePlaysBlack:
15965         if (!WhiteOnMove(forwardMostMove)) {
15966             DisplayError(_("Wait until your turn."), 0);
15967             return;
15968         }
15969         break;
15970       default:
15971         DisplayError(_("No hint available"), 0);
15972         return;
15973     }
15974     SendToProgram("hint\n", &first);
15975     hintRequested = TRUE;
15976 }
15977
15978 int
15979 SaveSelected (FILE *g, int dummy, char *dummy2)
15980 {
15981     ListGame * lg = (ListGame *) gameList.head;
15982     int nItem, cnt=0;
15983     FILE *f;
15984
15985     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15986         DisplayError(_("Game list not loaded or empty"), 0);
15987         return 0;
15988     }
15989
15990     creatingBook = TRUE; // suppresses stuff during load game
15991
15992     /* Get list size */
15993     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15994         if(lg->position >= 0) { // selected?
15995             LoadGame(f, nItem, "", TRUE);
15996             SaveGamePGN2(g); // leaves g open
15997             cnt++; DoEvents();
15998         }
15999         lg = (ListGame *) lg->node.succ;
16000     }
16001
16002     fclose(g);
16003     creatingBook = FALSE;
16004
16005     return cnt;
16006 }
16007
16008 void
16009 CreateBookEvent ()
16010 {
16011     ListGame * lg = (ListGame *) gameList.head;
16012     FILE *f, *g;
16013     int nItem;
16014     static int secondTime = FALSE;
16015
16016     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16017         DisplayError(_("Game list not loaded or empty"), 0);
16018         return;
16019     }
16020
16021     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16022         fclose(g);
16023         secondTime++;
16024         DisplayNote(_("Book file exists! Try again for overwrite."));
16025         return;
16026     }
16027
16028     creatingBook = TRUE;
16029     secondTime = FALSE;
16030
16031     /* Get list size */
16032     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16033         if(lg->position >= 0) {
16034             LoadGame(f, nItem, "", TRUE);
16035             AddGameToBook(TRUE);
16036             DoEvents();
16037         }
16038         lg = (ListGame *) lg->node.succ;
16039     }
16040
16041     creatingBook = FALSE;
16042     FlushBook();
16043 }
16044
16045 void
16046 BookEvent ()
16047 {
16048     if (appData.noChessProgram) return;
16049     switch (gameMode) {
16050       case MachinePlaysWhite:
16051         if (WhiteOnMove(forwardMostMove)) {
16052             DisplayError(_("Wait until your turn."), 0);
16053             return;
16054         }
16055         break;
16056       case BeginningOfGame:
16057       case MachinePlaysBlack:
16058         if (!WhiteOnMove(forwardMostMove)) {
16059             DisplayError(_("Wait until your turn."), 0);
16060             return;
16061         }
16062         break;
16063       case EditPosition:
16064         EditPositionDone(TRUE);
16065         break;
16066       case TwoMachinesPlay:
16067         return;
16068       default:
16069         break;
16070     }
16071     SendToProgram("bk\n", &first);
16072     bookOutput[0] = NULLCHAR;
16073     bookRequested = TRUE;
16074 }
16075
16076 void
16077 AboutGameEvent ()
16078 {
16079     char *tags = PGNTags(&gameInfo);
16080     TagsPopUp(tags, CmailMsg());
16081     free(tags);
16082 }
16083
16084 /* end button procedures */
16085
16086 void
16087 PrintPosition (FILE *fp, int move)
16088 {
16089     int i, j;
16090
16091     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16092         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16093             char c = PieceToChar(boards[move][i][j]);
16094             fputc(c == 'x' ? '.' : c, fp);
16095             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16096         }
16097     }
16098     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16099       fprintf(fp, "white to play\n");
16100     else
16101       fprintf(fp, "black to play\n");
16102 }
16103
16104 void
16105 PrintOpponents (FILE *fp)
16106 {
16107     if (gameInfo.white != NULL) {
16108         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16109     } else {
16110         fprintf(fp, "\n");
16111     }
16112 }
16113
16114 /* Find last component of program's own name, using some heuristics */
16115 void
16116 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16117 {
16118     char *p, *q, c;
16119     int local = (strcmp(host, "localhost") == 0);
16120     while (!local && (p = strchr(prog, ';')) != NULL) {
16121         p++;
16122         while (*p == ' ') p++;
16123         prog = p;
16124     }
16125     if (*prog == '"' || *prog == '\'') {
16126         q = strchr(prog + 1, *prog);
16127     } else {
16128         q = strchr(prog, ' ');
16129     }
16130     if (q == NULL) q = prog + strlen(prog);
16131     p = q;
16132     while (p >= prog && *p != '/' && *p != '\\') p--;
16133     p++;
16134     if(p == prog && *p == '"') p++;
16135     c = *q; *q = 0;
16136     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16137     memcpy(buf, p, q - p);
16138     buf[q - p] = NULLCHAR;
16139     if (!local) {
16140         strcat(buf, "@");
16141         strcat(buf, host);
16142     }
16143 }
16144
16145 char *
16146 TimeControlTagValue ()
16147 {
16148     char buf[MSG_SIZ];
16149     if (!appData.clockMode) {
16150       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16151     } else if (movesPerSession > 0) {
16152       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16153     } else if (timeIncrement == 0) {
16154       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16155     } else {
16156       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16157     }
16158     return StrSave(buf);
16159 }
16160
16161 void
16162 SetGameInfo ()
16163 {
16164     /* This routine is used only for certain modes */
16165     VariantClass v = gameInfo.variant;
16166     ChessMove r = GameUnfinished;
16167     char *p = NULL;
16168
16169     if(keepInfo) return;
16170
16171     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16172         r = gameInfo.result;
16173         p = gameInfo.resultDetails;
16174         gameInfo.resultDetails = NULL;
16175     }
16176     ClearGameInfo(&gameInfo);
16177     gameInfo.variant = v;
16178
16179     switch (gameMode) {
16180       case MachinePlaysWhite:
16181         gameInfo.event = StrSave( appData.pgnEventHeader );
16182         gameInfo.site = StrSave(HostName());
16183         gameInfo.date = PGNDate();
16184         gameInfo.round = StrSave("-");
16185         gameInfo.white = StrSave(first.tidy);
16186         gameInfo.black = StrSave(UserName());
16187         gameInfo.timeControl = TimeControlTagValue();
16188         break;
16189
16190       case MachinePlaysBlack:
16191         gameInfo.event = StrSave( appData.pgnEventHeader );
16192         gameInfo.site = StrSave(HostName());
16193         gameInfo.date = PGNDate();
16194         gameInfo.round = StrSave("-");
16195         gameInfo.white = StrSave(UserName());
16196         gameInfo.black = StrSave(first.tidy);
16197         gameInfo.timeControl = TimeControlTagValue();
16198         break;
16199
16200       case TwoMachinesPlay:
16201         gameInfo.event = StrSave( appData.pgnEventHeader );
16202         gameInfo.site = StrSave(HostName());
16203         gameInfo.date = PGNDate();
16204         if (roundNr > 0) {
16205             char buf[MSG_SIZ];
16206             snprintf(buf, MSG_SIZ, "%d", roundNr);
16207             gameInfo.round = StrSave(buf);
16208         } else {
16209             gameInfo.round = StrSave("-");
16210         }
16211         if (first.twoMachinesColor[0] == 'w') {
16212             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16213             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16214         } else {
16215             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16216             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16217         }
16218         gameInfo.timeControl = TimeControlTagValue();
16219         break;
16220
16221       case EditGame:
16222         gameInfo.event = StrSave("Edited game");
16223         gameInfo.site = StrSave(HostName());
16224         gameInfo.date = PGNDate();
16225         gameInfo.round = StrSave("-");
16226         gameInfo.white = StrSave("-");
16227         gameInfo.black = StrSave("-");
16228         gameInfo.result = r;
16229         gameInfo.resultDetails = p;
16230         break;
16231
16232       case EditPosition:
16233         gameInfo.event = StrSave("Edited position");
16234         gameInfo.site = StrSave(HostName());
16235         gameInfo.date = PGNDate();
16236         gameInfo.round = StrSave("-");
16237         gameInfo.white = StrSave("-");
16238         gameInfo.black = StrSave("-");
16239         break;
16240
16241       case IcsPlayingWhite:
16242       case IcsPlayingBlack:
16243       case IcsObserving:
16244       case IcsExamining:
16245         break;
16246
16247       case PlayFromGameFile:
16248         gameInfo.event = StrSave("Game from non-PGN file");
16249         gameInfo.site = StrSave(HostName());
16250         gameInfo.date = PGNDate();
16251         gameInfo.round = StrSave("-");
16252         gameInfo.white = StrSave("?");
16253         gameInfo.black = StrSave("?");
16254         break;
16255
16256       default:
16257         break;
16258     }
16259 }
16260
16261 void
16262 ReplaceComment (int index, char *text)
16263 {
16264     int len;
16265     char *p;
16266     float score;
16267
16268     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16269        pvInfoList[index-1].depth == len &&
16270        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16271        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16272     while (*text == '\n') text++;
16273     len = strlen(text);
16274     while (len > 0 && text[len - 1] == '\n') len--;
16275
16276     if (commentList[index] != NULL)
16277       free(commentList[index]);
16278
16279     if (len == 0) {
16280         commentList[index] = NULL;
16281         return;
16282     }
16283   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16284       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16285       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16286     commentList[index] = (char *) malloc(len + 2);
16287     strncpy(commentList[index], text, len);
16288     commentList[index][len] = '\n';
16289     commentList[index][len + 1] = NULLCHAR;
16290   } else {
16291     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16292     char *p;
16293     commentList[index] = (char *) malloc(len + 7);
16294     safeStrCpy(commentList[index], "{\n", 3);
16295     safeStrCpy(commentList[index]+2, text, len+1);
16296     commentList[index][len+2] = NULLCHAR;
16297     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16298     strcat(commentList[index], "\n}\n");
16299   }
16300 }
16301
16302 void
16303 CrushCRs (char *text)
16304 {
16305   char *p = text;
16306   char *q = text;
16307   char ch;
16308
16309   do {
16310     ch = *p++;
16311     if (ch == '\r') continue;
16312     *q++ = ch;
16313   } while (ch != '\0');
16314 }
16315
16316 void
16317 AppendComment (int index, char *text, Boolean addBraces)
16318 /* addBraces  tells if we should add {} */
16319 {
16320     int oldlen, len;
16321     char *old;
16322
16323 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16324     if(addBraces == 3) addBraces = 0; else // force appending literally
16325     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16326
16327     CrushCRs(text);
16328     while (*text == '\n') text++;
16329     len = strlen(text);
16330     while (len > 0 && text[len - 1] == '\n') len--;
16331     text[len] = NULLCHAR;
16332
16333     if (len == 0) return;
16334
16335     if (commentList[index] != NULL) {
16336       Boolean addClosingBrace = addBraces;
16337         old = commentList[index];
16338         oldlen = strlen(old);
16339         while(commentList[index][oldlen-1] ==  '\n')
16340           commentList[index][--oldlen] = NULLCHAR;
16341         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16342         safeStrCpy(commentList[index], old, oldlen + len + 6);
16343         free(old);
16344         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16345         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16346           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16347           while (*text == '\n') { text++; len--; }
16348           commentList[index][--oldlen] = NULLCHAR;
16349       }
16350         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16351         else          strcat(commentList[index], "\n");
16352         strcat(commentList[index], text);
16353         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16354         else          strcat(commentList[index], "\n");
16355     } else {
16356         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16357         if(addBraces)
16358           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16359         else commentList[index][0] = NULLCHAR;
16360         strcat(commentList[index], text);
16361         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16362         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16363     }
16364 }
16365
16366 static char *
16367 FindStr (char * text, char * sub_text)
16368 {
16369     char * result = strstr( text, sub_text );
16370
16371     if( result != NULL ) {
16372         result += strlen( sub_text );
16373     }
16374
16375     return result;
16376 }
16377
16378 /* [AS] Try to extract PV info from PGN comment */
16379 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16380 char *
16381 GetInfoFromComment (int index, char * text)
16382 {
16383     char * sep = text, *p;
16384
16385     if( text != NULL && index > 0 ) {
16386         int score = 0;
16387         int depth = 0;
16388         int time = -1, sec = 0, deci;
16389         char * s_eval = FindStr( text, "[%eval " );
16390         char * s_emt = FindStr( text, "[%emt " );
16391 #if 0
16392         if( s_eval != NULL || s_emt != NULL ) {
16393 #else
16394         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16395 #endif
16396             /* New style */
16397             char delim;
16398
16399             if( s_eval != NULL ) {
16400                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16401                     return text;
16402                 }
16403
16404                 if( delim != ']' ) {
16405                     return text;
16406                 }
16407             }
16408
16409             if( s_emt != NULL ) {
16410             }
16411                 return text;
16412         }
16413         else {
16414             /* We expect something like: [+|-]nnn.nn/dd */
16415             int score_lo = 0;
16416
16417             if(*text != '{') return text; // [HGM] braces: must be normal comment
16418
16419             sep = strchr( text, '/' );
16420             if( sep == NULL || sep < (text+4) ) {
16421                 return text;
16422             }
16423
16424             p = text;
16425             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16426             if(p[1] == '(') { // comment starts with PV
16427                p = strchr(p, ')'); // locate end of PV
16428                if(p == NULL || sep < p+5) return text;
16429                // at this point we have something like "{(.*) +0.23/6 ..."
16430                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16431                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16432                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16433             }
16434             time = -1; sec = -1; deci = -1;
16435             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16436                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16437                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16438                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16439                 return text;
16440             }
16441
16442             if( score_lo < 0 || score_lo >= 100 ) {
16443                 return text;
16444             }
16445
16446             if(sec >= 0) time = 600*time + 10*sec; else
16447             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16448
16449             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16450
16451             /* [HGM] PV time: now locate end of PV info */
16452             while( *++sep >= '0' && *sep <= '9'); // strip depth
16453             if(time >= 0)
16454             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16455             if(sec >= 0)
16456             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16457             if(deci >= 0)
16458             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16459             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16460         }
16461
16462         if( depth <= 0 ) {
16463             return text;
16464         }
16465
16466         if( time < 0 ) {
16467             time = -1;
16468         }
16469
16470         pvInfoList[index-1].depth = depth;
16471         pvInfoList[index-1].score = score;
16472         pvInfoList[index-1].time  = 10*time; // centi-sec
16473         if(*sep == '}') *sep = 0; else *--sep = '{';
16474         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16475     }
16476     return sep;
16477 }
16478
16479 void
16480 SendToProgram (char *message, ChessProgramState *cps)
16481 {
16482     int count, outCount, error;
16483     char buf[MSG_SIZ];
16484
16485     if (cps->pr == NoProc) return;
16486     Attention(cps);
16487
16488     if (appData.debugMode) {
16489         TimeMark now;
16490         GetTimeMark(&now);
16491         fprintf(debugFP, "%ld >%-6s: %s",
16492                 SubtractTimeMarks(&now, &programStartTime),
16493                 cps->which, message);
16494         if(serverFP)
16495             fprintf(serverFP, "%ld >%-6s: %s",
16496                 SubtractTimeMarks(&now, &programStartTime),
16497                 cps->which, message), fflush(serverFP);
16498     }
16499
16500     count = strlen(message);
16501     outCount = OutputToProcess(cps->pr, message, count, &error);
16502     if (outCount < count && !exiting
16503                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16504       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16505       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16506         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16507             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16508                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16509                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16510                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16511             } else {
16512                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16513                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16514                 gameInfo.result = res;
16515             }
16516             gameInfo.resultDetails = StrSave(buf);
16517         }
16518         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16519         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16520     }
16521 }
16522
16523 void
16524 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16525 {
16526     char *end_str;
16527     char buf[MSG_SIZ];
16528     ChessProgramState *cps = (ChessProgramState *)closure;
16529
16530     if (isr != cps->isr) return; /* Killed intentionally */
16531     if (count <= 0) {
16532         if (count == 0) {
16533             RemoveInputSource(cps->isr);
16534             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16535                     _(cps->which), cps->program);
16536             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16537             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16538                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16539                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16540                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16541                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16542                 } else {
16543                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16544                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16545                     gameInfo.result = res;
16546                 }
16547                 gameInfo.resultDetails = StrSave(buf);
16548             }
16549             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16550             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16551         } else {
16552             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16553                     _(cps->which), cps->program);
16554             RemoveInputSource(cps->isr);
16555
16556             /* [AS] Program is misbehaving badly... kill it */
16557             if( count == -2 ) {
16558                 DestroyChildProcess( cps->pr, 9 );
16559                 cps->pr = NoProc;
16560             }
16561
16562             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16563         }
16564         return;
16565     }
16566
16567     if ((end_str = strchr(message, '\r')) != NULL)
16568       *end_str = NULLCHAR;
16569     if ((end_str = strchr(message, '\n')) != NULL)
16570       *end_str = NULLCHAR;
16571
16572     if (appData.debugMode) {
16573         TimeMark now; int print = 1;
16574         char *quote = ""; char c; int i;
16575
16576         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16577                 char start = message[0];
16578                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16579                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16580                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16581                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16582                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16583                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16584                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16585                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16586                    sscanf(message, "hint: %c", &c)!=1 &&
16587                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16588                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16589                     print = (appData.engineComments >= 2);
16590                 }
16591                 message[0] = start; // restore original message
16592         }
16593         if(print) {
16594                 GetTimeMark(&now);
16595                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16596                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16597                         quote,
16598                         message);
16599                 if(serverFP)
16600                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16601                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16602                         quote,
16603                         message), fflush(serverFP);
16604         }
16605     }
16606
16607     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16608     if (appData.icsEngineAnalyze) {
16609         if (strstr(message, "whisper") != NULL ||
16610              strstr(message, "kibitz") != NULL ||
16611             strstr(message, "tellics") != NULL) return;
16612     }
16613
16614     HandleMachineMove(message, cps);
16615 }
16616
16617
16618 void
16619 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16620 {
16621     char buf[MSG_SIZ];
16622     int seconds;
16623
16624     if( timeControl_2 > 0 ) {
16625         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16626             tc = timeControl_2;
16627         }
16628     }
16629     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16630     inc /= cps->timeOdds;
16631     st  /= cps->timeOdds;
16632
16633     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16634
16635     if (st > 0) {
16636       /* Set exact time per move, normally using st command */
16637       if (cps->stKludge) {
16638         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16639         seconds = st % 60;
16640         if (seconds == 0) {
16641           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16642         } else {
16643           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16644         }
16645       } else {
16646         snprintf(buf, MSG_SIZ, "st %d\n", st);
16647       }
16648     } else {
16649       /* Set conventional or incremental time control, using level command */
16650       if (seconds == 0) {
16651         /* Note old gnuchess bug -- minutes:seconds used to not work.
16652            Fixed in later versions, but still avoid :seconds
16653            when seconds is 0. */
16654         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16655       } else {
16656         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16657                  seconds, inc/1000.);
16658       }
16659     }
16660     SendToProgram(buf, cps);
16661
16662     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16663     /* Orthogonally, limit search to given depth */
16664     if (sd > 0) {
16665       if (cps->sdKludge) {
16666         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16667       } else {
16668         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16669       }
16670       SendToProgram(buf, cps);
16671     }
16672
16673     if(cps->nps >= 0) { /* [HGM] nps */
16674         if(cps->supportsNPS == FALSE)
16675           cps->nps = -1; // don't use if engine explicitly says not supported!
16676         else {
16677           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16678           SendToProgram(buf, cps);
16679         }
16680     }
16681 }
16682
16683 ChessProgramState *
16684 WhitePlayer ()
16685 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16686 {
16687     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16688        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16689         return &second;
16690     return &first;
16691 }
16692
16693 void
16694 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16695 {
16696     char message[MSG_SIZ];
16697     long time, otime;
16698
16699     /* Note: this routine must be called when the clocks are stopped
16700        or when they have *just* been set or switched; otherwise
16701        it will be off by the time since the current tick started.
16702     */
16703     if (machineWhite) {
16704         time = whiteTimeRemaining / 10;
16705         otime = blackTimeRemaining / 10;
16706     } else {
16707         time = blackTimeRemaining / 10;
16708         otime = whiteTimeRemaining / 10;
16709     }
16710     /* [HGM] translate opponent's time by time-odds factor */
16711     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16712
16713     if (time <= 0) time = 1;
16714     if (otime <= 0) otime = 1;
16715
16716     snprintf(message, MSG_SIZ, "time %ld\n", time);
16717     SendToProgram(message, cps);
16718
16719     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16720     SendToProgram(message, cps);
16721 }
16722
16723 char *
16724 EngineDefinedVariant (ChessProgramState *cps, int n)
16725 {   // return name of n-th unknown variant that engine supports
16726     static char buf[MSG_SIZ];
16727     char *p, *s = cps->variants;
16728     if(!s) return NULL;
16729     do { // parse string from variants feature
16730       VariantClass v;
16731         p = strchr(s, ',');
16732         if(p) *p = NULLCHAR;
16733       v = StringToVariant(s);
16734       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16735         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16736             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16737                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16738                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16739                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16740             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16741         }
16742         if(p) *p++ = ',';
16743         if(n < 0) return buf;
16744     } while(s = p);
16745     return NULL;
16746 }
16747
16748 int
16749 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16750 {
16751   char buf[MSG_SIZ];
16752   int len = strlen(name);
16753   int val;
16754
16755   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16756     (*p) += len + 1;
16757     sscanf(*p, "%d", &val);
16758     *loc = (val != 0);
16759     while (**p && **p != ' ')
16760       (*p)++;
16761     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16762     SendToProgram(buf, cps);
16763     return TRUE;
16764   }
16765   return FALSE;
16766 }
16767
16768 int
16769 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16770 {
16771   char buf[MSG_SIZ];
16772   int len = strlen(name);
16773   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16774     (*p) += len + 1;
16775     sscanf(*p, "%d", loc);
16776     while (**p && **p != ' ') (*p)++;
16777     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16778     SendToProgram(buf, cps);
16779     return TRUE;
16780   }
16781   return FALSE;
16782 }
16783
16784 int
16785 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16786 {
16787   char buf[MSG_SIZ];
16788   int len = strlen(name);
16789   if (strncmp((*p), name, len) == 0
16790       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16791     (*p) += len + 2;
16792     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16793     sscanf(*p, "%[^\"]", *loc);
16794     while (**p && **p != '\"') (*p)++;
16795     if (**p == '\"') (*p)++;
16796     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16797     SendToProgram(buf, cps);
16798     return TRUE;
16799   }
16800   return FALSE;
16801 }
16802
16803 int
16804 ParseOption (Option *opt, ChessProgramState *cps)
16805 // [HGM] options: process the string that defines an engine option, and determine
16806 // name, type, default value, and allowed value range
16807 {
16808         char *p, *q, buf[MSG_SIZ];
16809         int n, min = (-1)<<31, max = 1<<31, def;
16810
16811         if(p = strstr(opt->name, " -spin ")) {
16812             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16813             if(max < min) max = min; // enforce consistency
16814             if(def < min) def = min;
16815             if(def > max) def = max;
16816             opt->value = def;
16817             opt->min = min;
16818             opt->max = max;
16819             opt->type = Spin;
16820         } else if((p = strstr(opt->name, " -slider "))) {
16821             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16822             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16823             if(max < min) max = min; // enforce consistency
16824             if(def < min) def = min;
16825             if(def > max) def = max;
16826             opt->value = def;
16827             opt->min = min;
16828             opt->max = max;
16829             opt->type = Spin; // Slider;
16830         } else if((p = strstr(opt->name, " -string "))) {
16831             opt->textValue = p+9;
16832             opt->type = TextBox;
16833         } else if((p = strstr(opt->name, " -file "))) {
16834             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16835             opt->textValue = p+7;
16836             opt->type = FileName; // FileName;
16837         } else if((p = strstr(opt->name, " -path "))) {
16838             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16839             opt->textValue = p+7;
16840             opt->type = PathName; // PathName;
16841         } else if(p = strstr(opt->name, " -check ")) {
16842             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16843             opt->value = (def != 0);
16844             opt->type = CheckBox;
16845         } else if(p = strstr(opt->name, " -combo ")) {
16846             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16847             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16848             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16849             opt->value = n = 0;
16850             while(q = StrStr(q, " /// ")) {
16851                 n++; *q = 0;    // count choices, and null-terminate each of them
16852                 q += 5;
16853                 if(*q == '*') { // remember default, which is marked with * prefix
16854                     q++;
16855                     opt->value = n;
16856                 }
16857                 cps->comboList[cps->comboCnt++] = q;
16858             }
16859             cps->comboList[cps->comboCnt++] = NULL;
16860             opt->max = n + 1;
16861             opt->type = ComboBox;
16862         } else if(p = strstr(opt->name, " -button")) {
16863             opt->type = Button;
16864         } else if(p = strstr(opt->name, " -save")) {
16865             opt->type = SaveButton;
16866         } else return FALSE;
16867         *p = 0; // terminate option name
16868         // now look if the command-line options define a setting for this engine option.
16869         if(cps->optionSettings && cps->optionSettings[0])
16870             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16871         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16872           snprintf(buf, MSG_SIZ, "option %s", p);
16873                 if(p = strstr(buf, ",")) *p = 0;
16874                 if(q = strchr(buf, '=')) switch(opt->type) {
16875                     case ComboBox:
16876                         for(n=0; n<opt->max; n++)
16877                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16878                         break;
16879                     case TextBox:
16880                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16881                         break;
16882                     case Spin:
16883                     case CheckBox:
16884                         opt->value = atoi(q+1);
16885                     default:
16886                         break;
16887                 }
16888                 strcat(buf, "\n");
16889                 SendToProgram(buf, cps);
16890         }
16891         return TRUE;
16892 }
16893
16894 void
16895 FeatureDone (ChessProgramState *cps, int val)
16896 {
16897   DelayedEventCallback cb = GetDelayedEvent();
16898   if ((cb == InitBackEnd3 && cps == &first) ||
16899       (cb == SettingsMenuIfReady && cps == &second) ||
16900       (cb == LoadEngine) ||
16901       (cb == TwoMachinesEventIfReady)) {
16902     CancelDelayedEvent();
16903     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16904   }
16905   cps->initDone = val;
16906   if(val) cps->reload = FALSE;
16907 }
16908
16909 /* Parse feature command from engine */
16910 void
16911 ParseFeatures (char *args, ChessProgramState *cps)
16912 {
16913   char *p = args;
16914   char *q = NULL;
16915   int val;
16916   char buf[MSG_SIZ];
16917
16918   for (;;) {
16919     while (*p == ' ') p++;
16920     if (*p == NULLCHAR) return;
16921
16922     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16923     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16924     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16925     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16926     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16927     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16928     if (BoolFeature(&p, "reuse", &val, cps)) {
16929       /* Engine can disable reuse, but can't enable it if user said no */
16930       if (!val) cps->reuse = FALSE;
16931       continue;
16932     }
16933     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16934     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16935       if (gameMode == TwoMachinesPlay) {
16936         DisplayTwoMachinesTitle();
16937       } else {
16938         DisplayTitle("");
16939       }
16940       continue;
16941     }
16942     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16943     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16944     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16945     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16946     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16947     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16948     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16949     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16950     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16951     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16952     if (IntFeature(&p, "done", &val, cps)) {
16953       FeatureDone(cps, val);
16954       continue;
16955     }
16956     /* Added by Tord: */
16957     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16958     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16959     /* End of additions by Tord */
16960
16961     /* [HGM] added features: */
16962     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16963     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16964     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16965     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16966     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16967     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16968     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16969     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16970         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16971         FREE(cps->option[cps->nrOptions].name);
16972         cps->option[cps->nrOptions].name = q; q = NULL;
16973         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16974           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16975             SendToProgram(buf, cps);
16976             continue;
16977         }
16978         if(cps->nrOptions >= MAX_OPTIONS) {
16979             cps->nrOptions--;
16980             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16981             DisplayError(buf, 0);
16982         }
16983         continue;
16984     }
16985     /* End of additions by HGM */
16986
16987     /* unknown feature: complain and skip */
16988     q = p;
16989     while (*q && *q != '=') q++;
16990     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16991     SendToProgram(buf, cps);
16992     p = q;
16993     if (*p == '=') {
16994       p++;
16995       if (*p == '\"') {
16996         p++;
16997         while (*p && *p != '\"') p++;
16998         if (*p == '\"') p++;
16999       } else {
17000         while (*p && *p != ' ') p++;
17001       }
17002     }
17003   }
17004
17005 }
17006
17007 void
17008 PeriodicUpdatesEvent (int newState)
17009 {
17010     if (newState == appData.periodicUpdates)
17011       return;
17012
17013     appData.periodicUpdates=newState;
17014
17015     /* Display type changes, so update it now */
17016 //    DisplayAnalysis();
17017
17018     /* Get the ball rolling again... */
17019     if (newState) {
17020         AnalysisPeriodicEvent(1);
17021         StartAnalysisClock();
17022     }
17023 }
17024
17025 void
17026 PonderNextMoveEvent (int newState)
17027 {
17028     if (newState == appData.ponderNextMove) return;
17029     if (gameMode == EditPosition) EditPositionDone(TRUE);
17030     if (newState) {
17031         SendToProgram("hard\n", &first);
17032         if (gameMode == TwoMachinesPlay) {
17033             SendToProgram("hard\n", &second);
17034         }
17035     } else {
17036         SendToProgram("easy\n", &first);
17037         thinkOutput[0] = NULLCHAR;
17038         if (gameMode == TwoMachinesPlay) {
17039             SendToProgram("easy\n", &second);
17040         }
17041     }
17042     appData.ponderNextMove = newState;
17043 }
17044
17045 void
17046 NewSettingEvent (int option, int *feature, char *command, int value)
17047 {
17048     char buf[MSG_SIZ];
17049
17050     if (gameMode == EditPosition) EditPositionDone(TRUE);
17051     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17052     if(feature == NULL || *feature) SendToProgram(buf, &first);
17053     if (gameMode == TwoMachinesPlay) {
17054         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17055     }
17056 }
17057
17058 void
17059 ShowThinkingEvent ()
17060 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17061 {
17062     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17063     int newState = appData.showThinking
17064         // [HGM] thinking: other features now need thinking output as well
17065         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17066
17067     if (oldState == newState) return;
17068     oldState = newState;
17069     if (gameMode == EditPosition) EditPositionDone(TRUE);
17070     if (oldState) {
17071         SendToProgram("post\n", &first);
17072         if (gameMode == TwoMachinesPlay) {
17073             SendToProgram("post\n", &second);
17074         }
17075     } else {
17076         SendToProgram("nopost\n", &first);
17077         thinkOutput[0] = NULLCHAR;
17078         if (gameMode == TwoMachinesPlay) {
17079             SendToProgram("nopost\n", &second);
17080         }
17081     }
17082 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17083 }
17084
17085 void
17086 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17087 {
17088   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17089   if (pr == NoProc) return;
17090   AskQuestion(title, question, replyPrefix, pr);
17091 }
17092
17093 void
17094 TypeInEvent (char firstChar)
17095 {
17096     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17097         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17098         gameMode == AnalyzeMode || gameMode == EditGame ||
17099         gameMode == EditPosition || gameMode == IcsExamining ||
17100         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17101         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17102                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17103                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17104         gameMode == Training) PopUpMoveDialog(firstChar);
17105 }
17106
17107 void
17108 TypeInDoneEvent (char *move)
17109 {
17110         Board board;
17111         int n, fromX, fromY, toX, toY;
17112         char promoChar;
17113         ChessMove moveType;
17114
17115         // [HGM] FENedit
17116         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17117                 EditPositionPasteFEN(move);
17118                 return;
17119         }
17120         // [HGM] movenum: allow move number to be typed in any mode
17121         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17122           ToNrEvent(2*n-1);
17123           return;
17124         }
17125         // undocumented kludge: allow command-line option to be typed in!
17126         // (potentially fatal, and does not implement the effect of the option.)
17127         // should only be used for options that are values on which future decisions will be made,
17128         // and definitely not on options that would be used during initialization.
17129         if(strstr(move, "!!! -") == move) {
17130             ParseArgsFromString(move+4);
17131             return;
17132         }
17133
17134       if (gameMode != EditGame && currentMove != forwardMostMove &&
17135         gameMode != Training) {
17136         DisplayMoveError(_("Displayed move is not current"));
17137       } else {
17138         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17139           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17140         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17141         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17142           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17143           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17144         } else {
17145           DisplayMoveError(_("Could not parse move"));
17146         }
17147       }
17148 }
17149
17150 void
17151 DisplayMove (int moveNumber)
17152 {
17153     char message[MSG_SIZ];
17154     char res[MSG_SIZ];
17155     char cpThinkOutput[MSG_SIZ];
17156
17157     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17158
17159     if (moveNumber == forwardMostMove - 1 ||
17160         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17161
17162         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17163
17164         if (strchr(cpThinkOutput, '\n')) {
17165             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17166         }
17167     } else {
17168         *cpThinkOutput = NULLCHAR;
17169     }
17170
17171     /* [AS] Hide thinking from human user */
17172     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17173         *cpThinkOutput = NULLCHAR;
17174         if( thinkOutput[0] != NULLCHAR ) {
17175             int i;
17176
17177             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17178                 cpThinkOutput[i] = '.';
17179             }
17180             cpThinkOutput[i] = NULLCHAR;
17181             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17182         }
17183     }
17184
17185     if (moveNumber == forwardMostMove - 1 &&
17186         gameInfo.resultDetails != NULL) {
17187         if (gameInfo.resultDetails[0] == NULLCHAR) {
17188           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17189         } else {
17190           snprintf(res, MSG_SIZ, " {%s} %s",
17191                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17192         }
17193     } else {
17194         res[0] = NULLCHAR;
17195     }
17196
17197     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17198         DisplayMessage(res, cpThinkOutput);
17199     } else {
17200       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17201                 WhiteOnMove(moveNumber) ? " " : ".. ",
17202                 parseList[moveNumber], res);
17203         DisplayMessage(message, cpThinkOutput);
17204     }
17205 }
17206
17207 void
17208 DisplayComment (int moveNumber, char *text)
17209 {
17210     char title[MSG_SIZ];
17211
17212     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17213       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17214     } else {
17215       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17216               WhiteOnMove(moveNumber) ? " " : ".. ",
17217               parseList[moveNumber]);
17218     }
17219     if (text != NULL && (appData.autoDisplayComment || commentUp))
17220         CommentPopUp(title, text);
17221 }
17222
17223 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17224  * might be busy thinking or pondering.  It can be omitted if your
17225  * gnuchess is configured to stop thinking immediately on any user
17226  * input.  However, that gnuchess feature depends on the FIONREAD
17227  * ioctl, which does not work properly on some flavors of Unix.
17228  */
17229 void
17230 Attention (ChessProgramState *cps)
17231 {
17232 #if ATTENTION
17233     if (!cps->useSigint) return;
17234     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17235     switch (gameMode) {
17236       case MachinePlaysWhite:
17237       case MachinePlaysBlack:
17238       case TwoMachinesPlay:
17239       case IcsPlayingWhite:
17240       case IcsPlayingBlack:
17241       case AnalyzeMode:
17242       case AnalyzeFile:
17243         /* Skip if we know it isn't thinking */
17244         if (!cps->maybeThinking) return;
17245         if (appData.debugMode)
17246           fprintf(debugFP, "Interrupting %s\n", cps->which);
17247         InterruptChildProcess(cps->pr);
17248         cps->maybeThinking = FALSE;
17249         break;
17250       default:
17251         break;
17252     }
17253 #endif /*ATTENTION*/
17254 }
17255
17256 int
17257 CheckFlags ()
17258 {
17259     if (whiteTimeRemaining <= 0) {
17260         if (!whiteFlag) {
17261             whiteFlag = TRUE;
17262             if (appData.icsActive) {
17263                 if (appData.autoCallFlag &&
17264                     gameMode == IcsPlayingBlack && !blackFlag) {
17265                   SendToICS(ics_prefix);
17266                   SendToICS("flag\n");
17267                 }
17268             } else {
17269                 if (blackFlag) {
17270                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17271                 } else {
17272                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17273                     if (appData.autoCallFlag) {
17274                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17275                         return TRUE;
17276                     }
17277                 }
17278             }
17279         }
17280     }
17281     if (blackTimeRemaining <= 0) {
17282         if (!blackFlag) {
17283             blackFlag = TRUE;
17284             if (appData.icsActive) {
17285                 if (appData.autoCallFlag &&
17286                     gameMode == IcsPlayingWhite && !whiteFlag) {
17287                   SendToICS(ics_prefix);
17288                   SendToICS("flag\n");
17289                 }
17290             } else {
17291                 if (whiteFlag) {
17292                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17293                 } else {
17294                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17295                     if (appData.autoCallFlag) {
17296                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17297                         return TRUE;
17298                     }
17299                 }
17300             }
17301         }
17302     }
17303     return FALSE;
17304 }
17305
17306 void
17307 CheckTimeControl ()
17308 {
17309     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17310         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17311
17312     /*
17313      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17314      */
17315     if ( !WhiteOnMove(forwardMostMove) ) {
17316         /* White made time control */
17317         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17318         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17319         /* [HGM] time odds: correct new time quota for time odds! */
17320                                             / WhitePlayer()->timeOdds;
17321         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17322     } else {
17323         lastBlack -= blackTimeRemaining;
17324         /* Black made time control */
17325         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17326                                             / WhitePlayer()->other->timeOdds;
17327         lastWhite = whiteTimeRemaining;
17328     }
17329 }
17330
17331 void
17332 DisplayBothClocks ()
17333 {
17334     int wom = gameMode == EditPosition ?
17335       !blackPlaysFirst : WhiteOnMove(currentMove);
17336     DisplayWhiteClock(whiteTimeRemaining, wom);
17337     DisplayBlackClock(blackTimeRemaining, !wom);
17338 }
17339
17340
17341 /* Timekeeping seems to be a portability nightmare.  I think everyone
17342    has ftime(), but I'm really not sure, so I'm including some ifdefs
17343    to use other calls if you don't.  Clocks will be less accurate if
17344    you have neither ftime nor gettimeofday.
17345 */
17346
17347 /* VS 2008 requires the #include outside of the function */
17348 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17349 #include <sys/timeb.h>
17350 #endif
17351
17352 /* Get the current time as a TimeMark */
17353 void
17354 GetTimeMark (TimeMark *tm)
17355 {
17356 #if HAVE_GETTIMEOFDAY
17357
17358     struct timeval timeVal;
17359     struct timezone timeZone;
17360
17361     gettimeofday(&timeVal, &timeZone);
17362     tm->sec = (long) timeVal.tv_sec;
17363     tm->ms = (int) (timeVal.tv_usec / 1000L);
17364
17365 #else /*!HAVE_GETTIMEOFDAY*/
17366 #if HAVE_FTIME
17367
17368 // include <sys/timeb.h> / moved to just above start of function
17369     struct timeb timeB;
17370
17371     ftime(&timeB);
17372     tm->sec = (long) timeB.time;
17373     tm->ms = (int) timeB.millitm;
17374
17375 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17376     tm->sec = (long) time(NULL);
17377     tm->ms = 0;
17378 #endif
17379 #endif
17380 }
17381
17382 /* Return the difference in milliseconds between two
17383    time marks.  We assume the difference will fit in a long!
17384 */
17385 long
17386 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17387 {
17388     return 1000L*(tm2->sec - tm1->sec) +
17389            (long) (tm2->ms - tm1->ms);
17390 }
17391
17392
17393 /*
17394  * Code to manage the game clocks.
17395  *
17396  * In tournament play, black starts the clock and then white makes a move.
17397  * We give the human user a slight advantage if he is playing white---the
17398  * clocks don't run until he makes his first move, so it takes zero time.
17399  * Also, we don't account for network lag, so we could get out of sync
17400  * with GNU Chess's clock -- but then, referees are always right.
17401  */
17402
17403 static TimeMark tickStartTM;
17404 static long intendedTickLength;
17405
17406 long
17407 NextTickLength (long timeRemaining)
17408 {
17409     long nominalTickLength, nextTickLength;
17410
17411     if (timeRemaining > 0L && timeRemaining <= 10000L)
17412       nominalTickLength = 100L;
17413     else
17414       nominalTickLength = 1000L;
17415     nextTickLength = timeRemaining % nominalTickLength;
17416     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17417
17418     return nextTickLength;
17419 }
17420
17421 /* Adjust clock one minute up or down */
17422 void
17423 AdjustClock (Boolean which, int dir)
17424 {
17425     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17426     if(which) blackTimeRemaining += 60000*dir;
17427     else      whiteTimeRemaining += 60000*dir;
17428     DisplayBothClocks();
17429     adjustedClock = TRUE;
17430 }
17431
17432 /* Stop clocks and reset to a fresh time control */
17433 void
17434 ResetClocks ()
17435 {
17436     (void) StopClockTimer();
17437     if (appData.icsActive) {
17438         whiteTimeRemaining = blackTimeRemaining = 0;
17439     } else if (searchTime) {
17440         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17441         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17442     } else { /* [HGM] correct new time quote for time odds */
17443         whiteTC = blackTC = fullTimeControlString;
17444         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17445         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17446     }
17447     if (whiteFlag || blackFlag) {
17448         DisplayTitle("");
17449         whiteFlag = blackFlag = FALSE;
17450     }
17451     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17452     DisplayBothClocks();
17453     adjustedClock = FALSE;
17454 }
17455
17456 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17457
17458 /* Decrement running clock by amount of time that has passed */
17459 void
17460 DecrementClocks ()
17461 {
17462     long timeRemaining;
17463     long lastTickLength, fudge;
17464     TimeMark now;
17465
17466     if (!appData.clockMode) return;
17467     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17468
17469     GetTimeMark(&now);
17470
17471     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17472
17473     /* Fudge if we woke up a little too soon */
17474     fudge = intendedTickLength - lastTickLength;
17475     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17476
17477     if (WhiteOnMove(forwardMostMove)) {
17478         if(whiteNPS >= 0) lastTickLength = 0;
17479         timeRemaining = whiteTimeRemaining -= lastTickLength;
17480         if(timeRemaining < 0 && !appData.icsActive) {
17481             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17482             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17483                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17484                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17485             }
17486         }
17487         DisplayWhiteClock(whiteTimeRemaining - fudge,
17488                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17489     } else {
17490         if(blackNPS >= 0) lastTickLength = 0;
17491         timeRemaining = blackTimeRemaining -= lastTickLength;
17492         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17493             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17494             if(suddenDeath) {
17495                 blackStartMove = forwardMostMove;
17496                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17497             }
17498         }
17499         DisplayBlackClock(blackTimeRemaining - fudge,
17500                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17501     }
17502     if (CheckFlags()) return;
17503
17504     if(twoBoards) { // count down secondary board's clocks as well
17505         activePartnerTime -= lastTickLength;
17506         partnerUp = 1;
17507         if(activePartner == 'W')
17508             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17509         else
17510             DisplayBlackClock(activePartnerTime, TRUE);
17511         partnerUp = 0;
17512     }
17513
17514     tickStartTM = now;
17515     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17516     StartClockTimer(intendedTickLength);
17517
17518     /* if the time remaining has fallen below the alarm threshold, sound the
17519      * alarm. if the alarm has sounded and (due to a takeback or time control
17520      * with increment) the time remaining has increased to a level above the
17521      * threshold, reset the alarm so it can sound again.
17522      */
17523
17524     if (appData.icsActive && appData.icsAlarm) {
17525
17526         /* make sure we are dealing with the user's clock */
17527         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17528                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17529            )) return;
17530
17531         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17532             alarmSounded = FALSE;
17533         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17534             PlayAlarmSound();
17535             alarmSounded = TRUE;
17536         }
17537     }
17538 }
17539
17540
17541 /* A player has just moved, so stop the previously running
17542    clock and (if in clock mode) start the other one.
17543    We redisplay both clocks in case we're in ICS mode, because
17544    ICS gives us an update to both clocks after every move.
17545    Note that this routine is called *after* forwardMostMove
17546    is updated, so the last fractional tick must be subtracted
17547    from the color that is *not* on move now.
17548 */
17549 void
17550 SwitchClocks (int newMoveNr)
17551 {
17552     long lastTickLength;
17553     TimeMark now;
17554     int flagged = FALSE;
17555
17556     GetTimeMark(&now);
17557
17558     if (StopClockTimer() && appData.clockMode) {
17559         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17560         if (!WhiteOnMove(forwardMostMove)) {
17561             if(blackNPS >= 0) lastTickLength = 0;
17562             blackTimeRemaining -= lastTickLength;
17563            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17564 //         if(pvInfoList[forwardMostMove].time == -1)
17565                  pvInfoList[forwardMostMove].time =               // use GUI time
17566                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17567         } else {
17568            if(whiteNPS >= 0) lastTickLength = 0;
17569            whiteTimeRemaining -= lastTickLength;
17570            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17571 //         if(pvInfoList[forwardMostMove].time == -1)
17572                  pvInfoList[forwardMostMove].time =
17573                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17574         }
17575         flagged = CheckFlags();
17576     }
17577     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17578     CheckTimeControl();
17579
17580     if (flagged || !appData.clockMode) return;
17581
17582     switch (gameMode) {
17583       case MachinePlaysBlack:
17584       case MachinePlaysWhite:
17585       case BeginningOfGame:
17586         if (pausing) return;
17587         break;
17588
17589       case EditGame:
17590       case PlayFromGameFile:
17591       case IcsExamining:
17592         return;
17593
17594       default:
17595         break;
17596     }
17597
17598     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17599         if(WhiteOnMove(forwardMostMove))
17600              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17601         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17602     }
17603
17604     tickStartTM = now;
17605     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17606       whiteTimeRemaining : blackTimeRemaining);
17607     StartClockTimer(intendedTickLength);
17608 }
17609
17610
17611 /* Stop both clocks */
17612 void
17613 StopClocks ()
17614 {
17615     long lastTickLength;
17616     TimeMark now;
17617
17618     if (!StopClockTimer()) return;
17619     if (!appData.clockMode) return;
17620
17621     GetTimeMark(&now);
17622
17623     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17624     if (WhiteOnMove(forwardMostMove)) {
17625         if(whiteNPS >= 0) lastTickLength = 0;
17626         whiteTimeRemaining -= lastTickLength;
17627         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17628     } else {
17629         if(blackNPS >= 0) lastTickLength = 0;
17630         blackTimeRemaining -= lastTickLength;
17631         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17632     }
17633     CheckFlags();
17634 }
17635
17636 /* Start clock of player on move.  Time may have been reset, so
17637    if clock is already running, stop and restart it. */
17638 void
17639 StartClocks ()
17640 {
17641     (void) StopClockTimer(); /* in case it was running already */
17642     DisplayBothClocks();
17643     if (CheckFlags()) return;
17644
17645     if (!appData.clockMode) return;
17646     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17647
17648     GetTimeMark(&tickStartTM);
17649     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17650       whiteTimeRemaining : blackTimeRemaining);
17651
17652    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17653     whiteNPS = blackNPS = -1;
17654     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17655        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17656         whiteNPS = first.nps;
17657     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17658        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17659         blackNPS = first.nps;
17660     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17661         whiteNPS = second.nps;
17662     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17663         blackNPS = second.nps;
17664     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17665
17666     StartClockTimer(intendedTickLength);
17667 }
17668
17669 char *
17670 TimeString (long ms)
17671 {
17672     long second, minute, hour, day;
17673     char *sign = "";
17674     static char buf[32];
17675
17676     if (ms > 0 && ms <= 9900) {
17677       /* convert milliseconds to tenths, rounding up */
17678       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17679
17680       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17681       return buf;
17682     }
17683
17684     /* convert milliseconds to seconds, rounding up */
17685     /* use floating point to avoid strangeness of integer division
17686        with negative dividends on many machines */
17687     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17688
17689     if (second < 0) {
17690         sign = "-";
17691         second = -second;
17692     }
17693
17694     day = second / (60 * 60 * 24);
17695     second = second % (60 * 60 * 24);
17696     hour = second / (60 * 60);
17697     second = second % (60 * 60);
17698     minute = second / 60;
17699     second = second % 60;
17700
17701     if (day > 0)
17702       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17703               sign, day, hour, minute, second);
17704     else if (hour > 0)
17705       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17706     else
17707       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17708
17709     return buf;
17710 }
17711
17712
17713 /*
17714  * This is necessary because some C libraries aren't ANSI C compliant yet.
17715  */
17716 char *
17717 StrStr (char *string, char *match)
17718 {
17719     int i, length;
17720
17721     length = strlen(match);
17722
17723     for (i = strlen(string) - length; i >= 0; i--, string++)
17724       if (!strncmp(match, string, length))
17725         return string;
17726
17727     return NULL;
17728 }
17729
17730 char *
17731 StrCaseStr (char *string, char *match)
17732 {
17733     int i, j, length;
17734
17735     length = strlen(match);
17736
17737     for (i = strlen(string) - length; i >= 0; i--, string++) {
17738         for (j = 0; j < length; j++) {
17739             if (ToLower(match[j]) != ToLower(string[j]))
17740               break;
17741         }
17742         if (j == length) return string;
17743     }
17744
17745     return NULL;
17746 }
17747
17748 #ifndef _amigados
17749 int
17750 StrCaseCmp (char *s1, char *s2)
17751 {
17752     char c1, c2;
17753
17754     for (;;) {
17755         c1 = ToLower(*s1++);
17756         c2 = ToLower(*s2++);
17757         if (c1 > c2) return 1;
17758         if (c1 < c2) return -1;
17759         if (c1 == NULLCHAR) return 0;
17760     }
17761 }
17762
17763
17764 int
17765 ToLower (int c)
17766 {
17767     return isupper(c) ? tolower(c) : c;
17768 }
17769
17770
17771 int
17772 ToUpper (int c)
17773 {
17774     return islower(c) ? toupper(c) : c;
17775 }
17776 #endif /* !_amigados    */
17777
17778 char *
17779 StrSave (char *s)
17780 {
17781   char *ret;
17782
17783   if ((ret = (char *) malloc(strlen(s) + 1)))
17784     {
17785       safeStrCpy(ret, s, strlen(s)+1);
17786     }
17787   return ret;
17788 }
17789
17790 char *
17791 StrSavePtr (char *s, char **savePtr)
17792 {
17793     if (*savePtr) {
17794         free(*savePtr);
17795     }
17796     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17797       safeStrCpy(*savePtr, s, strlen(s)+1);
17798     }
17799     return(*savePtr);
17800 }
17801
17802 char *
17803 PGNDate ()
17804 {
17805     time_t clock;
17806     struct tm *tm;
17807     char buf[MSG_SIZ];
17808
17809     clock = time((time_t *)NULL);
17810     tm = localtime(&clock);
17811     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17812             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17813     return StrSave(buf);
17814 }
17815
17816
17817 char *
17818 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17819 {
17820     int i, j, fromX, fromY, toX, toY;
17821     int whiteToPlay;
17822     char buf[MSG_SIZ];
17823     char *p, *q;
17824     int emptycount;
17825     ChessSquare piece;
17826
17827     whiteToPlay = (gameMode == EditPosition) ?
17828       !blackPlaysFirst : (move % 2 == 0);
17829     p = buf;
17830
17831     /* Piece placement data */
17832     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17833         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17834         emptycount = 0;
17835         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17836             if (boards[move][i][j] == EmptySquare) {
17837                 emptycount++;
17838             } else { ChessSquare piece = boards[move][i][j];
17839                 if (emptycount > 0) {
17840                     if(emptycount<10) /* [HGM] can be >= 10 */
17841                         *p++ = '0' + emptycount;
17842                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17843                     emptycount = 0;
17844                 }
17845                 if(PieceToChar(piece) == '+') {
17846                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17847                     *p++ = '+';
17848                     piece = (ChessSquare)(CHUDEMOTED piece);
17849                 }
17850                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17851                 if(p[-1] == '~') {
17852                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17853                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17854                     *p++ = '~';
17855                 }
17856             }
17857         }
17858         if (emptycount > 0) {
17859             if(emptycount<10) /* [HGM] can be >= 10 */
17860                 *p++ = '0' + emptycount;
17861             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17862             emptycount = 0;
17863         }
17864         *p++ = '/';
17865     }
17866     *(p - 1) = ' ';
17867
17868     /* [HGM] print Crazyhouse or Shogi holdings */
17869     if( gameInfo.holdingsWidth ) {
17870         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17871         q = p;
17872         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17873             piece = boards[move][i][BOARD_WIDTH-1];
17874             if( piece != EmptySquare )
17875               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17876                   *p++ = PieceToChar(piece);
17877         }
17878         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17879             piece = boards[move][BOARD_HEIGHT-i-1][0];
17880             if( piece != EmptySquare )
17881               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17882                   *p++ = PieceToChar(piece);
17883         }
17884
17885         if( q == p ) *p++ = '-';
17886         *p++ = ']';
17887         *p++ = ' ';
17888     }
17889
17890     /* Active color */
17891     *p++ = whiteToPlay ? 'w' : 'b';
17892     *p++ = ' ';
17893
17894   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17895     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17896   } else {
17897   if(nrCastlingRights) {
17898      int handW=0, handB=0;
17899      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17900         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17901         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17902      }
17903      q = p;
17904      if(appData.fischerCastling) {
17905         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17906            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17907                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17908         } else {
17909        /* [HGM] write directly from rights */
17910            if(boards[move][CASTLING][2] != NoRights &&
17911               boards[move][CASTLING][0] != NoRights   )
17912                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17913            if(boards[move][CASTLING][2] != NoRights &&
17914               boards[move][CASTLING][1] != NoRights   )
17915                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17916         }
17917         if(handB) {
17918            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17919                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17920         } else {
17921            if(boards[move][CASTLING][5] != NoRights &&
17922               boards[move][CASTLING][3] != NoRights   )
17923                 *p++ = boards[move][CASTLING][3] + AAA;
17924            if(boards[move][CASTLING][5] != NoRights &&
17925               boards[move][CASTLING][4] != NoRights   )
17926                 *p++ = boards[move][CASTLING][4] + AAA;
17927         }
17928      } else {
17929
17930         /* [HGM] write true castling rights */
17931         if( nrCastlingRights == 6 ) {
17932             int q, k=0;
17933             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17934                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17935             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17936                  boards[move][CASTLING][2] != NoRights  );
17937             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17938                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17939                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17940                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17941             }
17942             if(q) *p++ = 'Q';
17943             k = 0;
17944             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17945                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17946             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17947                  boards[move][CASTLING][5] != NoRights  );
17948             if(handB) {
17949                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17950                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17951                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17952             }
17953             if(q) *p++ = 'q';
17954         }
17955      }
17956      if (q == p) *p++ = '-'; /* No castling rights */
17957      *p++ = ' ';
17958   }
17959
17960   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17961      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17962      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17963     /* En passant target square */
17964     if (move > backwardMostMove) {
17965         fromX = moveList[move - 1][0] - AAA;
17966         fromY = moveList[move - 1][1] - ONE;
17967         toX = moveList[move - 1][2] - AAA;
17968         toY = moveList[move - 1][3] - ONE;
17969         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17970             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17971             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17972             fromX == toX) {
17973             /* 2-square pawn move just happened */
17974             *p++ = toX + AAA;
17975             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17976         } else {
17977             *p++ = '-';
17978         }
17979     } else if(move == backwardMostMove) {
17980         // [HGM] perhaps we should always do it like this, and forget the above?
17981         if((signed char)boards[move][EP_STATUS] >= 0) {
17982             *p++ = boards[move][EP_STATUS] + AAA;
17983             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17984         } else {
17985             *p++ = '-';
17986         }
17987     } else {
17988         *p++ = '-';
17989     }
17990     *p++ = ' ';
17991   }
17992   }
17993
17994     if(moveCounts)
17995     {   int i = 0, j=move;
17996
17997         /* [HGM] find reversible plies */
17998         if (appData.debugMode) { int k;
17999             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18000             for(k=backwardMostMove; k<=forwardMostMove; k++)
18001                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18002
18003         }
18004
18005         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18006         if( j == backwardMostMove ) i += initialRulePlies;
18007         sprintf(p, "%d ", i);
18008         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18009
18010         /* Fullmove number */
18011         sprintf(p, "%d", (move / 2) + 1);
18012     } else *--p = NULLCHAR;
18013
18014     return StrSave(buf);
18015 }
18016
18017 Boolean
18018 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18019 {
18020     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18021     char *p, c;
18022     int emptycount, virgin[BOARD_FILES];
18023     ChessSquare piece;
18024
18025     p = fen;
18026
18027     /* Piece placement data */
18028     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18029         j = 0;
18030         for (;;) {
18031             if (*p == '/' || *p == ' ' || *p == '[' ) {
18032                 if(j > w) w = j;
18033                 emptycount = gameInfo.boardWidth - j;
18034                 while (emptycount--)
18035                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18036                 if (*p == '/') p++;
18037                 else if(autoSize) { // we stumbled unexpectedly into end of board
18038                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18039                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18040                     }
18041                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18042                 }
18043                 break;
18044 #if(BOARD_FILES >= 10)*0
18045             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18046                 p++; emptycount=10;
18047                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18048                 while (emptycount--)
18049                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18050 #endif
18051             } else if (*p == '*') {
18052                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18053             } else if (isdigit(*p)) {
18054                 emptycount = *p++ - '0';
18055                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18056                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18057                 while (emptycount--)
18058                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18059             } else if (*p == '<') {
18060                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18061                 else if (i != 0 || !shuffle) return FALSE;
18062                 p++;
18063             } else if (shuffle && *p == '>') {
18064                 p++; // for now ignore closing shuffle range, and assume rank-end
18065             } else if (*p == '?') {
18066                 if (j >= gameInfo.boardWidth) return FALSE;
18067                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18068                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18069             } else if (*p == '+' || isalpha(*p)) {
18070                 if (j >= gameInfo.boardWidth) return FALSE;
18071                 if(*p=='+') {
18072                     piece = CharToPiece(*++p);
18073                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18074                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18075                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18076                 } else piece = CharToPiece(*p++);
18077
18078                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18079                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18080                     piece = (ChessSquare) (PROMOTED piece);
18081                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18082                     p++;
18083                 }
18084                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18085                 if(piece == WhiteKing) wKingRank = i;
18086                 if(piece == BlackKing) bKingRank = i;
18087             } else {
18088                 return FALSE;
18089             }
18090         }
18091     }
18092     while (*p == '/' || *p == ' ') p++;
18093
18094     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18095
18096     /* [HGM] by default clear Crazyhouse holdings, if present */
18097     if(gameInfo.holdingsWidth) {
18098        for(i=0; i<BOARD_HEIGHT; i++) {
18099            board[i][0]             = EmptySquare; /* black holdings */
18100            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18101            board[i][1]             = (ChessSquare) 0; /* black counts */
18102            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18103        }
18104     }
18105
18106     /* [HGM] look for Crazyhouse holdings here */
18107     while(*p==' ') p++;
18108     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18109         int swap=0, wcnt=0, bcnt=0;
18110         if(*p == '[') p++;
18111         if(*p == '<') swap++, p++;
18112         if(*p == '-' ) p++; /* empty holdings */ else {
18113             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18114             /* if we would allow FEN reading to set board size, we would   */
18115             /* have to add holdings and shift the board read so far here   */
18116             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18117                 p++;
18118                 if((int) piece >= (int) BlackPawn ) {
18119                     i = (int)piece - (int)BlackPawn;
18120                     i = PieceToNumber((ChessSquare)i);
18121                     if( i >= gameInfo.holdingsSize ) return FALSE;
18122                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18123                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18124                     bcnt++;
18125                 } else {
18126                     i = (int)piece - (int)WhitePawn;
18127                     i = PieceToNumber((ChessSquare)i);
18128                     if( i >= gameInfo.holdingsSize ) return FALSE;
18129                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18130                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18131                     wcnt++;
18132                 }
18133             }
18134             if(subst) { // substitute back-rank question marks by holdings pieces
18135                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18136                     int k, m, n = bcnt + 1;
18137                     if(board[0][j] == ClearBoard) {
18138                         if(!wcnt) return FALSE;
18139                         n = rand() % wcnt;
18140                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18141                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18142                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18143                             break;
18144                         }
18145                     }
18146                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18147                         if(!bcnt) return FALSE;
18148                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18149                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18150                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18151                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18152                             break;
18153                         }
18154                     }
18155                 }
18156                 subst = 0;
18157             }
18158         }
18159         if(*p == ']') p++;
18160     }
18161
18162     if(subst) return FALSE; // substitution requested, but no holdings
18163
18164     while(*p == ' ') p++;
18165
18166     /* Active color */
18167     c = *p++;
18168     if(appData.colorNickNames) {
18169       if( c == appData.colorNickNames[0] ) c = 'w'; else
18170       if( c == appData.colorNickNames[1] ) c = 'b';
18171     }
18172     switch (c) {
18173       case 'w':
18174         *blackPlaysFirst = FALSE;
18175         break;
18176       case 'b':
18177         *blackPlaysFirst = TRUE;
18178         break;
18179       default:
18180         return FALSE;
18181     }
18182
18183     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18184     /* return the extra info in global variiables             */
18185
18186     while(*p==' ') p++;
18187
18188     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18189         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18190         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18191     }
18192
18193     /* set defaults in case FEN is incomplete */
18194     board[EP_STATUS] = EP_UNKNOWN;
18195     for(i=0; i<nrCastlingRights; i++ ) {
18196         board[CASTLING][i] =
18197             appData.fischerCastling ? NoRights : initialRights[i];
18198     }   /* assume possible unless obviously impossible */
18199     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18200     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18201     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18202                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18203     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18204     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18205     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18206                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18207     FENrulePlies = 0;
18208
18209     if(nrCastlingRights) {
18210       int fischer = 0;
18211       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18212       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18213           /* castling indicator present, so default becomes no castlings */
18214           for(i=0; i<nrCastlingRights; i++ ) {
18215                  board[CASTLING][i] = NoRights;
18216           }
18217       }
18218       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18219              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18220              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18221              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18222         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18223
18224         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18225             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18226             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18227         }
18228         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18229             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18230         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18231                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18232         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18233                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18234         switch(c) {
18235           case'K':
18236               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18237               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18238               board[CASTLING][2] = whiteKingFile;
18239               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18240               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18241               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18242               break;
18243           case'Q':
18244               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18245               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18246               board[CASTLING][2] = whiteKingFile;
18247               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18248               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18249               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18250               break;
18251           case'k':
18252               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18253               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18254               board[CASTLING][5] = blackKingFile;
18255               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18256               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18257               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18258               break;
18259           case'q':
18260               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18261               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18262               board[CASTLING][5] = blackKingFile;
18263               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18264               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18265               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18266           case '-':
18267               break;
18268           default: /* FRC castlings */
18269               if(c >= 'a') { /* black rights */
18270                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18271                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18272                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18273                   if(i == BOARD_RGHT) break;
18274                   board[CASTLING][5] = i;
18275                   c -= AAA;
18276                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18277                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18278                   if(c > i)
18279                       board[CASTLING][3] = c;
18280                   else
18281                       board[CASTLING][4] = c;
18282               } else { /* white rights */
18283                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18284                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18285                     if(board[0][i] == WhiteKing) break;
18286                   if(i == BOARD_RGHT) break;
18287                   board[CASTLING][2] = i;
18288                   c -= AAA - 'a' + 'A';
18289                   if(board[0][c] >= WhiteKing) break;
18290                   if(c > i)
18291                       board[CASTLING][0] = c;
18292                   else
18293                       board[CASTLING][1] = c;
18294               }
18295         }
18296       }
18297       for(i=0; i<nrCastlingRights; i++)
18298         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18299       if(gameInfo.variant == VariantSChess)
18300         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18301       if(fischer && shuffle) appData.fischerCastling = TRUE;
18302     if (appData.debugMode) {
18303         fprintf(debugFP, "FEN castling rights:");
18304         for(i=0; i<nrCastlingRights; i++)
18305         fprintf(debugFP, " %d", board[CASTLING][i]);
18306         fprintf(debugFP, "\n");
18307     }
18308
18309       while(*p==' ') p++;
18310     }
18311
18312     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18313
18314     /* read e.p. field in games that know e.p. capture */
18315     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18316        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18317        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18318       if(*p=='-') {
18319         p++; board[EP_STATUS] = EP_NONE;
18320       } else {
18321          char c = *p++ - AAA;
18322
18323          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18324          if(*p >= '0' && *p <='9') p++;
18325          board[EP_STATUS] = c;
18326       }
18327     }
18328
18329
18330     if(sscanf(p, "%d", &i) == 1) {
18331         FENrulePlies = i; /* 50-move ply counter */
18332         /* (The move number is still ignored)    */
18333     }
18334
18335     return TRUE;
18336 }
18337
18338 void
18339 EditPositionPasteFEN (char *fen)
18340 {
18341   if (fen != NULL) {
18342     Board initial_position;
18343
18344     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18345       DisplayError(_("Bad FEN position in clipboard"), 0);
18346       return ;
18347     } else {
18348       int savedBlackPlaysFirst = blackPlaysFirst;
18349       EditPositionEvent();
18350       blackPlaysFirst = savedBlackPlaysFirst;
18351       CopyBoard(boards[0], initial_position);
18352       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18353       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18354       DisplayBothClocks();
18355       DrawPosition(FALSE, boards[currentMove]);
18356     }
18357   }
18358 }
18359
18360 static char cseq[12] = "\\   ";
18361
18362 Boolean
18363 set_cont_sequence (char *new_seq)
18364 {
18365     int len;
18366     Boolean ret;
18367
18368     // handle bad attempts to set the sequence
18369         if (!new_seq)
18370                 return 0; // acceptable error - no debug
18371
18372     len = strlen(new_seq);
18373     ret = (len > 0) && (len < sizeof(cseq));
18374     if (ret)
18375       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18376     else if (appData.debugMode)
18377       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18378     return ret;
18379 }
18380
18381 /*
18382     reformat a source message so words don't cross the width boundary.  internal
18383     newlines are not removed.  returns the wrapped size (no null character unless
18384     included in source message).  If dest is NULL, only calculate the size required
18385     for the dest buffer.  lp argument indicats line position upon entry, and it's
18386     passed back upon exit.
18387 */
18388 int
18389 wrap (char *dest, char *src, int count, int width, int *lp)
18390 {
18391     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18392
18393     cseq_len = strlen(cseq);
18394     old_line = line = *lp;
18395     ansi = len = clen = 0;
18396
18397     for (i=0; i < count; i++)
18398     {
18399         if (src[i] == '\033')
18400             ansi = 1;
18401
18402         // if we hit the width, back up
18403         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18404         {
18405             // store i & len in case the word is too long
18406             old_i = i, old_len = len;
18407
18408             // find the end of the last word
18409             while (i && src[i] != ' ' && src[i] != '\n')
18410             {
18411                 i--;
18412                 len--;
18413             }
18414
18415             // word too long?  restore i & len before splitting it
18416             if ((old_i-i+clen) >= width)
18417             {
18418                 i = old_i;
18419                 len = old_len;
18420             }
18421
18422             // extra space?
18423             if (i && src[i-1] == ' ')
18424                 len--;
18425
18426             if (src[i] != ' ' && src[i] != '\n')
18427             {
18428                 i--;
18429                 if (len)
18430                     len--;
18431             }
18432
18433             // now append the newline and continuation sequence
18434             if (dest)
18435                 dest[len] = '\n';
18436             len++;
18437             if (dest)
18438                 strncpy(dest+len, cseq, cseq_len);
18439             len += cseq_len;
18440             line = cseq_len;
18441             clen = cseq_len;
18442             continue;
18443         }
18444
18445         if (dest)
18446             dest[len] = src[i];
18447         len++;
18448         if (!ansi)
18449             line++;
18450         if (src[i] == '\n')
18451             line = 0;
18452         if (src[i] == 'm')
18453             ansi = 0;
18454     }
18455     if (dest && appData.debugMode)
18456     {
18457         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18458             count, width, line, len, *lp);
18459         show_bytes(debugFP, src, count);
18460         fprintf(debugFP, "\ndest: ");
18461         show_bytes(debugFP, dest, len);
18462         fprintf(debugFP, "\n");
18463     }
18464     *lp = dest ? line : old_line;
18465
18466     return len;
18467 }
18468
18469 // [HGM] vari: routines for shelving variations
18470 Boolean modeRestore = FALSE;
18471
18472 void
18473 PushInner (int firstMove, int lastMove)
18474 {
18475         int i, j, nrMoves = lastMove - firstMove;
18476
18477         // push current tail of game on stack
18478         savedResult[storedGames] = gameInfo.result;
18479         savedDetails[storedGames] = gameInfo.resultDetails;
18480         gameInfo.resultDetails = NULL;
18481         savedFirst[storedGames] = firstMove;
18482         savedLast [storedGames] = lastMove;
18483         savedFramePtr[storedGames] = framePtr;
18484         framePtr -= nrMoves; // reserve space for the boards
18485         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18486             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18487             for(j=0; j<MOVE_LEN; j++)
18488                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18489             for(j=0; j<2*MOVE_LEN; j++)
18490                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18491             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18492             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18493             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18494             pvInfoList[firstMove+i-1].depth = 0;
18495             commentList[framePtr+i] = commentList[firstMove+i];
18496             commentList[firstMove+i] = NULL;
18497         }
18498
18499         storedGames++;
18500         forwardMostMove = firstMove; // truncate game so we can start variation
18501 }
18502
18503 void
18504 PushTail (int firstMove, int lastMove)
18505 {
18506         if(appData.icsActive) { // only in local mode
18507                 forwardMostMove = currentMove; // mimic old ICS behavior
18508                 return;
18509         }
18510         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18511
18512         PushInner(firstMove, lastMove);
18513         if(storedGames == 1) GreyRevert(FALSE);
18514         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18515 }
18516
18517 void
18518 PopInner (Boolean annotate)
18519 {
18520         int i, j, nrMoves;
18521         char buf[8000], moveBuf[20];
18522
18523         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18524         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18525         nrMoves = savedLast[storedGames] - currentMove;
18526         if(annotate) {
18527                 int cnt = 10;
18528                 if(!WhiteOnMove(currentMove))
18529                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18530                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18531                 for(i=currentMove; i<forwardMostMove; i++) {
18532                         if(WhiteOnMove(i))
18533                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18534                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18535                         strcat(buf, moveBuf);
18536                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18537                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18538                 }
18539                 strcat(buf, ")");
18540         }
18541         for(i=1; i<=nrMoves; i++) { // copy last variation back
18542             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18543             for(j=0; j<MOVE_LEN; j++)
18544                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18545             for(j=0; j<2*MOVE_LEN; j++)
18546                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18547             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18548             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18549             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18550             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18551             commentList[currentMove+i] = commentList[framePtr+i];
18552             commentList[framePtr+i] = NULL;
18553         }
18554         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18555         framePtr = savedFramePtr[storedGames];
18556         gameInfo.result = savedResult[storedGames];
18557         if(gameInfo.resultDetails != NULL) {
18558             free(gameInfo.resultDetails);
18559       }
18560         gameInfo.resultDetails = savedDetails[storedGames];
18561         forwardMostMove = currentMove + nrMoves;
18562 }
18563
18564 Boolean
18565 PopTail (Boolean annotate)
18566 {
18567         if(appData.icsActive) return FALSE; // only in local mode
18568         if(!storedGames) return FALSE; // sanity
18569         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18570
18571         PopInner(annotate);
18572         if(currentMove < forwardMostMove) ForwardEvent(); else
18573         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18574
18575         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18576         return TRUE;
18577 }
18578
18579 void
18580 CleanupTail ()
18581 {       // remove all shelved variations
18582         int i;
18583         for(i=0; i<storedGames; i++) {
18584             if(savedDetails[i])
18585                 free(savedDetails[i]);
18586             savedDetails[i] = NULL;
18587         }
18588         for(i=framePtr; i<MAX_MOVES; i++) {
18589                 if(commentList[i]) free(commentList[i]);
18590                 commentList[i] = NULL;
18591         }
18592         framePtr = MAX_MOVES-1;
18593         storedGames = 0;
18594 }
18595
18596 void
18597 LoadVariation (int index, char *text)
18598 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18599         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18600         int level = 0, move;
18601
18602         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18603         // first find outermost bracketing variation
18604         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18605             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18606                 if(*p == '{') wait = '}'; else
18607                 if(*p == '[') wait = ']'; else
18608                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18609                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18610             }
18611             if(*p == wait) wait = NULLCHAR; // closing ]} found
18612             p++;
18613         }
18614         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18615         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18616         end[1] = NULLCHAR; // clip off comment beyond variation
18617         ToNrEvent(currentMove-1);
18618         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18619         // kludge: use ParsePV() to append variation to game
18620         move = currentMove;
18621         ParsePV(start, TRUE, TRUE);
18622         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18623         ClearPremoveHighlights();
18624         CommentPopDown();
18625         ToNrEvent(currentMove+1);
18626 }
18627
18628 void
18629 LoadTheme ()
18630 {
18631     char *p, *q, buf[MSG_SIZ];
18632     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18633         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18634         ParseArgsFromString(buf);
18635         ActivateTheme(TRUE); // also redo colors
18636         return;
18637     }
18638     p = nickName;
18639     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18640     {
18641         int len;
18642         q = appData.themeNames;
18643         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18644       if(appData.useBitmaps) {
18645         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18646                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18647                 appData.liteBackTextureMode,
18648                 appData.darkBackTextureMode );
18649       } else {
18650         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18651                 Col2Text(2),   // lightSquareColor
18652                 Col2Text(3) ); // darkSquareColor
18653       }
18654       if(appData.useBorder) {
18655         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18656                 appData.border);
18657       } else {
18658         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18659       }
18660       if(appData.useFont) {
18661         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18662                 appData.renderPiecesWithFont,
18663                 appData.fontToPieceTable,
18664                 Col2Text(9),    // appData.fontBackColorWhite
18665                 Col2Text(10) ); // appData.fontForeColorBlack
18666       } else {
18667         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18668                 appData.pieceDirectory);
18669         if(!appData.pieceDirectory[0])
18670           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18671                 Col2Text(0),   // whitePieceColor
18672                 Col2Text(1) ); // blackPieceColor
18673       }
18674       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18675                 Col2Text(4),   // highlightSquareColor
18676                 Col2Text(5) ); // premoveHighlightColor
18677         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18678         if(insert != q) insert[-1] = NULLCHAR;
18679         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18680         if(q)   free(q);
18681     }
18682     ActivateTheme(FALSE);
18683 }