Fix click-click sweep-select
[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] == '@') goto drop; // illegal drop
5531         *fromX = currentMoveString[0] - AAA;
5532         *fromY = currentMoveString[1] - ONE;
5533         *toX = currentMoveString[2] - AAA;
5534         *toY = currentMoveString[3] - ONE;
5535         *promoChar = currentMoveString[4];
5536         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5537             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5538     if (appData.debugMode) {
5539         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5540     }
5541             *fromX = *fromY = *toX = *toY = 0;
5542             return FALSE;
5543         }
5544         if (appData.testLegality) {
5545           return (*moveType != IllegalMove);
5546         } else {
5547           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5548                          // [HGM] lion: if this is a double move we are less critical
5549                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5550         }
5551
5552       case WhiteDrop:
5553       case BlackDrop:
5554       drop:
5555         *fromX = *moveType == WhiteDrop ?
5556           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5557           (int) CharToPiece(ToLower(currentMoveString[0]));
5558         *fromY = DROP_RANK;
5559         *toX = currentMoveString[2] - AAA;
5560         *toY = currentMoveString[3] - ONE;
5561         *promoChar = NULLCHAR;
5562         return TRUE;
5563
5564       case AmbiguousMove:
5565       case ImpossibleMove:
5566       case EndOfFile:
5567       case ElapsedTime:
5568       case Comment:
5569       case PGNTag:
5570       case NAG:
5571       case WhiteWins:
5572       case BlackWins:
5573       case GameIsDrawn:
5574       default:
5575     if (appData.debugMode) {
5576         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5577     }
5578         /* bug? */
5579         *fromX = *fromY = *toX = *toY = 0;
5580         *promoChar = NULLCHAR;
5581         return FALSE;
5582     }
5583 }
5584
5585 Boolean pushed = FALSE;
5586 char *lastParseAttempt;
5587
5588 void
5589 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5590 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5591   int fromX, fromY, toX, toY; char promoChar;
5592   ChessMove moveType;
5593   Boolean valid;
5594   int nr = 0;
5595
5596   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5597   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5598     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5599     pushed = TRUE;
5600   }
5601   endPV = forwardMostMove;
5602   do {
5603     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5604     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5605     lastParseAttempt = pv;
5606     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5607     if(!valid && nr == 0 &&
5608        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5609         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5610         // Hande case where played move is different from leading PV move
5611         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5612         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5613         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5614         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5615           endPV += 2; // if position different, keep this
5616           moveList[endPV-1][0] = fromX + AAA;
5617           moveList[endPV-1][1] = fromY + ONE;
5618           moveList[endPV-1][2] = toX + AAA;
5619           moveList[endPV-1][3] = toY + ONE;
5620           parseList[endPV-1][0] = NULLCHAR;
5621           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5622         }
5623       }
5624     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5625     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5626     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5627     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5628         valid++; // allow comments in PV
5629         continue;
5630     }
5631     nr++;
5632     if(endPV+1 > framePtr) break; // no space, truncate
5633     if(!valid) break;
5634     endPV++;
5635     CopyBoard(boards[endPV], boards[endPV-1]);
5636     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5637     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5638     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5639     CoordsToAlgebraic(boards[endPV - 1],
5640                              PosFlags(endPV - 1),
5641                              fromY, fromX, toY, toX, promoChar,
5642                              parseList[endPV - 1]);
5643   } while(valid);
5644   if(atEnd == 2) return; // used hidden, for PV conversion
5645   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5646   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5647   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5648                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5649   DrawPosition(TRUE, boards[currentMove]);
5650 }
5651
5652 int
5653 MultiPV (ChessProgramState *cps)
5654 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5655         int i;
5656         for(i=0; i<cps->nrOptions; i++)
5657             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5658                 return i;
5659         return -1;
5660 }
5661
5662 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5663
5664 Boolean
5665 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5666 {
5667         int startPV, multi, lineStart, origIndex = index;
5668         char *p, buf2[MSG_SIZ];
5669         ChessProgramState *cps = (pane ? &second : &first);
5670
5671         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5672         lastX = x; lastY = y;
5673         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5674         lineStart = startPV = index;
5675         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5676         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5677         index = startPV;
5678         do{ while(buf[index] && buf[index] != '\n') index++;
5679         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5680         buf[index] = 0;
5681         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5682                 int n = cps->option[multi].value;
5683                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5684                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5685                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5686                 cps->option[multi].value = n;
5687                 *start = *end = 0;
5688                 return FALSE;
5689         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5690                 ExcludeClick(origIndex - lineStart);
5691                 return FALSE;
5692         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5693                 Collapse(origIndex - lineStart);
5694                 return FALSE;
5695         }
5696         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5697         *start = startPV; *end = index-1;
5698         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5699         return TRUE;
5700 }
5701
5702 char *
5703 PvToSAN (char *pv)
5704 {
5705         static char buf[10*MSG_SIZ];
5706         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5707         *buf = NULLCHAR;
5708         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5709         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5710         for(i = forwardMostMove; i<endPV; i++){
5711             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5712             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5713             k += strlen(buf+k);
5714         }
5715         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5716         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5717         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5718         endPV = savedEnd;
5719         return buf;
5720 }
5721
5722 Boolean
5723 LoadPV (int x, int y)
5724 { // called on right mouse click to load PV
5725   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5726   lastX = x; lastY = y;
5727   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5728   extendGame = FALSE;
5729   return TRUE;
5730 }
5731
5732 void
5733 UnLoadPV ()
5734 {
5735   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5736   if(endPV < 0) return;
5737   if(appData.autoCopyPV) CopyFENToClipboard();
5738   endPV = -1;
5739   if(extendGame && currentMove > forwardMostMove) {
5740         Boolean saveAnimate = appData.animate;
5741         if(pushed) {
5742             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5743                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5744             } else storedGames--; // abandon shelved tail of original game
5745         }
5746         pushed = FALSE;
5747         forwardMostMove = currentMove;
5748         currentMove = oldFMM;
5749         appData.animate = FALSE;
5750         ToNrEvent(forwardMostMove);
5751         appData.animate = saveAnimate;
5752   }
5753   currentMove = forwardMostMove;
5754   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5755   ClearPremoveHighlights();
5756   DrawPosition(TRUE, boards[currentMove]);
5757 }
5758
5759 void
5760 MovePV (int x, int y, int h)
5761 { // step through PV based on mouse coordinates (called on mouse move)
5762   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5763
5764   // we must somehow check if right button is still down (might be released off board!)
5765   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5766   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5767   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5768   if(!step) return;
5769   lastX = x; lastY = y;
5770
5771   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5772   if(endPV < 0) return;
5773   if(y < margin) step = 1; else
5774   if(y > h - margin) step = -1;
5775   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5776   currentMove += step;
5777   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5778   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5779                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5780   DrawPosition(FALSE, boards[currentMove]);
5781 }
5782
5783
5784 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5785 // All positions will have equal probability, but the current method will not provide a unique
5786 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5787 #define DARK 1
5788 #define LITE 2
5789 #define ANY 3
5790
5791 int squaresLeft[4];
5792 int piecesLeft[(int)BlackPawn];
5793 int seed, nrOfShuffles;
5794
5795 void
5796 GetPositionNumber ()
5797 {       // sets global variable seed
5798         int i;
5799
5800         seed = appData.defaultFrcPosition;
5801         if(seed < 0) { // randomize based on time for negative FRC position numbers
5802                 for(i=0; i<50; i++) seed += random();
5803                 seed = random() ^ random() >> 8 ^ random() << 8;
5804                 if(seed<0) seed = -seed;
5805         }
5806 }
5807
5808 int
5809 put (Board board, int pieceType, int rank, int n, int shade)
5810 // put the piece on the (n-1)-th empty squares of the given shade
5811 {
5812         int i;
5813
5814         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5815                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5816                         board[rank][i] = (ChessSquare) pieceType;
5817                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5818                         squaresLeft[ANY]--;
5819                         piecesLeft[pieceType]--;
5820                         return i;
5821                 }
5822         }
5823         return -1;
5824 }
5825
5826
5827 void
5828 AddOnePiece (Board board, int pieceType, int rank, int shade)
5829 // calculate where the next piece goes, (any empty square), and put it there
5830 {
5831         int i;
5832
5833         i = seed % squaresLeft[shade];
5834         nrOfShuffles *= squaresLeft[shade];
5835         seed /= squaresLeft[shade];
5836         put(board, pieceType, rank, i, shade);
5837 }
5838
5839 void
5840 AddTwoPieces (Board board, int pieceType, int rank)
5841 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5842 {
5843         int i, n=squaresLeft[ANY], j=n-1, k;
5844
5845         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5846         i = seed % k;  // pick one
5847         nrOfShuffles *= k;
5848         seed /= k;
5849         while(i >= j) i -= j--;
5850         j = n - 1 - j; i += j;
5851         put(board, pieceType, rank, j, ANY);
5852         put(board, pieceType, rank, i, ANY);
5853 }
5854
5855 void
5856 SetUpShuffle (Board board, int number)
5857 {
5858         int i, p, first=1;
5859
5860         GetPositionNumber(); nrOfShuffles = 1;
5861
5862         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5863         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5864         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5865
5866         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5867
5868         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5869             p = (int) board[0][i];
5870             if(p < (int) BlackPawn) piecesLeft[p] ++;
5871             board[0][i] = EmptySquare;
5872         }
5873
5874         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5875             // shuffles restricted to allow normal castling put KRR first
5876             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5877                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5878             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5879                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5880             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5881                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5882             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5883                 put(board, WhiteRook, 0, 0, ANY);
5884             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5885         }
5886
5887         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5888             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5889             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5890                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5891                 while(piecesLeft[p] >= 2) {
5892                     AddOnePiece(board, p, 0, LITE);
5893                     AddOnePiece(board, p, 0, DARK);
5894                 }
5895                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5896             }
5897
5898         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5899             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5900             // but we leave King and Rooks for last, to possibly obey FRC restriction
5901             if(p == (int)WhiteRook) continue;
5902             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5903             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5904         }
5905
5906         // now everything is placed, except perhaps King (Unicorn) and Rooks
5907
5908         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5909             // Last King gets castling rights
5910             while(piecesLeft[(int)WhiteUnicorn]) {
5911                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5912                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5913             }
5914
5915             while(piecesLeft[(int)WhiteKing]) {
5916                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5917                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5918             }
5919
5920
5921         } else {
5922             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5923             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5924         }
5925
5926         // Only Rooks can be left; simply place them all
5927         while(piecesLeft[(int)WhiteRook]) {
5928                 i = put(board, WhiteRook, 0, 0, ANY);
5929                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5930                         if(first) {
5931                                 first=0;
5932                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5933                         }
5934                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5935                 }
5936         }
5937         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5938             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5939         }
5940
5941         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5942 }
5943
5944 int
5945 SetCharTable (char *table, const char * map)
5946 /* [HGM] moved here from winboard.c because of its general usefulness */
5947 /*       Basically a safe strcpy that uses the last character as King */
5948 {
5949     int result = FALSE; int NrPieces;
5950
5951     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5952                     && NrPieces >= 12 && !(NrPieces&1)) {
5953         int i; /* [HGM] Accept even length from 12 to 34 */
5954
5955         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5956         for( i=0; i<NrPieces/2-1; i++ ) {
5957             table[i] = map[i];
5958             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5959         }
5960         table[(int) WhiteKing]  = map[NrPieces/2-1];
5961         table[(int) BlackKing]  = map[NrPieces-1];
5962
5963         result = TRUE;
5964     }
5965
5966     return result;
5967 }
5968
5969 void
5970 Prelude (Board board)
5971 {       // [HGM] superchess: random selection of exo-pieces
5972         int i, j, k; ChessSquare p;
5973         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5974
5975         GetPositionNumber(); // use FRC position number
5976
5977         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5978             SetCharTable(pieceToChar, appData.pieceToCharTable);
5979             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5980                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5981         }
5982
5983         j = seed%4;                 seed /= 4;
5984         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5985         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5986         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5987         j = seed%3 + (seed%3 >= j); seed /= 3;
5988         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5989         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5990         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5991         j = seed%3;                 seed /= 3;
5992         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5993         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5994         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5995         j = seed%2 + (seed%2 >= j); seed /= 2;
5996         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5997         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5998         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5999         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6000         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6001         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6002         put(board, exoPieces[0],    0, 0, ANY);
6003         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6004 }
6005
6006 void
6007 InitPosition (int redraw)
6008 {
6009     ChessSquare (* pieces)[BOARD_FILES];
6010     int i, j, pawnRow=1, pieceRows=1, overrule,
6011     oldx = gameInfo.boardWidth,
6012     oldy = gameInfo.boardHeight,
6013     oldh = gameInfo.holdingsWidth;
6014     static int oldv;
6015
6016     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6017
6018     /* [AS] Initialize pv info list [HGM] and game status */
6019     {
6020         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6021             pvInfoList[i].depth = 0;
6022             boards[i][EP_STATUS] = EP_NONE;
6023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6024         }
6025
6026         initialRulePlies = 0; /* 50-move counter start */
6027
6028         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6029         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6030     }
6031
6032
6033     /* [HGM] logic here is completely changed. In stead of full positions */
6034     /* the initialized data only consist of the two backranks. The switch */
6035     /* selects which one we will use, which is than copied to the Board   */
6036     /* initialPosition, which for the rest is initialized by Pawns and    */
6037     /* empty squares. This initial position is then copied to boards[0],  */
6038     /* possibly after shuffling, so that it remains available.            */
6039
6040     gameInfo.holdingsWidth = 0; /* default board sizes */
6041     gameInfo.boardWidth    = 8;
6042     gameInfo.boardHeight   = 8;
6043     gameInfo.holdingsSize  = 0;
6044     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6045     for(i=0; i<BOARD_FILES-6; i++)
6046       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6047     initialPosition[EP_STATUS] = EP_NONE;
6048     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6049     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6050     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6051          SetCharTable(pieceNickName, appData.pieceNickNames);
6052     else SetCharTable(pieceNickName, "............");
6053     pieces = FIDEArray;
6054
6055     switch (gameInfo.variant) {
6056     case VariantFischeRandom:
6057       shuffleOpenings = TRUE;
6058       appData.fischerCastling = TRUE;
6059     default:
6060       break;
6061     case VariantShatranj:
6062       pieces = ShatranjArray;
6063       nrCastlingRights = 0;
6064       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6065       break;
6066     case VariantMakruk:
6067       pieces = makrukArray;
6068       nrCastlingRights = 0;
6069       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6070       break;
6071     case VariantASEAN:
6072       pieces = aseanArray;
6073       nrCastlingRights = 0;
6074       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6075       break;
6076     case VariantTwoKings:
6077       pieces = twoKingsArray;
6078       break;
6079     case VariantGrand:
6080       pieces = GrandArray;
6081       nrCastlingRights = 0;
6082       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6083       gameInfo.boardWidth = 10;
6084       gameInfo.boardHeight = 10;
6085       gameInfo.holdingsSize = 7;
6086       break;
6087     case VariantCapaRandom:
6088       shuffleOpenings = TRUE;
6089       appData.fischerCastling = TRUE;
6090     case VariantCapablanca:
6091       pieces = CapablancaArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6094       break;
6095     case VariantGothic:
6096       pieces = GothicArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6099       break;
6100     case VariantSChess:
6101       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6102       gameInfo.holdingsSize = 7;
6103       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6104       break;
6105     case VariantJanus:
6106       pieces = JanusArray;
6107       gameInfo.boardWidth = 10;
6108       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6109       nrCastlingRights = 6;
6110         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6111         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6112         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6113         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6114         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6115         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6116       break;
6117     case VariantFalcon:
6118       pieces = FalconArray;
6119       gameInfo.boardWidth = 10;
6120       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6121       break;
6122     case VariantXiangqi:
6123       pieces = XiangqiArray;
6124       gameInfo.boardWidth  = 9;
6125       gameInfo.boardHeight = 10;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6128       break;
6129     case VariantShogi:
6130       pieces = ShogiArray;
6131       gameInfo.boardWidth  = 9;
6132       gameInfo.boardHeight = 9;
6133       gameInfo.holdingsSize = 7;
6134       nrCastlingRights = 0;
6135       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6136       break;
6137     case VariantChu:
6138       pieces = ChuArray; pieceRows = 3;
6139       gameInfo.boardWidth  = 12;
6140       gameInfo.boardHeight = 12;
6141       nrCastlingRights = 0;
6142       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6143                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6144       break;
6145     case VariantCourier:
6146       pieces = CourierArray;
6147       gameInfo.boardWidth  = 12;
6148       nrCastlingRights = 0;
6149       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6150       break;
6151     case VariantKnightmate:
6152       pieces = KnightmateArray;
6153       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6154       break;
6155     case VariantSpartan:
6156       pieces = SpartanArray;
6157       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6158       break;
6159     case VariantLion:
6160       pieces = lionArray;
6161       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6162       break;
6163     case VariantChuChess:
6164       pieces = ChuChessArray;
6165       gameInfo.boardWidth = 10;
6166       gameInfo.boardHeight = 10;
6167       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6168       break;
6169     case VariantFairy:
6170       pieces = fairyArray;
6171       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6172       break;
6173     case VariantGreat:
6174       pieces = GreatArray;
6175       gameInfo.boardWidth = 10;
6176       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6177       gameInfo.holdingsSize = 8;
6178       break;
6179     case VariantSuper:
6180       pieces = FIDEArray;
6181       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6182       gameInfo.holdingsSize = 8;
6183       startedFromSetupPosition = TRUE;
6184       break;
6185     case VariantCrazyhouse:
6186     case VariantBughouse:
6187       pieces = FIDEArray;
6188       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6189       gameInfo.holdingsSize = 5;
6190       break;
6191     case VariantWildCastle:
6192       pieces = FIDEArray;
6193       /* !!?shuffle with kings guaranteed to be on d or e file */
6194       shuffleOpenings = 1;
6195       break;
6196     case VariantNoCastle:
6197       pieces = FIDEArray;
6198       nrCastlingRights = 0;
6199       /* !!?unconstrained back-rank shuffle */
6200       shuffleOpenings = 1;
6201       break;
6202     }
6203
6204     overrule = 0;
6205     if(appData.NrFiles >= 0) {
6206         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6207         gameInfo.boardWidth = appData.NrFiles;
6208     }
6209     if(appData.NrRanks >= 0) {
6210         gameInfo.boardHeight = appData.NrRanks;
6211     }
6212     if(appData.holdingsSize >= 0) {
6213         i = appData.holdingsSize;
6214         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6215         gameInfo.holdingsSize = i;
6216     }
6217     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6218     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6219         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6220
6221     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6222     if(pawnRow < 1) pawnRow = 1;
6223     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6224        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6225     if(gameInfo.variant == VariantChu) pawnRow = 3;
6226
6227     /* User pieceToChar list overrules defaults */
6228     if(appData.pieceToCharTable != NULL)
6229         SetCharTable(pieceToChar, appData.pieceToCharTable);
6230
6231     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6232
6233         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6234             s = (ChessSquare) 0; /* account holding counts in guard band */
6235         for( i=0; i<BOARD_HEIGHT; i++ )
6236             initialPosition[i][j] = s;
6237
6238         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6239         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6240         initialPosition[pawnRow][j] = WhitePawn;
6241         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6242         if(gameInfo.variant == VariantXiangqi) {
6243             if(j&1) {
6244                 initialPosition[pawnRow][j] =
6245                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6246                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6247                    initialPosition[2][j] = WhiteCannon;
6248                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6249                 }
6250             }
6251         }
6252         if(gameInfo.variant == VariantChu) {
6253              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6254                initialPosition[pawnRow+1][j] = WhiteCobra,
6255                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6256              for(i=1; i<pieceRows; i++) {
6257                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6258                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6259              }
6260         }
6261         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6262             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6263                initialPosition[0][j] = WhiteRook;
6264                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6265             }
6266         }
6267         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6268     }
6269     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6270     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6271
6272             j=BOARD_LEFT+1;
6273             initialPosition[1][j] = WhiteBishop;
6274             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6275             j=BOARD_RGHT-2;
6276             initialPosition[1][j] = WhiteRook;
6277             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6278     }
6279
6280     if( nrCastlingRights == -1) {
6281         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6282         /*       This sets default castling rights from none to normal corners   */
6283         /* Variants with other castling rights must set them themselves above    */
6284         nrCastlingRights = 6;
6285
6286         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6287         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6288         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6289         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6290         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6291         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6292      }
6293
6294      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6295      if(gameInfo.variant == VariantGreat) { // promotion commoners
6296         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6297         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6298         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6299         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6300      }
6301      if( gameInfo.variant == VariantSChess ) {
6302       initialPosition[1][0] = BlackMarshall;
6303       initialPosition[2][0] = BlackAngel;
6304       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6305       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6306       initialPosition[1][1] = initialPosition[2][1] =
6307       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6308      }
6309   if (appData.debugMode) {
6310     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6311   }
6312     if(shuffleOpenings) {
6313         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6314         startedFromSetupPosition = TRUE;
6315     }
6316     if(startedFromPositionFile) {
6317       /* [HGM] loadPos: use PositionFile for every new game */
6318       CopyBoard(initialPosition, filePosition);
6319       for(i=0; i<nrCastlingRights; i++)
6320           initialRights[i] = filePosition[CASTLING][i];
6321       startedFromSetupPosition = TRUE;
6322     }
6323
6324     CopyBoard(boards[0], initialPosition);
6325
6326     if(oldx != gameInfo.boardWidth ||
6327        oldy != gameInfo.boardHeight ||
6328        oldv != gameInfo.variant ||
6329        oldh != gameInfo.holdingsWidth
6330                                          )
6331             InitDrawingSizes(-2 ,0);
6332
6333     oldv = gameInfo.variant;
6334     if (redraw)
6335       DrawPosition(TRUE, boards[currentMove]);
6336 }
6337
6338 void
6339 SendBoard (ChessProgramState *cps, int moveNum)
6340 {
6341     char message[MSG_SIZ];
6342
6343     if (cps->useSetboard) {
6344       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6345       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6346       SendToProgram(message, cps);
6347       free(fen);
6348
6349     } else {
6350       ChessSquare *bp;
6351       int i, j, left=0, right=BOARD_WIDTH;
6352       /* Kludge to set black to move, avoiding the troublesome and now
6353        * deprecated "black" command.
6354        */
6355       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6356         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6357
6358       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6359
6360       SendToProgram("edit\n", cps);
6361       SendToProgram("#\n", cps);
6362       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6363         bp = &boards[moveNum][i][left];
6364         for (j = left; j < right; j++, bp++) {
6365           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6366           if ((int) *bp < (int) BlackPawn) {
6367             if(j == BOARD_RGHT+1)
6368                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6369             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6370             if(message[0] == '+' || message[0] == '~') {
6371               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6372                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6373                         AAA + j, ONE + i);
6374             }
6375             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6376                 message[1] = BOARD_RGHT   - 1 - j + '1';
6377                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6378             }
6379             SendToProgram(message, cps);
6380           }
6381         }
6382       }
6383
6384       SendToProgram("c\n", cps);
6385       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6386         bp = &boards[moveNum][i][left];
6387         for (j = left; j < right; j++, bp++) {
6388           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6389           if (((int) *bp != (int) EmptySquare)
6390               && ((int) *bp >= (int) BlackPawn)) {
6391             if(j == BOARD_LEFT-2)
6392                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6393             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6394                     AAA + j, ONE + i);
6395             if(message[0] == '+' || message[0] == '~') {
6396               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6397                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6398                         AAA + j, ONE + i);
6399             }
6400             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6401                 message[1] = BOARD_RGHT   - 1 - j + '1';
6402                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6403             }
6404             SendToProgram(message, cps);
6405           }
6406         }
6407       }
6408
6409       SendToProgram(".\n", cps);
6410     }
6411     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6412 }
6413
6414 char exclusionHeader[MSG_SIZ];
6415 int exCnt, excludePtr;
6416 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6417 static Exclusion excluTab[200];
6418 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6419
6420 static void
6421 WriteMap (int s)
6422 {
6423     int j;
6424     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6425     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6426 }
6427
6428 static void
6429 ClearMap ()
6430 {
6431     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6432     excludePtr = 24; exCnt = 0;
6433     WriteMap(0);
6434 }
6435
6436 static void
6437 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6438 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6439     char buf[2*MOVE_LEN], *p;
6440     Exclusion *e = excluTab;
6441     int i;
6442     for(i=0; i<exCnt; i++)
6443         if(e[i].ff == fromX && e[i].fr == fromY &&
6444            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6445     if(i == exCnt) { // was not in exclude list; add it
6446         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6447         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6448             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6449             return; // abort
6450         }
6451         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6452         excludePtr++; e[i].mark = excludePtr++;
6453         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6454         exCnt++;
6455     }
6456     exclusionHeader[e[i].mark] = state;
6457 }
6458
6459 static int
6460 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6461 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6462     char buf[MSG_SIZ];
6463     int j, k;
6464     ChessMove moveType;
6465     if((signed char)promoChar == -1) { // kludge to indicate best move
6466         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6467             return 1; // if unparsable, abort
6468     }
6469     // update exclusion map (resolving toggle by consulting existing state)
6470     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6471     j = k%8; k >>= 3;
6472     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6473     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6474          excludeMap[k] |=   1<<j;
6475     else excludeMap[k] &= ~(1<<j);
6476     // update header
6477     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6478     // inform engine
6479     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6480     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6481     SendToBoth(buf);
6482     return (state == '+');
6483 }
6484
6485 static void
6486 ExcludeClick (int index)
6487 {
6488     int i, j;
6489     Exclusion *e = excluTab;
6490     if(index < 25) { // none, best or tail clicked
6491         if(index < 13) { // none: include all
6492             WriteMap(0); // clear map
6493             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6494             SendToBoth("include all\n"); // and inform engine
6495         } else if(index > 18) { // tail
6496             if(exclusionHeader[19] == '-') { // tail was excluded
6497                 SendToBoth("include all\n");
6498                 WriteMap(0); // clear map completely
6499                 // now re-exclude selected moves
6500                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6501                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6502             } else { // tail was included or in mixed state
6503                 SendToBoth("exclude all\n");
6504                 WriteMap(0xFF); // fill map completely
6505                 // now re-include selected moves
6506                 j = 0; // count them
6507                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6508                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6509                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6510             }
6511         } else { // best
6512             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6513         }
6514     } else {
6515         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6516             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6517             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6518             break;
6519         }
6520     }
6521 }
6522
6523 ChessSquare
6524 DefaultPromoChoice (int white)
6525 {
6526     ChessSquare result;
6527     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6528        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6529         result = WhiteFerz; // no choice
6530     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6531         result= WhiteKing; // in Suicide Q is the last thing we want
6532     else if(gameInfo.variant == VariantSpartan)
6533         result = white ? WhiteQueen : WhiteAngel;
6534     else result = WhiteQueen;
6535     if(!white) result = WHITE_TO_BLACK result;
6536     return result;
6537 }
6538
6539 static int autoQueen; // [HGM] oneclick
6540
6541 int
6542 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6543 {
6544     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6545     /* [HGM] add Shogi promotions */
6546     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6547     ChessSquare piece, partner;
6548     ChessMove moveType;
6549     Boolean premove;
6550
6551     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6552     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6553
6554     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6555       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6556         return FALSE;
6557
6558     piece = boards[currentMove][fromY][fromX];
6559     if(gameInfo.variant == VariantChu) {
6560         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6561         promotionZoneSize = BOARD_HEIGHT/3;
6562         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6563     } else if(gameInfo.variant == VariantShogi) {
6564         promotionZoneSize = BOARD_HEIGHT/3;
6565         highestPromotingPiece = (int)WhiteAlfil;
6566     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6567         promotionZoneSize = 3;
6568     }
6569
6570     // Treat Lance as Pawn when it is not representing Amazon or Lance
6571     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6572         if(piece == WhiteLance) piece = WhitePawn; else
6573         if(piece == BlackLance) piece = BlackPawn;
6574     }
6575
6576     // next weed out all moves that do not touch the promotion zone at all
6577     if((int)piece >= BlackPawn) {
6578         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6579              return FALSE;
6580         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6581         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6582     } else {
6583         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6584            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6585         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6586              return FALSE;
6587     }
6588
6589     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6590
6591     // weed out mandatory Shogi promotions
6592     if(gameInfo.variant == VariantShogi) {
6593         if(piece >= BlackPawn) {
6594             if(toY == 0 && piece == BlackPawn ||
6595                toY == 0 && piece == BlackQueen ||
6596                toY <= 1 && piece == BlackKnight) {
6597                 *promoChoice = '+';
6598                 return FALSE;
6599             }
6600         } else {
6601             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6602                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6603                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6604                 *promoChoice = '+';
6605                 return FALSE;
6606             }
6607         }
6608     }
6609
6610     // weed out obviously illegal Pawn moves
6611     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6612         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6613         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6614         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6615         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6616         // note we are not allowed to test for valid (non-)capture, due to premove
6617     }
6618
6619     // we either have a choice what to promote to, or (in Shogi) whether to promote
6620     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6621        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6622         ChessSquare p=BlackFerz;  // no choice
6623         while(p < EmptySquare) {  //but make sure we use piece that exists
6624             *promoChoice = PieceToChar(p++);
6625             if(*promoChoice != '.') break;
6626         }
6627         return FALSE;
6628     }
6629     // no sense asking what we must promote to if it is going to explode...
6630     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6631         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6632         return FALSE;
6633     }
6634     // give caller the default choice even if we will not make it
6635     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6636     partner = piece; // pieces can promote if the pieceToCharTable says so
6637     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6638     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6639     if(        sweepSelect && gameInfo.variant != VariantGreat
6640                            && gameInfo.variant != VariantGrand
6641                            && gameInfo.variant != VariantSuper) return FALSE;
6642     if(autoQueen) return FALSE; // predetermined
6643
6644     // suppress promotion popup on illegal moves that are not premoves
6645     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6646               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6647     if(appData.testLegality && !premove) {
6648         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6649                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6650         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6651         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6652             return FALSE;
6653     }
6654
6655     return TRUE;
6656 }
6657
6658 int
6659 InPalace (int row, int column)
6660 {   /* [HGM] for Xiangqi */
6661     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6662          column < (BOARD_WIDTH + 4)/2 &&
6663          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6664     return FALSE;
6665 }
6666
6667 int
6668 PieceForSquare (int x, int y)
6669 {
6670   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6671      return -1;
6672   else
6673      return boards[currentMove][y][x];
6674 }
6675
6676 int
6677 OKToStartUserMove (int x, int y)
6678 {
6679     ChessSquare from_piece;
6680     int white_piece;
6681
6682     if (matchMode) return FALSE;
6683     if (gameMode == EditPosition) return TRUE;
6684
6685     if (x >= 0 && y >= 0)
6686       from_piece = boards[currentMove][y][x];
6687     else
6688       from_piece = EmptySquare;
6689
6690     if (from_piece == EmptySquare) return FALSE;
6691
6692     white_piece = (int)from_piece >= (int)WhitePawn &&
6693       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6694
6695     switch (gameMode) {
6696       case AnalyzeFile:
6697       case TwoMachinesPlay:
6698       case EndOfGame:
6699         return FALSE;
6700
6701       case IcsObserving:
6702       case IcsIdle:
6703         return FALSE;
6704
6705       case MachinePlaysWhite:
6706       case IcsPlayingBlack:
6707         if (appData.zippyPlay) return FALSE;
6708         if (white_piece) {
6709             DisplayMoveError(_("You are playing Black"));
6710             return FALSE;
6711         }
6712         break;
6713
6714       case MachinePlaysBlack:
6715       case IcsPlayingWhite:
6716         if (appData.zippyPlay) return FALSE;
6717         if (!white_piece) {
6718             DisplayMoveError(_("You are playing White"));
6719             return FALSE;
6720         }
6721         break;
6722
6723       case PlayFromGameFile:
6724             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6725       case EditGame:
6726         if (!white_piece && WhiteOnMove(currentMove)) {
6727             DisplayMoveError(_("It is White's turn"));
6728             return FALSE;
6729         }
6730         if (white_piece && !WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is Black's turn"));
6732             return FALSE;
6733         }
6734         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6735             /* Editing correspondence game history */
6736             /* Could disallow this or prompt for confirmation */
6737             cmailOldMove = -1;
6738         }
6739         break;
6740
6741       case BeginningOfGame:
6742         if (appData.icsActive) return FALSE;
6743         if (!appData.noChessProgram) {
6744             if (!white_piece) {
6745                 DisplayMoveError(_("You are playing White"));
6746                 return FALSE;
6747             }
6748         }
6749         break;
6750
6751       case Training:
6752         if (!white_piece && WhiteOnMove(currentMove)) {
6753             DisplayMoveError(_("It is White's turn"));
6754             return FALSE;
6755         }
6756         if (white_piece && !WhiteOnMove(currentMove)) {
6757             DisplayMoveError(_("It is Black's turn"));
6758             return FALSE;
6759         }
6760         break;
6761
6762       default:
6763       case IcsExamining:
6764         break;
6765     }
6766     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6767         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6768         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6769         && gameMode != AnalyzeFile && gameMode != Training) {
6770         DisplayMoveError(_("Displayed position is not current"));
6771         return FALSE;
6772     }
6773     return TRUE;
6774 }
6775
6776 Boolean
6777 OnlyMove (int *x, int *y, Boolean captures)
6778 {
6779     DisambiguateClosure cl;
6780     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6781     switch(gameMode) {
6782       case MachinePlaysBlack:
6783       case IcsPlayingWhite:
6784       case BeginningOfGame:
6785         if(!WhiteOnMove(currentMove)) return FALSE;
6786         break;
6787       case MachinePlaysWhite:
6788       case IcsPlayingBlack:
6789         if(WhiteOnMove(currentMove)) return FALSE;
6790         break;
6791       case EditGame:
6792         break;
6793       default:
6794         return FALSE;
6795     }
6796     cl.pieceIn = EmptySquare;
6797     cl.rfIn = *y;
6798     cl.ffIn = *x;
6799     cl.rtIn = -1;
6800     cl.ftIn = -1;
6801     cl.promoCharIn = NULLCHAR;
6802     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6803     if( cl.kind == NormalMove ||
6804         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6805         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6806         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6807       fromX = cl.ff;
6808       fromY = cl.rf;
6809       *x = cl.ft;
6810       *y = cl.rt;
6811       return TRUE;
6812     }
6813     if(cl.kind != ImpossibleMove) return FALSE;
6814     cl.pieceIn = EmptySquare;
6815     cl.rfIn = -1;
6816     cl.ffIn = -1;
6817     cl.rtIn = *y;
6818     cl.ftIn = *x;
6819     cl.promoCharIn = NULLCHAR;
6820     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6821     if( cl.kind == NormalMove ||
6822         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6823         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6824         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6825       fromX = cl.ff;
6826       fromY = cl.rf;
6827       *x = cl.ft;
6828       *y = cl.rt;
6829       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6830       return TRUE;
6831     }
6832     return FALSE;
6833 }
6834
6835 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6836 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6837 int lastLoadGameUseList = FALSE;
6838 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6839 ChessMove lastLoadGameStart = EndOfFile;
6840 int doubleClick;
6841 Boolean addToBookFlag;
6842
6843 void
6844 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6845 {
6846     ChessMove moveType;
6847     ChessSquare pup;
6848     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6849
6850     /* Check if the user is playing in turn.  This is complicated because we
6851        let the user "pick up" a piece before it is his turn.  So the piece he
6852        tried to pick up may have been captured by the time he puts it down!
6853        Therefore we use the color the user is supposed to be playing in this
6854        test, not the color of the piece that is currently on the starting
6855        square---except in EditGame mode, where the user is playing both
6856        sides; fortunately there the capture race can't happen.  (It can
6857        now happen in IcsExamining mode, but that's just too bad.  The user
6858        will get a somewhat confusing message in that case.)
6859        */
6860
6861     switch (gameMode) {
6862       case AnalyzeFile:
6863       case TwoMachinesPlay:
6864       case EndOfGame:
6865       case IcsObserving:
6866       case IcsIdle:
6867         /* We switched into a game mode where moves are not accepted,
6868            perhaps while the mouse button was down. */
6869         return;
6870
6871       case MachinePlaysWhite:
6872         /* User is moving for Black */
6873         if (WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is White's turn"));
6875             return;
6876         }
6877         break;
6878
6879       case MachinePlaysBlack:
6880         /* User is moving for White */
6881         if (!WhiteOnMove(currentMove)) {
6882             DisplayMoveError(_("It is Black's turn"));
6883             return;
6884         }
6885         break;
6886
6887       case PlayFromGameFile:
6888             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6889       case EditGame:
6890       case IcsExamining:
6891       case BeginningOfGame:
6892       case AnalyzeMode:
6893       case Training:
6894         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6895         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6896             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6897             /* User is moving for Black */
6898             if (WhiteOnMove(currentMove)) {
6899                 DisplayMoveError(_("It is White's turn"));
6900                 return;
6901             }
6902         } else {
6903             /* User is moving for White */
6904             if (!WhiteOnMove(currentMove)) {
6905                 DisplayMoveError(_("It is Black's turn"));
6906                 return;
6907             }
6908         }
6909         break;
6910
6911       case IcsPlayingBlack:
6912         /* User is moving for Black */
6913         if (WhiteOnMove(currentMove)) {
6914             if (!appData.premove) {
6915                 DisplayMoveError(_("It is White's turn"));
6916             } else if (toX >= 0 && toY >= 0) {
6917                 premoveToX = toX;
6918                 premoveToY = toY;
6919                 premoveFromX = fromX;
6920                 premoveFromY = fromY;
6921                 premovePromoChar = promoChar;
6922                 gotPremove = 1;
6923                 if (appData.debugMode)
6924                     fprintf(debugFP, "Got premove: fromX %d,"
6925                             "fromY %d, toX %d, toY %d\n",
6926                             fromX, fromY, toX, toY);
6927             }
6928             return;
6929         }
6930         break;
6931
6932       case IcsPlayingWhite:
6933         /* User is moving for White */
6934         if (!WhiteOnMove(currentMove)) {
6935             if (!appData.premove) {
6936                 DisplayMoveError(_("It is Black's turn"));
6937             } else if (toX >= 0 && toY >= 0) {
6938                 premoveToX = toX;
6939                 premoveToY = toY;
6940                 premoveFromX = fromX;
6941                 premoveFromY = fromY;
6942                 premovePromoChar = promoChar;
6943                 gotPremove = 1;
6944                 if (appData.debugMode)
6945                     fprintf(debugFP, "Got premove: fromX %d,"
6946                             "fromY %d, toX %d, toY %d\n",
6947                             fromX, fromY, toX, toY);
6948             }
6949             return;
6950         }
6951         break;
6952
6953       default:
6954         break;
6955
6956       case EditPosition:
6957         /* EditPosition, empty square, or different color piece;
6958            click-click move is possible */
6959         if (toX == -2 || toY == -2) {
6960             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6961             DrawPosition(FALSE, boards[currentMove]);
6962             return;
6963         } else if (toX >= 0 && toY >= 0) {
6964             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6965                 ChessSquare q, p = boards[0][rf][ff];
6966                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6967                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6968                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6969                 if(PieceToChar(q) == '+') gatingPiece = p;
6970             }
6971             boards[0][toY][toX] = boards[0][fromY][fromX];
6972             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6973                 if(boards[0][fromY][0] != EmptySquare) {
6974                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6975                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6976                 }
6977             } else
6978             if(fromX == BOARD_RGHT+1) {
6979                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6980                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6981                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6982                 }
6983             } else
6984             boards[0][fromY][fromX] = gatingPiece;
6985             DrawPosition(FALSE, boards[currentMove]);
6986             return;
6987         }
6988         return;
6989     }
6990
6991     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6992     pup = boards[currentMove][toY][toX];
6993
6994     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6995     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6996          if( pup != EmptySquare ) return;
6997          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6998            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6999                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7000            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7001            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7002            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7003            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7004          fromY = DROP_RANK;
7005     }
7006
7007     /* [HGM] always test for legality, to get promotion info */
7008     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7009                                          fromY, fromX, toY, toX, promoChar);
7010
7011     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7012
7013     /* [HGM] but possibly ignore an IllegalMove result */
7014     if (appData.testLegality) {
7015         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7016             DisplayMoveError(_("Illegal move"));
7017             return;
7018         }
7019     }
7020
7021     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7022         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7023              ClearPremoveHighlights(); // was included
7024         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7025         return;
7026     }
7027
7028     if(addToBookFlag) { // adding moves to book
7029         char buf[MSG_SIZ], move[MSG_SIZ];
7030         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7031         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7032         AddBookMove(buf);
7033         addToBookFlag = FALSE;
7034         ClearHighlights();
7035         return;
7036     }
7037
7038     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7039 }
7040
7041 /* Common tail of UserMoveEvent and DropMenuEvent */
7042 int
7043 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7044 {
7045     char *bookHit = 0;
7046
7047     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7048         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7049         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7050         if(WhiteOnMove(currentMove)) {
7051             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7052         } else {
7053             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7054         }
7055     }
7056
7057     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7058        move type in caller when we know the move is a legal promotion */
7059     if(moveType == NormalMove && promoChar)
7060         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7061
7062     /* [HGM] <popupFix> The following if has been moved here from
7063        UserMoveEvent(). Because it seemed to belong here (why not allow
7064        piece drops in training games?), and because it can only be
7065        performed after it is known to what we promote. */
7066     if (gameMode == Training) {
7067       /* compare the move played on the board to the next move in the
7068        * game. If they match, display the move and the opponent's response.
7069        * If they don't match, display an error message.
7070        */
7071       int saveAnimate;
7072       Board testBoard;
7073       CopyBoard(testBoard, boards[currentMove]);
7074       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7075
7076       if (CompareBoards(testBoard, boards[currentMove+1])) {
7077         ForwardInner(currentMove+1);
7078
7079         /* Autoplay the opponent's response.
7080          * if appData.animate was TRUE when Training mode was entered,
7081          * the response will be animated.
7082          */
7083         saveAnimate = appData.animate;
7084         appData.animate = animateTraining;
7085         ForwardInner(currentMove+1);
7086         appData.animate = saveAnimate;
7087
7088         /* check for the end of the game */
7089         if (currentMove >= forwardMostMove) {
7090           gameMode = PlayFromGameFile;
7091           ModeHighlight();
7092           SetTrainingModeOff();
7093           DisplayInformation(_("End of game"));
7094         }
7095       } else {
7096         DisplayError(_("Incorrect move"), 0);
7097       }
7098       return 1;
7099     }
7100
7101   /* Ok, now we know that the move is good, so we can kill
7102      the previous line in Analysis Mode */
7103   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7104                                 && currentMove < forwardMostMove) {
7105     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7106     else forwardMostMove = currentMove;
7107   }
7108
7109   ClearMap();
7110
7111   /* If we need the chess program but it's dead, restart it */
7112   ResurrectChessProgram();
7113
7114   /* A user move restarts a paused game*/
7115   if (pausing)
7116     PauseEvent();
7117
7118   thinkOutput[0] = NULLCHAR;
7119
7120   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7121
7122   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7123     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7124     return 1;
7125   }
7126
7127   if (gameMode == BeginningOfGame) {
7128     if (appData.noChessProgram) {
7129       gameMode = EditGame;
7130       SetGameInfo();
7131     } else {
7132       char buf[MSG_SIZ];
7133       gameMode = MachinePlaysBlack;
7134       StartClocks();
7135       SetGameInfo();
7136       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7137       DisplayTitle(buf);
7138       if (first.sendName) {
7139         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7140         SendToProgram(buf, &first);
7141       }
7142       StartClocks();
7143     }
7144     ModeHighlight();
7145   }
7146
7147   /* Relay move to ICS or chess engine */
7148   if (appData.icsActive) {
7149     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7150         gameMode == IcsExamining) {
7151       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7152         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7153         SendToICS("draw ");
7154         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7155       }
7156       // also send plain move, in case ICS does not understand atomic claims
7157       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7158       ics_user_moved = 1;
7159     }
7160   } else {
7161     if (first.sendTime && (gameMode == BeginningOfGame ||
7162                            gameMode == MachinePlaysWhite ||
7163                            gameMode == MachinePlaysBlack)) {
7164       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7165     }
7166     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7167          // [HGM] book: if program might be playing, let it use book
7168         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7169         first.maybeThinking = TRUE;
7170     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7171         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7172         SendBoard(&first, currentMove+1);
7173         if(second.analyzing) {
7174             if(!second.useSetboard) SendToProgram("undo\n", &second);
7175             SendBoard(&second, currentMove+1);
7176         }
7177     } else {
7178         SendMoveToProgram(forwardMostMove-1, &first);
7179         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7180     }
7181     if (currentMove == cmailOldMove + 1) {
7182       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7183     }
7184   }
7185
7186   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7187
7188   switch (gameMode) {
7189   case EditGame:
7190     if(appData.testLegality)
7191     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7192     case MT_NONE:
7193     case MT_CHECK:
7194       break;
7195     case MT_CHECKMATE:
7196     case MT_STAINMATE:
7197       if (WhiteOnMove(currentMove)) {
7198         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7199       } else {
7200         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7201       }
7202       break;
7203     case MT_STALEMATE:
7204       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7205       break;
7206     }
7207     break;
7208
7209   case MachinePlaysBlack:
7210   case MachinePlaysWhite:
7211     /* disable certain menu options while machine is thinking */
7212     SetMachineThinkingEnables();
7213     break;
7214
7215   default:
7216     break;
7217   }
7218
7219   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7220   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7221
7222   if(bookHit) { // [HGM] book: simulate book reply
7223         static char bookMove[MSG_SIZ]; // a bit generous?
7224
7225         programStats.nodes = programStats.depth = programStats.time =
7226         programStats.score = programStats.got_only_move = 0;
7227         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7228
7229         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7230         strcat(bookMove, bookHit);
7231         HandleMachineMove(bookMove, &first);
7232   }
7233   return 1;
7234 }
7235
7236 void
7237 MarkByFEN(char *fen)
7238 {
7239         int r, f;
7240         if(!appData.markers || !appData.highlightDragging) return;
7241         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7242         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7243         while(*fen) {
7244             int s = 0;
7245             marker[r][f] = 0;
7246             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7247             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7248             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7249             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7250             if(*fen == 'T') marker[r][f++] = 0; else
7251             if(*fen == 'Y') marker[r][f++] = 1; else
7252             if(*fen == 'G') marker[r][f++] = 3; else
7253             if(*fen == 'B') marker[r][f++] = 4; else
7254             if(*fen == 'C') marker[r][f++] = 5; else
7255             if(*fen == 'M') marker[r][f++] = 6; else
7256             if(*fen == 'W') marker[r][f++] = 7; else
7257             if(*fen == 'D') marker[r][f++] = 8; else
7258             if(*fen == 'R') marker[r][f++] = 2; else {
7259                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7260               f += s; fen -= s>0;
7261             }
7262             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7263             if(r < 0) break;
7264             fen++;
7265         }
7266         DrawPosition(TRUE, NULL);
7267 }
7268
7269 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7270
7271 void
7272 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7273 {
7274     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7275     Markers *m = (Markers *) closure;
7276     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7277         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7278                          || kind == WhiteCapturesEnPassant
7279                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7280     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7281 }
7282
7283 static int hoverSavedValid;
7284
7285 void
7286 MarkTargetSquares (int clear)
7287 {
7288   int x, y, sum=0;
7289   if(clear) { // no reason to ever suppress clearing
7290     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7291     hoverSavedValid = 0;
7292     if(!sum) return; // nothing was cleared,no redraw needed
7293   } else {
7294     int capt = 0;
7295     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7296        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7297     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7298     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7299       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7300       if(capt)
7301       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7302     }
7303   }
7304   DrawPosition(FALSE, NULL);
7305 }
7306
7307 int
7308 Explode (Board board, int fromX, int fromY, int toX, int toY)
7309 {
7310     if(gameInfo.variant == VariantAtomic &&
7311        (board[toY][toX] != EmptySquare ||                     // capture?
7312         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7313                          board[fromY][fromX] == BlackPawn   )
7314       )) {
7315         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7316         return TRUE;
7317     }
7318     return FALSE;
7319 }
7320
7321 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7322
7323 int
7324 CanPromote (ChessSquare piece, int y)
7325 {
7326         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7327         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7328         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7329         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7330            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7331            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7332          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7333         return (piece == BlackPawn && y <= zone ||
7334                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7335                 piece == BlackLance && y <= zone ||
7336                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7337 }
7338
7339 void
7340 HoverEvent (int xPix, int yPix, int x, int y)
7341 {
7342         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7343         int r, f;
7344         if(!first.highlight) return;
7345         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7346         if(x == oldX && y == oldY) return; // only do something if we enter new square
7347         oldFromX = fromX; oldFromY = fromY;
7348         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7349           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7350             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7351           hoverSavedValid = 1;
7352         } else if(oldX != x || oldY != y) {
7353           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7354           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7355           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7356             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7357           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7358             char buf[MSG_SIZ];
7359             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7360             SendToProgram(buf, &first);
7361           }
7362           oldX = x; oldY = y;
7363 //        SetHighlights(fromX, fromY, x, y);
7364         }
7365 }
7366
7367 void ReportClick(char *action, int x, int y)
7368 {
7369         char buf[MSG_SIZ]; // Inform engine of what user does
7370         int r, f;
7371         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7372           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7373             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7374         if(!first.highlight || gameMode == EditPosition) return;
7375         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7376         SendToProgram(buf, &first);
7377 }
7378
7379 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7380
7381 void
7382 LeftClick (ClickType clickType, int xPix, int yPix)
7383 {
7384     int x, y;
7385     Boolean saveAnimate;
7386     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7387     char promoChoice = NULLCHAR;
7388     ChessSquare piece;
7389     static TimeMark lastClickTime, prevClickTime;
7390
7391     x = EventToSquare(xPix, BOARD_WIDTH);
7392     y = EventToSquare(yPix, BOARD_HEIGHT);
7393     if (!flipView && y >= 0) {
7394         y = BOARD_HEIGHT - 1 - y;
7395     }
7396     if (flipView && x >= 0) {
7397         x = BOARD_WIDTH - 1 - x;
7398     }
7399
7400     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7401         static int dummy;
7402         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7403         right = TRUE;
7404         return;
7405     }
7406
7407     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7408
7409     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7410
7411     if (clickType == Press) ErrorPopDown();
7412     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7413
7414     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7415         defaultPromoChoice = promoSweep;
7416         promoSweep = EmptySquare;   // terminate sweep
7417         promoDefaultAltered = TRUE;
7418         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7419     }
7420
7421     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7422         if(clickType == Release) return; // ignore upclick of click-click destination
7423         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7424         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7425         if(gameInfo.holdingsWidth &&
7426                 (WhiteOnMove(currentMove)
7427                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7428                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7429             // click in right holdings, for determining promotion piece
7430             ChessSquare p = boards[currentMove][y][x];
7431             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7432             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7433             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7434                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7435                 fromX = fromY = -1;
7436                 return;
7437             }
7438         }
7439         DrawPosition(FALSE, boards[currentMove]);
7440         return;
7441     }
7442
7443     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7444     if(clickType == Press
7445             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7446               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7447               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7448         return;
7449
7450     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7451         // could be static click on premove from-square: abort premove
7452         gotPremove = 0;
7453         ClearPremoveHighlights();
7454     }
7455
7456     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7457         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7458
7459     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7460         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7461                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7462         defaultPromoChoice = DefaultPromoChoice(side);
7463     }
7464
7465     autoQueen = appData.alwaysPromoteToQueen;
7466
7467     if (fromX == -1) {
7468       int originalY = y;
7469       gatingPiece = EmptySquare;
7470       if (clickType != Press) {
7471         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7472             DragPieceEnd(xPix, yPix); dragging = 0;
7473             DrawPosition(FALSE, NULL);
7474         }
7475         return;
7476       }
7477       doubleClick = FALSE;
7478       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7479         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7480       }
7481       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7482       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7483          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7484          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7485             /* First square */
7486             if (OKToStartUserMove(fromX, fromY)) {
7487                 second = 0;
7488                 ReportClick("lift", x, y);
7489                 MarkTargetSquares(0);
7490                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7491                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7492                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7493                     promoSweep = defaultPromoChoice;
7494                     selectFlag = 0; lastX = xPix; lastY = yPix;
7495                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7496                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7497                 }
7498                 if (appData.highlightDragging) {
7499                     SetHighlights(fromX, fromY, -1, -1);
7500                 } else {
7501                     ClearHighlights();
7502                 }
7503             } else fromX = fromY = -1;
7504             return;
7505         }
7506     }
7507 printf("to click %d,%d\n",x,y);
7508     /* fromX != -1 */
7509     if (clickType == Press && gameMode != EditPosition) {
7510         ChessSquare fromP;
7511         ChessSquare toP;
7512         int frc;
7513
7514         // ignore off-board to clicks
7515         if(y < 0 || x < 0) return;
7516
7517         /* Check if clicking again on the same color piece */
7518         fromP = boards[currentMove][fromY][fromX];
7519         toP = boards[currentMove][y][x];
7520         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7521         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7522             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7523            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7524              WhitePawn <= toP && toP <= WhiteKing &&
7525              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7526              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7527             (BlackPawn <= fromP && fromP <= BlackKing &&
7528              BlackPawn <= toP && toP <= BlackKing &&
7529              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7530              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7531             /* Clicked again on same color piece -- changed his mind */
7532             second = (x == fromX && y == fromY);
7533             killX = killY = -1;
7534             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7535                 second = FALSE; // first double-click rather than scond click
7536                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7537             }
7538             promoDefaultAltered = FALSE;
7539             MarkTargetSquares(1);
7540            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7541             if (appData.highlightDragging) {
7542                 SetHighlights(x, y, -1, -1);
7543             } else {
7544                 ClearHighlights();
7545             }
7546             if (OKToStartUserMove(x, y)) {
7547                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7548                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7549                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7550                  gatingPiece = boards[currentMove][fromY][fromX];
7551                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7552                 fromX = x;
7553                 fromY = y; dragging = 1;
7554                 ReportClick("lift", x, y);
7555                 MarkTargetSquares(0);
7556                 DragPieceBegin(xPix, yPix, FALSE);
7557                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7558                     promoSweep = defaultPromoChoice;
7559                     selectFlag = 0; lastX = xPix; lastY = yPix;
7560                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7561                 }
7562             }
7563            }
7564            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7565            second = FALSE;
7566         }
7567         // ignore clicks on holdings
7568         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7569     }
7570 printf("A type=%d\n",clickType);
7571
7572     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7573         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7574         return;
7575     }
7576
7577     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7578         DragPieceEnd(xPix, yPix); dragging = 0;
7579         if(clearFlag) {
7580             // a deferred attempt to click-click move an empty square on top of a piece
7581             boards[currentMove][y][x] = EmptySquare;
7582             ClearHighlights();
7583             DrawPosition(FALSE, boards[currentMove]);
7584             fromX = fromY = -1; clearFlag = 0;
7585             return;
7586         }
7587         if (appData.animateDragging) {
7588             /* Undo animation damage if any */
7589             DrawPosition(FALSE, NULL);
7590         }
7591         if (second) {
7592             /* Second up/down in same square; just abort move */
7593             second = 0;
7594             fromX = fromY = -1;
7595             gatingPiece = EmptySquare;
7596             MarkTargetSquares(1);
7597             ClearHighlights();
7598             gotPremove = 0;
7599             ClearPremoveHighlights();
7600         } else {
7601             /* First upclick in same square; start click-click mode */
7602             SetHighlights(x, y, -1, -1);
7603         }
7604         return;
7605     }
7606
7607     clearFlag = 0;
7608 printf("B\n");
7609     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7610        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7611         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7612         DisplayMessage(_("only marked squares are legal"),"");
7613         DrawPosition(TRUE, NULL);
7614         return; // ignore to-click
7615     }
7616 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7617     /* we now have a different from- and (possibly off-board) to-square */
7618     /* Completed move */
7619     if(!sweepSelecting) {
7620         toX = x;
7621         toY = y;
7622     }
7623
7624     piece = boards[currentMove][fromY][fromX];
7625
7626     saveAnimate = appData.animate;
7627     if (clickType == Press) {
7628         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7629         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7630             // must be Edit Position mode with empty-square selected
7631             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7632             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7633             return;
7634         }
7635         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7636             return;
7637         }
7638         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7639             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7640         } else
7641         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7642         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7643           if(appData.sweepSelect) {
7644             promoSweep = defaultPromoChoice;
7645             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7646             selectFlag = 0; lastX = xPix; lastY = yPix;
7647             Sweep(0); // Pawn that is going to promote: preview promotion piece
7648             sweepSelecting = 1;
7649             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7650             MarkTargetSquares(1);
7651           }
7652           return; // promo popup appears on up-click
7653         }
7654         /* Finish clickclick move */
7655         if (appData.animate || appData.highlightLastMove) {
7656             SetHighlights(fromX, fromY, toX, toY);
7657         } else {
7658             ClearHighlights();
7659         }
7660     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7661         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7662         if (appData.animate || appData.highlightLastMove) {
7663             SetHighlights(fromX, fromY, toX, toY);
7664         } else {
7665             ClearHighlights();
7666         }
7667     } else {
7668 #if 0
7669 // [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
7670         /* Finish drag move */
7671         if (appData.highlightLastMove) {
7672             SetHighlights(fromX, fromY, toX, toY);
7673         } else {
7674             ClearHighlights();
7675         }
7676 #endif
7677         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7678         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7679           dragging *= 2;            // flag button-less dragging if we are dragging
7680           MarkTargetSquares(1);
7681           if(x == killX && y == killY) killX = killY = -1; else {
7682             killX = x; killY = y;     //remeber this square as intermediate
7683             ReportClick("put", x, y); // and inform engine
7684             ReportClick("lift", x, y);
7685             MarkTargetSquares(0);
7686             return;
7687           }
7688         }
7689         DragPieceEnd(xPix, yPix); dragging = 0;
7690         /* Don't animate move and drag both */
7691         appData.animate = FALSE;
7692     }
7693
7694     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7695     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7696         ChessSquare piece = boards[currentMove][fromY][fromX];
7697         if(gameMode == EditPosition && piece != EmptySquare &&
7698            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7699             int n;
7700
7701             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7702                 n = PieceToNumber(piece - (int)BlackPawn);
7703                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7704                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7705                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7706             } else
7707             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7708                 n = PieceToNumber(piece);
7709                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7710                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7711                 boards[currentMove][n][BOARD_WIDTH-2]++;
7712             }
7713             boards[currentMove][fromY][fromX] = EmptySquare;
7714         }
7715         ClearHighlights();
7716         fromX = fromY = -1;
7717         MarkTargetSquares(1);
7718         DrawPosition(TRUE, boards[currentMove]);
7719         return;
7720     }
7721
7722     // off-board moves should not be highlighted
7723     if(x < 0 || y < 0) ClearHighlights();
7724     else ReportClick("put", x, y);
7725
7726     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7727
7728     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7729
7730     if (legal[toY][toX] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7731         SetHighlights(fromX, fromY, toX, toY);
7732         MarkTargetSquares(1);
7733         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7734             // [HGM] super: promotion to captured piece selected from holdings
7735             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7736             promotionChoice = TRUE;
7737             // kludge follows to temporarily execute move on display, without promoting yet
7738             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7739             boards[currentMove][toY][toX] = p;
7740             DrawPosition(FALSE, boards[currentMove]);
7741             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7742             boards[currentMove][toY][toX] = q;
7743             DisplayMessage("Click in holdings to choose piece", "");
7744             return;
7745         }
7746         PromotionPopUp(promoChoice);
7747     } else {
7748         int oldMove = currentMove;
7749         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7750         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7751         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7752         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7753            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7754             DrawPosition(TRUE, boards[currentMove]);
7755         MarkTargetSquares(1);
7756         fromX = fromY = -1;
7757     }
7758     appData.animate = saveAnimate;
7759     if (appData.animate || appData.animateDragging) {
7760         /* Undo animation damage if needed */
7761         DrawPosition(FALSE, NULL);
7762     }
7763 }
7764
7765 int
7766 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7767 {   // front-end-free part taken out of PieceMenuPopup
7768     int whichMenu; int xSqr, ySqr;
7769
7770     if(seekGraphUp) { // [HGM] seekgraph
7771         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7772         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7773         return -2;
7774     }
7775
7776     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7777          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7778         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7779         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7780         if(action == Press)   {
7781             originalFlip = flipView;
7782             flipView = !flipView; // temporarily flip board to see game from partners perspective
7783             DrawPosition(TRUE, partnerBoard);
7784             DisplayMessage(partnerStatus, "");
7785             partnerUp = TRUE;
7786         } else if(action == Release) {
7787             flipView = originalFlip;
7788             DrawPosition(TRUE, boards[currentMove]);
7789             partnerUp = FALSE;
7790         }
7791         return -2;
7792     }
7793
7794     xSqr = EventToSquare(x, BOARD_WIDTH);
7795     ySqr = EventToSquare(y, BOARD_HEIGHT);
7796     if (action == Release) {
7797         if(pieceSweep != EmptySquare) {
7798             EditPositionMenuEvent(pieceSweep, toX, toY);
7799             pieceSweep = EmptySquare;
7800         } else UnLoadPV(); // [HGM] pv
7801     }
7802     if (action != Press) return -2; // return code to be ignored
7803     switch (gameMode) {
7804       case IcsExamining:
7805         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7806       case EditPosition:
7807         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7808         if (xSqr < 0 || ySqr < 0) return -1;
7809         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7810         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7811         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7812         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7813         NextPiece(0);
7814         return 2; // grab
7815       case IcsObserving:
7816         if(!appData.icsEngineAnalyze) return -1;
7817       case IcsPlayingWhite:
7818       case IcsPlayingBlack:
7819         if(!appData.zippyPlay) goto noZip;
7820       case AnalyzeMode:
7821       case AnalyzeFile:
7822       case MachinePlaysWhite:
7823       case MachinePlaysBlack:
7824       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7825         if (!appData.dropMenu) {
7826           LoadPV(x, y);
7827           return 2; // flag front-end to grab mouse events
7828         }
7829         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7830            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7831       case EditGame:
7832       noZip:
7833         if (xSqr < 0 || ySqr < 0) return -1;
7834         if (!appData.dropMenu || appData.testLegality &&
7835             gameInfo.variant != VariantBughouse &&
7836             gameInfo.variant != VariantCrazyhouse) return -1;
7837         whichMenu = 1; // drop menu
7838         break;
7839       default:
7840         return -1;
7841     }
7842
7843     if (((*fromX = xSqr) < 0) ||
7844         ((*fromY = ySqr) < 0)) {
7845         *fromX = *fromY = -1;
7846         return -1;
7847     }
7848     if (flipView)
7849       *fromX = BOARD_WIDTH - 1 - *fromX;
7850     else
7851       *fromY = BOARD_HEIGHT - 1 - *fromY;
7852
7853     return whichMenu;
7854 }
7855
7856 void
7857 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7858 {
7859 //    char * hint = lastHint;
7860     FrontEndProgramStats stats;
7861
7862     stats.which = cps == &first ? 0 : 1;
7863     stats.depth = cpstats->depth;
7864     stats.nodes = cpstats->nodes;
7865     stats.score = cpstats->score;
7866     stats.time = cpstats->time;
7867     stats.pv = cpstats->movelist;
7868     stats.hint = lastHint;
7869     stats.an_move_index = 0;
7870     stats.an_move_count = 0;
7871
7872     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7873         stats.hint = cpstats->move_name;
7874         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7875         stats.an_move_count = cpstats->nr_moves;
7876     }
7877
7878     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
7879
7880     SetProgramStats( &stats );
7881 }
7882
7883 void
7884 ClearEngineOutputPane (int which)
7885 {
7886     static FrontEndProgramStats dummyStats;
7887     dummyStats.which = which;
7888     dummyStats.pv = "#";
7889     SetProgramStats( &dummyStats );
7890 }
7891
7892 #define MAXPLAYERS 500
7893
7894 char *
7895 TourneyStandings (int display)
7896 {
7897     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7898     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7899     char result, *p, *names[MAXPLAYERS];
7900
7901     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7902         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7903     names[0] = p = strdup(appData.participants);
7904     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7905
7906     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7907
7908     while(result = appData.results[nr]) {
7909         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7910         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7911         wScore = bScore = 0;
7912         switch(result) {
7913           case '+': wScore = 2; break;
7914           case '-': bScore = 2; break;
7915           case '=': wScore = bScore = 1; break;
7916           case ' ':
7917           case '*': return strdup("busy"); // tourney not finished
7918         }
7919         score[w] += wScore;
7920         score[b] += bScore;
7921         games[w]++;
7922         games[b]++;
7923         nr++;
7924     }
7925     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7926     for(w=0; w<nPlayers; w++) {
7927         bScore = -1;
7928         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7929         ranking[w] = b; points[w] = bScore; score[b] = -2;
7930     }
7931     p = malloc(nPlayers*34+1);
7932     for(w=0; w<nPlayers && w<display; w++)
7933         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7934     free(names[0]);
7935     return p;
7936 }
7937
7938 void
7939 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7940 {       // count all piece types
7941         int p, f, r;
7942         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7943         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7944         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7945                 p = board[r][f];
7946                 pCnt[p]++;
7947                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7948                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7949                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7950                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7951                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7952                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7953         }
7954 }
7955
7956 int
7957 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7958 {
7959         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7960         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7961
7962         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7963         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7964         if(myPawns == 2 && nMine == 3) // KPP
7965             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7966         if(myPawns == 1 && nMine == 2) // KP
7967             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7968         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7969             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7970         if(myPawns) return FALSE;
7971         if(pCnt[WhiteRook+side])
7972             return pCnt[BlackRook-side] ||
7973                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7974                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7975                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7976         if(pCnt[WhiteCannon+side]) {
7977             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7978             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7979         }
7980         if(pCnt[WhiteKnight+side])
7981             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7982         return FALSE;
7983 }
7984
7985 int
7986 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7987 {
7988         VariantClass v = gameInfo.variant;
7989
7990         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7991         if(v == VariantShatranj) return TRUE; // always winnable through baring
7992         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7993         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7994
7995         if(v == VariantXiangqi) {
7996                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7997
7998                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7999                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8000                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8001                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8002                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8003                 if(stale) // we have at least one last-rank P plus perhaps C
8004                     return majors // KPKX
8005                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8006                 else // KCA*E*
8007                     return pCnt[WhiteFerz+side] // KCAK
8008                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8009                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8010                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8011
8012         } else if(v == VariantKnightmate) {
8013                 if(nMine == 1) return FALSE;
8014                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8015         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8016                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8017
8018                 if(nMine == 1) return FALSE; // bare King
8019                 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
8020                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8021                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8022                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8023                 if(pCnt[WhiteKnight+side])
8024                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8025                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8026                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8027                 if(nBishops)
8028                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8029                 if(pCnt[WhiteAlfil+side])
8030                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8031                 if(pCnt[WhiteWazir+side])
8032                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8033         }
8034
8035         return TRUE;
8036 }
8037
8038 int
8039 CompareWithRights (Board b1, Board b2)
8040 {
8041     int rights = 0;
8042     if(!CompareBoards(b1, b2)) return FALSE;
8043     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8044     /* compare castling rights */
8045     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8046            rights++; /* King lost rights, while rook still had them */
8047     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8048         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8049            rights++; /* but at least one rook lost them */
8050     }
8051     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8052            rights++;
8053     if( b1[CASTLING][5] != NoRights ) {
8054         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8055            rights++;
8056     }
8057     return rights == 0;
8058 }
8059
8060 int
8061 Adjudicate (ChessProgramState *cps)
8062 {       // [HGM] some adjudications useful with buggy engines
8063         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8064         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8065         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8066         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8067         int k, drop, count = 0; static int bare = 1;
8068         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8069         Boolean canAdjudicate = !appData.icsActive;
8070
8071         // most tests only when we understand the game, i.e. legality-checking on
8072             if( appData.testLegality )
8073             {   /* [HGM] Some more adjudications for obstinate engines */
8074                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8075                 static int moveCount = 6;
8076                 ChessMove result;
8077                 char *reason = NULL;
8078
8079                 /* Count what is on board. */
8080                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8081
8082                 /* Some material-based adjudications that have to be made before stalemate test */
8083                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8084                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8085                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8086                      if(canAdjudicate && appData.checkMates) {
8087                          if(engineOpponent)
8088                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8089                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8090                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8091                          return 1;
8092                      }
8093                 }
8094
8095                 /* Bare King in Shatranj (loses) or Losers (wins) */
8096                 if( nrW == 1 || nrB == 1) {
8097                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8098                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8099                      if(canAdjudicate && appData.checkMates) {
8100                          if(engineOpponent)
8101                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8102                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8103                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8104                          return 1;
8105                      }
8106                   } else
8107                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8108                   {    /* bare King */
8109                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8110                         if(canAdjudicate && appData.checkMates) {
8111                             /* but only adjudicate if adjudication enabled */
8112                             if(engineOpponent)
8113                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8114                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8115                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8116                             return 1;
8117                         }
8118                   }
8119                 } else bare = 1;
8120
8121
8122             // don't wait for engine to announce game end if we can judge ourselves
8123             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8124               case MT_CHECK:
8125                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8126                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8127                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8128                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8129                             checkCnt++;
8130                         if(checkCnt >= 2) {
8131                             reason = "Xboard adjudication: 3rd check";
8132                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8133                             break;
8134                         }
8135                     }
8136                 }
8137               case MT_NONE:
8138               default:
8139                 break;
8140               case MT_STEALMATE:
8141               case MT_STALEMATE:
8142               case MT_STAINMATE:
8143                 reason = "Xboard adjudication: Stalemate";
8144                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8145                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8146                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8147                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8148                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8149                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8150                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8151                                                                         EP_CHECKMATE : EP_WINS);
8152                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8153                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8154                 }
8155                 break;
8156               case MT_CHECKMATE:
8157                 reason = "Xboard adjudication: Checkmate";
8158                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8159                 if(gameInfo.variant == VariantShogi) {
8160                     if(forwardMostMove > backwardMostMove
8161                        && moveList[forwardMostMove-1][1] == '@'
8162                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8163                         reason = "XBoard adjudication: pawn-drop mate";
8164                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8165                     }
8166                 }
8167                 break;
8168             }
8169
8170                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8171                     case EP_STALEMATE:
8172                         result = GameIsDrawn; break;
8173                     case EP_CHECKMATE:
8174                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8175                     case EP_WINS:
8176                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8177                     default:
8178                         result = EndOfFile;
8179                 }
8180                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8181                     if(engineOpponent)
8182                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8183                     GameEnds( result, reason, GE_XBOARD );
8184                     return 1;
8185                 }
8186
8187                 /* Next absolutely insufficient mating material. */
8188                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8189                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8190                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8191
8192                      /* always flag draws, for judging claims */
8193                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8194
8195                      if(canAdjudicate && appData.materialDraws) {
8196                          /* but only adjudicate them if adjudication enabled */
8197                          if(engineOpponent) {
8198                            SendToProgram("force\n", engineOpponent); // suppress reply
8199                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8200                          }
8201                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8202                          return 1;
8203                      }
8204                 }
8205
8206                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8207                 if(gameInfo.variant == VariantXiangqi ?
8208                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8209                  : nrW + nrB == 4 &&
8210                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8211                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8212                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8213                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8214                    ) ) {
8215                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8216                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8217                           if(engineOpponent) {
8218                             SendToProgram("force\n", engineOpponent); // suppress reply
8219                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8220                           }
8221                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8222                           return 1;
8223                      }
8224                 } else moveCount = 6;
8225             }
8226
8227         // Repetition draws and 50-move rule can be applied independently of legality testing
8228
8229                 /* Check for rep-draws */
8230                 count = 0;
8231                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8232                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8233                 for(k = forwardMostMove-2;
8234                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8235                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8236                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8237                     k-=2)
8238                 {   int rights=0;
8239                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8240                         /* compare castling rights */
8241                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8242                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8243                                 rights++; /* King lost rights, while rook still had them */
8244                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8245                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8246                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8247                                    rights++; /* but at least one rook lost them */
8248                         }
8249                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8250                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8251                                 rights++;
8252                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8253                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8254                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8255                                    rights++;
8256                         }
8257                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8258                             && appData.drawRepeats > 1) {
8259                              /* adjudicate after user-specified nr of repeats */
8260                              int result = GameIsDrawn;
8261                              char *details = "XBoard adjudication: repetition draw";
8262                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8263                                 // [HGM] xiangqi: check for forbidden perpetuals
8264                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8265                                 for(m=forwardMostMove; m>k; m-=2) {
8266                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8267                                         ourPerpetual = 0; // the current mover did not always check
8268                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8269                                         hisPerpetual = 0; // the opponent did not always check
8270                                 }
8271                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8272                                                                         ourPerpetual, hisPerpetual);
8273                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8274                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8275                                     details = "Xboard adjudication: perpetual checking";
8276                                 } else
8277                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8278                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8279                                 } else
8280                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8281                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8282                                         result = BlackWins;
8283                                         details = "Xboard adjudication: repetition";
8284                                     }
8285                                 } else // it must be XQ
8286                                 // Now check for perpetual chases
8287                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8288                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8289                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8290                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8291                                         static char resdet[MSG_SIZ];
8292                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8293                                         details = resdet;
8294                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8295                                     } else
8296                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8297                                         break; // Abort repetition-checking loop.
8298                                 }
8299                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8300                              }
8301                              if(engineOpponent) {
8302                                SendToProgram("force\n", engineOpponent); // suppress reply
8303                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8304                              }
8305                              GameEnds( result, details, GE_XBOARD );
8306                              return 1;
8307                         }
8308                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8309                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8310                     }
8311                 }
8312
8313                 /* Now we test for 50-move draws. Determine ply count */
8314                 count = forwardMostMove;
8315                 /* look for last irreversble move */
8316                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8317                     count--;
8318                 /* if we hit starting position, add initial plies */
8319                 if( count == backwardMostMove )
8320                     count -= initialRulePlies;
8321                 count = forwardMostMove - count;
8322                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8323                         // adjust reversible move counter for checks in Xiangqi
8324                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8325                         if(i < backwardMostMove) i = backwardMostMove;
8326                         while(i <= forwardMostMove) {
8327                                 lastCheck = inCheck; // check evasion does not count
8328                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8329                                 if(inCheck || lastCheck) count--; // check does not count
8330                                 i++;
8331                         }
8332                 }
8333                 if( count >= 100)
8334                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8335                          /* this is used to judge if draw claims are legal */
8336                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8337                          if(engineOpponent) {
8338                            SendToProgram("force\n", engineOpponent); // suppress reply
8339                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                          }
8341                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8342                          return 1;
8343                 }
8344
8345                 /* if draw offer is pending, treat it as a draw claim
8346                  * when draw condition present, to allow engines a way to
8347                  * claim draws before making their move to avoid a race
8348                  * condition occurring after their move
8349                  */
8350                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8351                          char *p = NULL;
8352                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8353                              p = "Draw claim: 50-move rule";
8354                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8355                              p = "Draw claim: 3-fold repetition";
8356                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8357                              p = "Draw claim: insufficient mating material";
8358                          if( p != NULL && canAdjudicate) {
8359                              if(engineOpponent) {
8360                                SendToProgram("force\n", engineOpponent); // suppress reply
8361                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8362                              }
8363                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8364                              return 1;
8365                          }
8366                 }
8367
8368                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8369                     if(engineOpponent) {
8370                       SendToProgram("force\n", engineOpponent); // suppress reply
8371                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8372                     }
8373                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8374                     return 1;
8375                 }
8376         return 0;
8377 }
8378
8379 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8380 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8381 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8382
8383 static int
8384 BitbaseProbe ()
8385 {
8386     int pieces[10], squares[10], cnt=0, r, f, res;
8387     static int loaded;
8388     static PPROBE_EGBB probeBB;
8389     if(!appData.testLegality) return 10;
8390     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8391     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8392     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8393     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8394         ChessSquare piece = boards[forwardMostMove][r][f];
8395         int black = (piece >= BlackPawn);
8396         int type = piece - black*BlackPawn;
8397         if(piece == EmptySquare) continue;
8398         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8399         if(type == WhiteKing) type = WhiteQueen + 1;
8400         type = egbbCode[type];
8401         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8402         pieces[cnt] = type + black*6;
8403         if(++cnt > 5) return 11;
8404     }
8405     pieces[cnt] = squares[cnt] = 0;
8406     // probe EGBB
8407     if(loaded == 2) return 13; // loading failed before
8408     if(loaded == 0) {
8409         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8410         HMODULE lib;
8411         PLOAD_EGBB loadBB;
8412         loaded = 2; // prepare for failure
8413         if(!path) return 13; // no egbb installed
8414         strncpy(buf, path + 8, MSG_SIZ);
8415         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8416         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8417         lib = LoadLibrary(buf);
8418         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8419         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8420         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8421         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8422         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8423         loaded = 1; // success!
8424     }
8425     res = probeBB(forwardMostMove & 1, pieces, squares);
8426     return res > 0 ? 1 : res < 0 ? -1 : 0;
8427 }
8428
8429 char *
8430 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8431 {   // [HGM] book: this routine intercepts moves to simulate book replies
8432     char *bookHit = NULL;
8433
8434     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8435         char buf[MSG_SIZ];
8436         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8437         SendToProgram(buf, cps);
8438     }
8439     //first determine if the incoming move brings opponent into his book
8440     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8441         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8442     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8443     if(bookHit != NULL && !cps->bookSuspend) {
8444         // make sure opponent is not going to reply after receiving move to book position
8445         SendToProgram("force\n", cps);
8446         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8447     }
8448     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8449     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8450     // now arrange restart after book miss
8451     if(bookHit) {
8452         // after a book hit we never send 'go', and the code after the call to this routine
8453         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8454         char buf[MSG_SIZ], *move = bookHit;
8455         if(cps->useSAN) {
8456             int fromX, fromY, toX, toY;
8457             char promoChar;
8458             ChessMove moveType;
8459             move = buf + 30;
8460             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8461                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8462                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8463                                     PosFlags(forwardMostMove),
8464                                     fromY, fromX, toY, toX, promoChar, move);
8465             } else {
8466                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8467                 bookHit = NULL;
8468             }
8469         }
8470         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8471         SendToProgram(buf, cps);
8472         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8473     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8474         SendToProgram("go\n", cps);
8475         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8476     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8477         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8478             SendToProgram("go\n", cps);
8479         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8480     }
8481     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8482 }
8483
8484 int
8485 LoadError (char *errmess, ChessProgramState *cps)
8486 {   // unloads engine and switches back to -ncp mode if it was first
8487     if(cps->initDone) return FALSE;
8488     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8489     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8490     cps->pr = NoProc;
8491     if(cps == &first) {
8492         appData.noChessProgram = TRUE;
8493         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8494         gameMode = BeginningOfGame; ModeHighlight();
8495         SetNCPMode();
8496     }
8497     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8498     DisplayMessage("", ""); // erase waiting message
8499     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8500     return TRUE;
8501 }
8502
8503 char *savedMessage;
8504 ChessProgramState *savedState;
8505 void
8506 DeferredBookMove (void)
8507 {
8508         if(savedState->lastPing != savedState->lastPong)
8509                     ScheduleDelayedEvent(DeferredBookMove, 10);
8510         else
8511         HandleMachineMove(savedMessage, savedState);
8512 }
8513
8514 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8515 static ChessProgramState *stalledEngine;
8516 static char stashedInputMove[MSG_SIZ];
8517
8518 void
8519 HandleMachineMove (char *message, ChessProgramState *cps)
8520 {
8521     static char firstLeg[20];
8522     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8523     char realname[MSG_SIZ];
8524     int fromX, fromY, toX, toY;
8525     ChessMove moveType;
8526     char promoChar, roar;
8527     char *p, *pv=buf1;
8528     int machineWhite, oldError;
8529     char *bookHit;
8530
8531     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8532         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8533         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8534             DisplayError(_("Invalid pairing from pairing engine"), 0);
8535             return;
8536         }
8537         pairingReceived = 1;
8538         NextMatchGame();
8539         return; // Skim the pairing messages here.
8540     }
8541
8542     oldError = cps->userError; cps->userError = 0;
8543
8544 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8545     /*
8546      * Kludge to ignore BEL characters
8547      */
8548     while (*message == '\007') message++;
8549
8550     /*
8551      * [HGM] engine debug message: ignore lines starting with '#' character
8552      */
8553     if(cps->debug && *message == '#') return;
8554
8555     /*
8556      * Look for book output
8557      */
8558     if (cps == &first && bookRequested) {
8559         if (message[0] == '\t' || message[0] == ' ') {
8560             /* Part of the book output is here; append it */
8561             strcat(bookOutput, message);
8562             strcat(bookOutput, "  \n");
8563             return;
8564         } else if (bookOutput[0] != NULLCHAR) {
8565             /* All of book output has arrived; display it */
8566             char *p = bookOutput;
8567             while (*p != NULLCHAR) {
8568                 if (*p == '\t') *p = ' ';
8569                 p++;
8570             }
8571             DisplayInformation(bookOutput);
8572             bookRequested = FALSE;
8573             /* Fall through to parse the current output */
8574         }
8575     }
8576
8577     /*
8578      * Look for machine move.
8579      */
8580     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8581         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8582     {
8583         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8584             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8585             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8586             stalledEngine = cps;
8587             if(appData.ponderNextMove) { // bring opponent out of ponder
8588                 if(gameMode == TwoMachinesPlay) {
8589                     if(cps->other->pause)
8590                         PauseEngine(cps->other);
8591                     else
8592                         SendToProgram("easy\n", cps->other);
8593                 }
8594             }
8595             StopClocks();
8596             return;
8597         }
8598
8599         /* This method is only useful on engines that support ping */
8600         if (cps->lastPing != cps->lastPong) {
8601           if (gameMode == BeginningOfGame) {
8602             /* Extra move from before last new; ignore */
8603             if (appData.debugMode) {
8604                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8605             }
8606           } else {
8607             if (appData.debugMode) {
8608                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8609                         cps->which, gameMode);
8610             }
8611
8612             SendToProgram("undo\n", cps);
8613           }
8614           return;
8615         }
8616
8617         switch (gameMode) {
8618           case BeginningOfGame:
8619             /* Extra move from before last reset; ignore */
8620             if (appData.debugMode) {
8621                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8622             }
8623             return;
8624
8625           case EndOfGame:
8626           case IcsIdle:
8627           default:
8628             /* Extra move after we tried to stop.  The mode test is
8629                not a reliable way of detecting this problem, but it's
8630                the best we can do on engines that don't support ping.
8631             */
8632             if (appData.debugMode) {
8633                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8634                         cps->which, gameMode);
8635             }
8636             SendToProgram("undo\n", cps);
8637             return;
8638
8639           case MachinePlaysWhite:
8640           case IcsPlayingWhite:
8641             machineWhite = TRUE;
8642             break;
8643
8644           case MachinePlaysBlack:
8645           case IcsPlayingBlack:
8646             machineWhite = FALSE;
8647             break;
8648
8649           case TwoMachinesPlay:
8650             machineWhite = (cps->twoMachinesColor[0] == 'w');
8651             break;
8652         }
8653         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8654             if (appData.debugMode) {
8655                 fprintf(debugFP,
8656                         "Ignoring move out of turn by %s, gameMode %d"
8657                         ", forwardMost %d\n",
8658                         cps->which, gameMode, forwardMostMove);
8659             }
8660             return;
8661         }
8662
8663         if(cps->alphaRank) AlphaRank(machineMove, 4);
8664
8665         // [HGM] lion: (some very limited) support for Alien protocol
8666         killX = killY = -1;
8667         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8668             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8669             return;
8670         } else if(firstLeg[0]) { // there was a previous leg;
8671             // only support case where same piece makes two step
8672             char buf[20], *p = machineMove+1, *q = buf+1, f;
8673             safeStrCpy(buf, machineMove, 20);
8674             while(isdigit(*q)) q++; // find start of to-square
8675             safeStrCpy(machineMove, firstLeg, 20);
8676             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8677             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8678             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8679             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8680             firstLeg[0] = NULLCHAR;
8681         }
8682
8683         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8684                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8685             /* Machine move could not be parsed; ignore it. */
8686           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8687                     machineMove, _(cps->which));
8688             DisplayMoveError(buf1);
8689             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8690                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8691             if (gameMode == TwoMachinesPlay) {
8692               GameEnds(machineWhite ? BlackWins : WhiteWins,
8693                        buf1, GE_XBOARD);
8694             }
8695             return;
8696         }
8697
8698         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8699         /* So we have to redo legality test with true e.p. status here,  */
8700         /* to make sure an illegal e.p. capture does not slip through,   */
8701         /* to cause a forfeit on a justified illegal-move complaint      */
8702         /* of the opponent.                                              */
8703         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8704            ChessMove moveType;
8705            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8706                              fromY, fromX, toY, toX, promoChar);
8707             if(moveType == IllegalMove) {
8708               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8709                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8710                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8711                            buf1, GE_XBOARD);
8712                 return;
8713            } else if(!appData.fischerCastling)
8714            /* [HGM] Kludge to handle engines that send FRC-style castling
8715               when they shouldn't (like TSCP-Gothic) */
8716            switch(moveType) {
8717              case WhiteASideCastleFR:
8718              case BlackASideCastleFR:
8719                toX+=2;
8720                currentMoveString[2]++;
8721                break;
8722              case WhiteHSideCastleFR:
8723              case BlackHSideCastleFR:
8724                toX--;
8725                currentMoveString[2]--;
8726                break;
8727              default: ; // nothing to do, but suppresses warning of pedantic compilers
8728            }
8729         }
8730         hintRequested = FALSE;
8731         lastHint[0] = NULLCHAR;
8732         bookRequested = FALSE;
8733         /* Program may be pondering now */
8734         cps->maybeThinking = TRUE;
8735         if (cps->sendTime == 2) cps->sendTime = 1;
8736         if (cps->offeredDraw) cps->offeredDraw--;
8737
8738         /* [AS] Save move info*/
8739         pvInfoList[ forwardMostMove ].score = programStats.score;
8740         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8741         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8742
8743         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8744
8745         /* Test suites abort the 'game' after one move */
8746         if(*appData.finger) {
8747            static FILE *f;
8748            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8749            if(!f) f = fopen(appData.finger, "w");
8750            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8751            else { DisplayFatalError("Bad output file", errno, 0); return; }
8752            free(fen);
8753            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8754         }
8755
8756         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8757         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8758             int count = 0;
8759
8760             while( count < adjudicateLossPlies ) {
8761                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8762
8763                 if( count & 1 ) {
8764                     score = -score; /* Flip score for winning side */
8765                 }
8766
8767                 if( score > appData.adjudicateLossThreshold ) {
8768                     break;
8769                 }
8770
8771                 count++;
8772             }
8773
8774             if( count >= adjudicateLossPlies ) {
8775                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8776
8777                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8778                     "Xboard adjudication",
8779                     GE_XBOARD );
8780
8781                 return;
8782             }
8783         }
8784
8785         if(Adjudicate(cps)) {
8786             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8787             return; // [HGM] adjudicate: for all automatic game ends
8788         }
8789
8790 #if ZIPPY
8791         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8792             first.initDone) {
8793           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8794                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8795                 SendToICS("draw ");
8796                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8797           }
8798           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8799           ics_user_moved = 1;
8800           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8801                 char buf[3*MSG_SIZ];
8802
8803                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8804                         programStats.score / 100.,
8805                         programStats.depth,
8806                         programStats.time / 100.,
8807                         (unsigned int)programStats.nodes,
8808                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8809                         programStats.movelist);
8810                 SendToICS(buf);
8811           }
8812         }
8813 #endif
8814
8815         /* [AS] Clear stats for next move */
8816         ClearProgramStats();
8817         thinkOutput[0] = NULLCHAR;
8818         hiddenThinkOutputState = 0;
8819
8820         bookHit = NULL;
8821         if (gameMode == TwoMachinesPlay) {
8822             /* [HGM] relaying draw offers moved to after reception of move */
8823             /* and interpreting offer as claim if it brings draw condition */
8824             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8825                 SendToProgram("draw\n", cps->other);
8826             }
8827             if (cps->other->sendTime) {
8828                 SendTimeRemaining(cps->other,
8829                                   cps->other->twoMachinesColor[0] == 'w');
8830             }
8831             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8832             if (firstMove && !bookHit) {
8833                 firstMove = FALSE;
8834                 if (cps->other->useColors) {
8835                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8836                 }
8837                 SendToProgram("go\n", cps->other);
8838             }
8839             cps->other->maybeThinking = TRUE;
8840         }
8841
8842         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8843
8844         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8845
8846         if (!pausing && appData.ringBellAfterMoves) {
8847             if(!roar) RingBell();
8848         }
8849
8850         /*
8851          * Reenable menu items that were disabled while
8852          * machine was thinking
8853          */
8854         if (gameMode != TwoMachinesPlay)
8855             SetUserThinkingEnables();
8856
8857         // [HGM] book: after book hit opponent has received move and is now in force mode
8858         // force the book reply into it, and then fake that it outputted this move by jumping
8859         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8860         if(bookHit) {
8861                 static char bookMove[MSG_SIZ]; // a bit generous?
8862
8863                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8864                 strcat(bookMove, bookHit);
8865                 message = bookMove;
8866                 cps = cps->other;
8867                 programStats.nodes = programStats.depth = programStats.time =
8868                 programStats.score = programStats.got_only_move = 0;
8869                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8870
8871                 if(cps->lastPing != cps->lastPong) {
8872                     savedMessage = message; // args for deferred call
8873                     savedState = cps;
8874                     ScheduleDelayedEvent(DeferredBookMove, 10);
8875                     return;
8876                 }
8877                 goto FakeBookMove;
8878         }
8879
8880         return;
8881     }
8882
8883     /* Set special modes for chess engines.  Later something general
8884      *  could be added here; for now there is just one kludge feature,
8885      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8886      *  when "xboard" is given as an interactive command.
8887      */
8888     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8889         cps->useSigint = FALSE;
8890         cps->useSigterm = FALSE;
8891     }
8892     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8893       ParseFeatures(message+8, cps);
8894       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8895     }
8896
8897     if (!strncmp(message, "setup ", 6) && 
8898         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8899           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8900                                         ) { // [HGM] allow first engine to define opening position
8901       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8902       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8903       *buf = NULLCHAR;
8904       if(sscanf(message, "setup (%s", buf) == 1) {
8905         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8906         ASSIGN(appData.pieceToCharTable, buf);
8907       }
8908       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8909       if(dummy >= 3) {
8910         while(message[s] && message[s++] != ' ');
8911         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8912            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8913             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8914             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8915           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8916           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8917           startedFromSetupPosition = FALSE;
8918         }
8919       }
8920       if(startedFromSetupPosition) return;
8921       ParseFEN(boards[0], &dummy, message+s, FALSE);
8922       DrawPosition(TRUE, boards[0]);
8923       CopyBoard(initialPosition, boards[0]);
8924       startedFromSetupPosition = TRUE;
8925       return;
8926     }
8927     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8928       ChessSquare piece = WhitePawn;
8929       char *p=buf2;
8930       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8931       piece += CharToPiece(*p) - WhitePawn;
8932       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8933       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8934       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8935       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8936       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8937       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8938                                                && gameInfo.variant != VariantGreat
8939                                                && gameInfo.variant != VariantFairy    ) return;
8940       if(piece < EmptySquare) {
8941         pieceDefs = TRUE;
8942         ASSIGN(pieceDesc[piece], buf1);
8943         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8944       }
8945       return;
8946     }
8947     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8948      * want this, I was asked to put it in, and obliged.
8949      */
8950     if (!strncmp(message, "setboard ", 9)) {
8951         Board initial_position;
8952
8953         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8954
8955         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8956             DisplayError(_("Bad FEN received from engine"), 0);
8957             return ;
8958         } else {
8959            Reset(TRUE, FALSE);
8960            CopyBoard(boards[0], initial_position);
8961            initialRulePlies = FENrulePlies;
8962            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8963            else gameMode = MachinePlaysBlack;
8964            DrawPosition(FALSE, boards[currentMove]);
8965         }
8966         return;
8967     }
8968
8969     /*
8970      * Look for communication commands
8971      */
8972     if (!strncmp(message, "telluser ", 9)) {
8973         if(message[9] == '\\' && message[10] == '\\')
8974             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8975         PlayTellSound();
8976         DisplayNote(message + 9);
8977         return;
8978     }
8979     if (!strncmp(message, "tellusererror ", 14)) {
8980         cps->userError = 1;
8981         if(message[14] == '\\' && message[15] == '\\')
8982             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8983         PlayTellSound();
8984         DisplayError(message + 14, 0);
8985         return;
8986     }
8987     if (!strncmp(message, "tellopponent ", 13)) {
8988       if (appData.icsActive) {
8989         if (loggedOn) {
8990           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8991           SendToICS(buf1);
8992         }
8993       } else {
8994         DisplayNote(message + 13);
8995       }
8996       return;
8997     }
8998     if (!strncmp(message, "tellothers ", 11)) {
8999       if (appData.icsActive) {
9000         if (loggedOn) {
9001           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9002           SendToICS(buf1);
9003         }
9004       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9005       return;
9006     }
9007     if (!strncmp(message, "tellall ", 8)) {
9008       if (appData.icsActive) {
9009         if (loggedOn) {
9010           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9011           SendToICS(buf1);
9012         }
9013       } else {
9014         DisplayNote(message + 8);
9015       }
9016       return;
9017     }
9018     if (strncmp(message, "warning", 7) == 0) {
9019         /* Undocumented feature, use tellusererror in new code */
9020         DisplayError(message, 0);
9021         return;
9022     }
9023     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9024         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9025         strcat(realname, " query");
9026         AskQuestion(realname, buf2, buf1, cps->pr);
9027         return;
9028     }
9029     /* Commands from the engine directly to ICS.  We don't allow these to be
9030      *  sent until we are logged on. Crafty kibitzes have been known to
9031      *  interfere with the login process.
9032      */
9033     if (loggedOn) {
9034         if (!strncmp(message, "tellics ", 8)) {
9035             SendToICS(message + 8);
9036             SendToICS("\n");
9037             return;
9038         }
9039         if (!strncmp(message, "tellicsnoalias ", 15)) {
9040             SendToICS(ics_prefix);
9041             SendToICS(message + 15);
9042             SendToICS("\n");
9043             return;
9044         }
9045         /* The following are for backward compatibility only */
9046         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9047             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9048             SendToICS(ics_prefix);
9049             SendToICS(message);
9050             SendToICS("\n");
9051             return;
9052         }
9053     }
9054     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9055         if(initPing == cps->lastPong) {
9056             if(gameInfo.variant == VariantUnknown) {
9057                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9058                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9059                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9060             }
9061             initPing = -1;
9062         }
9063         return;
9064     }
9065     if(!strncmp(message, "highlight ", 10)) {
9066         if(appData.testLegality && appData.markers) return;
9067         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9068         return;
9069     }
9070     if(!strncmp(message, "click ", 6)) {
9071         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9072         if(appData.testLegality || !appData.oneClick) return;
9073         sscanf(message+6, "%c%d%c", &f, &y, &c);
9074         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9075         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9076         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9077         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9078         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9079         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9080             LeftClick(Release, lastLeftX, lastLeftY);
9081         controlKey  = (c == ',');
9082         LeftClick(Press, x, y);
9083         LeftClick(Release, x, y);
9084         first.highlight = f;
9085         return;
9086     }
9087     /*
9088      * If the move is illegal, cancel it and redraw the board.
9089      * Also deal with other error cases.  Matching is rather loose
9090      * here to accommodate engines written before the spec.
9091      */
9092     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9093         strncmp(message, "Error", 5) == 0) {
9094         if (StrStr(message, "name") ||
9095             StrStr(message, "rating") || StrStr(message, "?") ||
9096             StrStr(message, "result") || StrStr(message, "board") ||
9097             StrStr(message, "bk") || StrStr(message, "computer") ||
9098             StrStr(message, "variant") || StrStr(message, "hint") ||
9099             StrStr(message, "random") || StrStr(message, "depth") ||
9100             StrStr(message, "accepted")) {
9101             return;
9102         }
9103         if (StrStr(message, "protover")) {
9104           /* Program is responding to input, so it's apparently done
9105              initializing, and this error message indicates it is
9106              protocol version 1.  So we don't need to wait any longer
9107              for it to initialize and send feature commands. */
9108           FeatureDone(cps, 1);
9109           cps->protocolVersion = 1;
9110           return;
9111         }
9112         cps->maybeThinking = FALSE;
9113
9114         if (StrStr(message, "draw")) {
9115             /* Program doesn't have "draw" command */
9116             cps->sendDrawOffers = 0;
9117             return;
9118         }
9119         if (cps->sendTime != 1 &&
9120             (StrStr(message, "time") || StrStr(message, "otim"))) {
9121           /* Program apparently doesn't have "time" or "otim" command */
9122           cps->sendTime = 0;
9123           return;
9124         }
9125         if (StrStr(message, "analyze")) {
9126             cps->analysisSupport = FALSE;
9127             cps->analyzing = FALSE;
9128 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9129             EditGameEvent(); // [HGM] try to preserve loaded game
9130             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9131             DisplayError(buf2, 0);
9132             return;
9133         }
9134         if (StrStr(message, "(no matching move)st")) {
9135           /* Special kludge for GNU Chess 4 only */
9136           cps->stKludge = TRUE;
9137           SendTimeControl(cps, movesPerSession, timeControl,
9138                           timeIncrement, appData.searchDepth,
9139                           searchTime);
9140           return;
9141         }
9142         if (StrStr(message, "(no matching move)sd")) {
9143           /* Special kludge for GNU Chess 4 only */
9144           cps->sdKludge = TRUE;
9145           SendTimeControl(cps, movesPerSession, timeControl,
9146                           timeIncrement, appData.searchDepth,
9147                           searchTime);
9148           return;
9149         }
9150         if (!StrStr(message, "llegal")) {
9151             return;
9152         }
9153         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9154             gameMode == IcsIdle) return;
9155         if (forwardMostMove <= backwardMostMove) return;
9156         if (pausing) PauseEvent();
9157       if(appData.forceIllegal) {
9158             // [HGM] illegal: machine refused move; force position after move into it
9159           SendToProgram("force\n", cps);
9160           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9161                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9162                 // when black is to move, while there might be nothing on a2 or black
9163                 // might already have the move. So send the board as if white has the move.
9164                 // But first we must change the stm of the engine, as it refused the last move
9165                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9166                 if(WhiteOnMove(forwardMostMove)) {
9167                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9168                     SendBoard(cps, forwardMostMove); // kludgeless board
9169                 } else {
9170                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9171                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9172                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9173                 }
9174           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9175             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9176                  gameMode == TwoMachinesPlay)
9177               SendToProgram("go\n", cps);
9178             return;
9179       } else
9180         if (gameMode == PlayFromGameFile) {
9181             /* Stop reading this game file */
9182             gameMode = EditGame;
9183             ModeHighlight();
9184         }
9185         /* [HGM] illegal-move claim should forfeit game when Xboard */
9186         /* only passes fully legal moves                            */
9187         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9188             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9189                                 "False illegal-move claim", GE_XBOARD );
9190             return; // do not take back move we tested as valid
9191         }
9192         currentMove = forwardMostMove-1;
9193         DisplayMove(currentMove-1); /* before DisplayMoveError */
9194         SwitchClocks(forwardMostMove-1); // [HGM] race
9195         DisplayBothClocks();
9196         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9197                 parseList[currentMove], _(cps->which));
9198         DisplayMoveError(buf1);
9199         DrawPosition(FALSE, boards[currentMove]);
9200
9201         SetUserThinkingEnables();
9202         return;
9203     }
9204     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9205         /* Program has a broken "time" command that
9206            outputs a string not ending in newline.
9207            Don't use it. */
9208         cps->sendTime = 0;
9209     }
9210     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9211         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9212             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9213     }
9214
9215     /*
9216      * If chess program startup fails, exit with an error message.
9217      * Attempts to recover here are futile. [HGM] Well, we try anyway
9218      */
9219     if ((StrStr(message, "unknown host") != NULL)
9220         || (StrStr(message, "No remote directory") != NULL)
9221         || (StrStr(message, "not found") != NULL)
9222         || (StrStr(message, "No such file") != NULL)
9223         || (StrStr(message, "can't alloc") != NULL)
9224         || (StrStr(message, "Permission denied") != NULL)) {
9225
9226         cps->maybeThinking = FALSE;
9227         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9228                 _(cps->which), cps->program, cps->host, message);
9229         RemoveInputSource(cps->isr);
9230         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9231             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9232             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9233         }
9234         return;
9235     }
9236
9237     /*
9238      * Look for hint output
9239      */
9240     if (sscanf(message, "Hint: %s", buf1) == 1) {
9241         if (cps == &first && hintRequested) {
9242             hintRequested = FALSE;
9243             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9244                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9245                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9246                                     PosFlags(forwardMostMove),
9247                                     fromY, fromX, toY, toX, promoChar, buf1);
9248                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9249                 DisplayInformation(buf2);
9250             } else {
9251                 /* Hint move could not be parsed!? */
9252               snprintf(buf2, sizeof(buf2),
9253                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9254                         buf1, _(cps->which));
9255                 DisplayError(buf2, 0);
9256             }
9257         } else {
9258           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9259         }
9260         return;
9261     }
9262
9263     /*
9264      * Ignore other messages if game is not in progress
9265      */
9266     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9267         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9268
9269     /*
9270      * look for win, lose, draw, or draw offer
9271      */
9272     if (strncmp(message, "1-0", 3) == 0) {
9273         char *p, *q, *r = "";
9274         p = strchr(message, '{');
9275         if (p) {
9276             q = strchr(p, '}');
9277             if (q) {
9278                 *q = NULLCHAR;
9279                 r = p + 1;
9280             }
9281         }
9282         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9283         return;
9284     } else if (strncmp(message, "0-1", 3) == 0) {
9285         char *p, *q, *r = "";
9286         p = strchr(message, '{');
9287         if (p) {
9288             q = strchr(p, '}');
9289             if (q) {
9290                 *q = NULLCHAR;
9291                 r = p + 1;
9292             }
9293         }
9294         /* Kludge for Arasan 4.1 bug */
9295         if (strcmp(r, "Black resigns") == 0) {
9296             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9297             return;
9298         }
9299         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9300         return;
9301     } else if (strncmp(message, "1/2", 3) == 0) {
9302         char *p, *q, *r = "";
9303         p = strchr(message, '{');
9304         if (p) {
9305             q = strchr(p, '}');
9306             if (q) {
9307                 *q = NULLCHAR;
9308                 r = p + 1;
9309             }
9310         }
9311
9312         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9313         return;
9314
9315     } else if (strncmp(message, "White resign", 12) == 0) {
9316         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9317         return;
9318     } else if (strncmp(message, "Black resign", 12) == 0) {
9319         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9320         return;
9321     } else if (strncmp(message, "White matches", 13) == 0 ||
9322                strncmp(message, "Black matches", 13) == 0   ) {
9323         /* [HGM] ignore GNUShogi noises */
9324         return;
9325     } else if (strncmp(message, "White", 5) == 0 &&
9326                message[5] != '(' &&
9327                StrStr(message, "Black") == NULL) {
9328         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9329         return;
9330     } else if (strncmp(message, "Black", 5) == 0 &&
9331                message[5] != '(') {
9332         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9333         return;
9334     } else if (strcmp(message, "resign") == 0 ||
9335                strcmp(message, "computer resigns") == 0) {
9336         switch (gameMode) {
9337           case MachinePlaysBlack:
9338           case IcsPlayingBlack:
9339             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9340             break;
9341           case MachinePlaysWhite:
9342           case IcsPlayingWhite:
9343             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9344             break;
9345           case TwoMachinesPlay:
9346             if (cps->twoMachinesColor[0] == 'w')
9347               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9348             else
9349               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9350             break;
9351           default:
9352             /* can't happen */
9353             break;
9354         }
9355         return;
9356     } else if (strncmp(message, "opponent mates", 14) == 0) {
9357         switch (gameMode) {
9358           case MachinePlaysBlack:
9359           case IcsPlayingBlack:
9360             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9361             break;
9362           case MachinePlaysWhite:
9363           case IcsPlayingWhite:
9364             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9365             break;
9366           case TwoMachinesPlay:
9367             if (cps->twoMachinesColor[0] == 'w')
9368               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9369             else
9370               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9371             break;
9372           default:
9373             /* can't happen */
9374             break;
9375         }
9376         return;
9377     } else if (strncmp(message, "computer mates", 14) == 0) {
9378         switch (gameMode) {
9379           case MachinePlaysBlack:
9380           case IcsPlayingBlack:
9381             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9382             break;
9383           case MachinePlaysWhite:
9384           case IcsPlayingWhite:
9385             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9386             break;
9387           case TwoMachinesPlay:
9388             if (cps->twoMachinesColor[0] == 'w')
9389               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9390             else
9391               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9392             break;
9393           default:
9394             /* can't happen */
9395             break;
9396         }
9397         return;
9398     } else if (strncmp(message, "checkmate", 9) == 0) {
9399         if (WhiteOnMove(forwardMostMove)) {
9400             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9401         } else {
9402             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9403         }
9404         return;
9405     } else if (strstr(message, "Draw") != NULL ||
9406                strstr(message, "game is a draw") != NULL) {
9407         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9408         return;
9409     } else if (strstr(message, "offer") != NULL &&
9410                strstr(message, "draw") != NULL) {
9411 #if ZIPPY
9412         if (appData.zippyPlay && first.initDone) {
9413             /* Relay offer to ICS */
9414             SendToICS(ics_prefix);
9415             SendToICS("draw\n");
9416         }
9417 #endif
9418         cps->offeredDraw = 2; /* valid until this engine moves twice */
9419         if (gameMode == TwoMachinesPlay) {
9420             if (cps->other->offeredDraw) {
9421                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9422             /* [HGM] in two-machine mode we delay relaying draw offer      */
9423             /* until after we also have move, to see if it is really claim */
9424             }
9425         } else if (gameMode == MachinePlaysWhite ||
9426                    gameMode == MachinePlaysBlack) {
9427           if (userOfferedDraw) {
9428             DisplayInformation(_("Machine accepts your draw offer"));
9429             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9430           } else {
9431             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9432           }
9433         }
9434     }
9435
9436
9437     /*
9438      * Look for thinking output
9439      */
9440     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9441           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9442                                 ) {
9443         int plylev, mvleft, mvtot, curscore, time;
9444         char mvname[MOVE_LEN];
9445         u64 nodes; // [DM]
9446         char plyext;
9447         int ignore = FALSE;
9448         int prefixHint = FALSE;
9449         mvname[0] = NULLCHAR;
9450
9451         switch (gameMode) {
9452           case MachinePlaysBlack:
9453           case IcsPlayingBlack:
9454             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9455             break;
9456           case MachinePlaysWhite:
9457           case IcsPlayingWhite:
9458             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9459             break;
9460           case AnalyzeMode:
9461           case AnalyzeFile:
9462             break;
9463           case IcsObserving: /* [DM] icsEngineAnalyze */
9464             if (!appData.icsEngineAnalyze) ignore = TRUE;
9465             break;
9466           case TwoMachinesPlay:
9467             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9468                 ignore = TRUE;
9469             }
9470             break;
9471           default:
9472             ignore = TRUE;
9473             break;
9474         }
9475
9476         if (!ignore) {
9477             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9478             buf1[0] = NULLCHAR;
9479             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9480                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9481
9482                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9483                     nodes += u64Const(0x100000000);
9484
9485                 if (plyext != ' ' && plyext != '\t') {
9486                     time *= 100;
9487                 }
9488
9489                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9490                 if( cps->scoreIsAbsolute &&
9491                     ( gameMode == MachinePlaysBlack ||
9492                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9493                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9494                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9495                      !WhiteOnMove(currentMove)
9496                     ) )
9497                 {
9498                     curscore = -curscore;
9499                 }
9500
9501                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9502
9503                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9504                         char buf[MSG_SIZ];
9505                         FILE *f;
9506                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9507                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9508                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9509                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9510                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9511                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9512                                 fclose(f);
9513                         }
9514                         else
9515                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9516                           DisplayError(_("failed writing PV"), 0);
9517                 }
9518
9519                 tempStats.depth = plylev;
9520                 tempStats.nodes = nodes;
9521                 tempStats.time = time;
9522                 tempStats.score = curscore;
9523                 tempStats.got_only_move = 0;
9524
9525                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9526                         int ticklen;
9527
9528                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9529                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9530                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9531                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9532                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9533                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9534                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9535                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9536                 }
9537
9538                 /* Buffer overflow protection */
9539                 if (pv[0] != NULLCHAR) {
9540                     if (strlen(pv) >= sizeof(tempStats.movelist)
9541                         && appData.debugMode) {
9542                         fprintf(debugFP,
9543                                 "PV is too long; using the first %u bytes.\n",
9544                                 (unsigned) sizeof(tempStats.movelist) - 1);
9545                     }
9546
9547                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9548                 } else {
9549                     sprintf(tempStats.movelist, " no PV\n");
9550                 }
9551
9552                 if (tempStats.seen_stat) {
9553                     tempStats.ok_to_send = 1;
9554                 }
9555
9556                 if (strchr(tempStats.movelist, '(') != NULL) {
9557                     tempStats.line_is_book = 1;
9558                     tempStats.nr_moves = 0;
9559                     tempStats.moves_left = 0;
9560                 } else {
9561                     tempStats.line_is_book = 0;
9562                 }
9563
9564                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9565                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9566
9567                 SendProgramStatsToFrontend( cps, &tempStats );
9568
9569                 /*
9570                     [AS] Protect the thinkOutput buffer from overflow... this
9571                     is only useful if buf1 hasn't overflowed first!
9572                 */
9573                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9574                          plylev,
9575                          (gameMode == TwoMachinesPlay ?
9576                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9577                          ((double) curscore) / 100.0,
9578                          prefixHint ? lastHint : "",
9579                          prefixHint ? " " : "" );
9580
9581                 if( buf1[0] != NULLCHAR ) {
9582                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9583
9584                     if( strlen(pv) > max_len ) {
9585                         if( appData.debugMode) {
9586                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9587                         }
9588                         pv[max_len+1] = '\0';
9589                     }
9590
9591                     strcat( thinkOutput, pv);
9592                 }
9593
9594                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9595                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9596                     DisplayMove(currentMove - 1);
9597                 }
9598                 return;
9599
9600             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9601                 /* crafty (9.25+) says "(only move) <move>"
9602                  * if there is only 1 legal move
9603                  */
9604                 sscanf(p, "(only move) %s", buf1);
9605                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9606                 sprintf(programStats.movelist, "%s (only move)", buf1);
9607                 programStats.depth = 1;
9608                 programStats.nr_moves = 1;
9609                 programStats.moves_left = 1;
9610                 programStats.nodes = 1;
9611                 programStats.time = 1;
9612                 programStats.got_only_move = 1;
9613
9614                 /* Not really, but we also use this member to
9615                    mean "line isn't going to change" (Crafty
9616                    isn't searching, so stats won't change) */
9617                 programStats.line_is_book = 1;
9618
9619                 SendProgramStatsToFrontend( cps, &programStats );
9620
9621                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9622                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9623                     DisplayMove(currentMove - 1);
9624                 }
9625                 return;
9626             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9627                               &time, &nodes, &plylev, &mvleft,
9628                               &mvtot, mvname) >= 5) {
9629                 /* The stat01: line is from Crafty (9.29+) in response
9630                    to the "." command */
9631                 programStats.seen_stat = 1;
9632                 cps->maybeThinking = TRUE;
9633
9634                 if (programStats.got_only_move || !appData.periodicUpdates)
9635                   return;
9636
9637                 programStats.depth = plylev;
9638                 programStats.time = time;
9639                 programStats.nodes = nodes;
9640                 programStats.moves_left = mvleft;
9641                 programStats.nr_moves = mvtot;
9642                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9643                 programStats.ok_to_send = 1;
9644                 programStats.movelist[0] = '\0';
9645
9646                 SendProgramStatsToFrontend( cps, &programStats );
9647
9648                 return;
9649
9650             } else if (strncmp(message,"++",2) == 0) {
9651                 /* Crafty 9.29+ outputs this */
9652                 programStats.got_fail = 2;
9653                 return;
9654
9655             } else if (strncmp(message,"--",2) == 0) {
9656                 /* Crafty 9.29+ outputs this */
9657                 programStats.got_fail = 1;
9658                 return;
9659
9660             } else if (thinkOutput[0] != NULLCHAR &&
9661                        strncmp(message, "    ", 4) == 0) {
9662                 unsigned message_len;
9663
9664                 p = message;
9665                 while (*p && *p == ' ') p++;
9666
9667                 message_len = strlen( p );
9668
9669                 /* [AS] Avoid buffer overflow */
9670                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9671                     strcat(thinkOutput, " ");
9672                     strcat(thinkOutput, p);
9673                 }
9674
9675                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9676                     strcat(programStats.movelist, " ");
9677                     strcat(programStats.movelist, p);
9678                 }
9679
9680                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9681                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9682                     DisplayMove(currentMove - 1);
9683                 }
9684                 return;
9685             }
9686         }
9687         else {
9688             buf1[0] = NULLCHAR;
9689
9690             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9691                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9692             {
9693                 ChessProgramStats cpstats;
9694
9695                 if (plyext != ' ' && plyext != '\t') {
9696                     time *= 100;
9697                 }
9698
9699                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9700                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9701                     curscore = -curscore;
9702                 }
9703
9704                 cpstats.depth = plylev;
9705                 cpstats.nodes = nodes;
9706                 cpstats.time = time;
9707                 cpstats.score = curscore;
9708                 cpstats.got_only_move = 0;
9709                 cpstats.movelist[0] = '\0';
9710
9711                 if (buf1[0] != NULLCHAR) {
9712                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9713                 }
9714
9715                 cpstats.ok_to_send = 0;
9716                 cpstats.line_is_book = 0;
9717                 cpstats.nr_moves = 0;
9718                 cpstats.moves_left = 0;
9719
9720                 SendProgramStatsToFrontend( cps, &cpstats );
9721             }
9722         }
9723     }
9724 }
9725
9726
9727 /* Parse a game score from the character string "game", and
9728    record it as the history of the current game.  The game
9729    score is NOT assumed to start from the standard position.
9730    The display is not updated in any way.
9731    */
9732 void
9733 ParseGameHistory (char *game)
9734 {
9735     ChessMove moveType;
9736     int fromX, fromY, toX, toY, boardIndex;
9737     char promoChar;
9738     char *p, *q;
9739     char buf[MSG_SIZ];
9740
9741     if (appData.debugMode)
9742       fprintf(debugFP, "Parsing game history: %s\n", game);
9743
9744     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9745     gameInfo.site = StrSave(appData.icsHost);
9746     gameInfo.date = PGNDate();
9747     gameInfo.round = StrSave("-");
9748
9749     /* Parse out names of players */
9750     while (*game == ' ') game++;
9751     p = buf;
9752     while (*game != ' ') *p++ = *game++;
9753     *p = NULLCHAR;
9754     gameInfo.white = StrSave(buf);
9755     while (*game == ' ') game++;
9756     p = buf;
9757     while (*game != ' ' && *game != '\n') *p++ = *game++;
9758     *p = NULLCHAR;
9759     gameInfo.black = StrSave(buf);
9760
9761     /* Parse moves */
9762     boardIndex = blackPlaysFirst ? 1 : 0;
9763     yynewstr(game);
9764     for (;;) {
9765         yyboardindex = boardIndex;
9766         moveType = (ChessMove) Myylex();
9767         switch (moveType) {
9768           case IllegalMove:             /* maybe suicide chess, etc. */
9769   if (appData.debugMode) {
9770     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9771     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9772     setbuf(debugFP, NULL);
9773   }
9774           case WhitePromotion:
9775           case BlackPromotion:
9776           case WhiteNonPromotion:
9777           case BlackNonPromotion:
9778           case NormalMove:
9779           case FirstLeg:
9780           case WhiteCapturesEnPassant:
9781           case BlackCapturesEnPassant:
9782           case WhiteKingSideCastle:
9783           case WhiteQueenSideCastle:
9784           case BlackKingSideCastle:
9785           case BlackQueenSideCastle:
9786           case WhiteKingSideCastleWild:
9787           case WhiteQueenSideCastleWild:
9788           case BlackKingSideCastleWild:
9789           case BlackQueenSideCastleWild:
9790           /* PUSH Fabien */
9791           case WhiteHSideCastleFR:
9792           case WhiteASideCastleFR:
9793           case BlackHSideCastleFR:
9794           case BlackASideCastleFR:
9795           /* POP Fabien */
9796             fromX = currentMoveString[0] - AAA;
9797             fromY = currentMoveString[1] - ONE;
9798             toX = currentMoveString[2] - AAA;
9799             toY = currentMoveString[3] - ONE;
9800             promoChar = currentMoveString[4];
9801             break;
9802           case WhiteDrop:
9803           case BlackDrop:
9804             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9805             fromX = moveType == WhiteDrop ?
9806               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9807             (int) CharToPiece(ToLower(currentMoveString[0]));
9808             fromY = DROP_RANK;
9809             toX = currentMoveString[2] - AAA;
9810             toY = currentMoveString[3] - ONE;
9811             promoChar = NULLCHAR;
9812             break;
9813           case AmbiguousMove:
9814             /* bug? */
9815             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9816   if (appData.debugMode) {
9817     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9818     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9819     setbuf(debugFP, NULL);
9820   }
9821             DisplayError(buf, 0);
9822             return;
9823           case ImpossibleMove:
9824             /* bug? */
9825             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9826   if (appData.debugMode) {
9827     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9828     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9829     setbuf(debugFP, NULL);
9830   }
9831             DisplayError(buf, 0);
9832             return;
9833           case EndOfFile:
9834             if (boardIndex < backwardMostMove) {
9835                 /* Oops, gap.  How did that happen? */
9836                 DisplayError(_("Gap in move list"), 0);
9837                 return;
9838             }
9839             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9840             if (boardIndex > forwardMostMove) {
9841                 forwardMostMove = boardIndex;
9842             }
9843             return;
9844           case ElapsedTime:
9845             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9846                 strcat(parseList[boardIndex-1], " ");
9847                 strcat(parseList[boardIndex-1], yy_text);
9848             }
9849             continue;
9850           case Comment:
9851           case PGNTag:
9852           case NAG:
9853           default:
9854             /* ignore */
9855             continue;
9856           case WhiteWins:
9857           case BlackWins:
9858           case GameIsDrawn:
9859           case GameUnfinished:
9860             if (gameMode == IcsExamining) {
9861                 if (boardIndex < backwardMostMove) {
9862                     /* Oops, gap.  How did that happen? */
9863                     return;
9864                 }
9865                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9866                 return;
9867             }
9868             gameInfo.result = moveType;
9869             p = strchr(yy_text, '{');
9870             if (p == NULL) p = strchr(yy_text, '(');
9871             if (p == NULL) {
9872                 p = yy_text;
9873                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9874             } else {
9875                 q = strchr(p, *p == '{' ? '}' : ')');
9876                 if (q != NULL) *q = NULLCHAR;
9877                 p++;
9878             }
9879             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9880             gameInfo.resultDetails = StrSave(p);
9881             continue;
9882         }
9883         if (boardIndex >= forwardMostMove &&
9884             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9885             backwardMostMove = blackPlaysFirst ? 1 : 0;
9886             return;
9887         }
9888         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9889                                  fromY, fromX, toY, toX, promoChar,
9890                                  parseList[boardIndex]);
9891         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9892         /* currentMoveString is set as a side-effect of yylex */
9893         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9894         strcat(moveList[boardIndex], "\n");
9895         boardIndex++;
9896         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9897         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9898           case MT_NONE:
9899           case MT_STALEMATE:
9900           default:
9901             break;
9902           case MT_CHECK:
9903             if(!IS_SHOGI(gameInfo.variant))
9904                 strcat(parseList[boardIndex - 1], "+");
9905             break;
9906           case MT_CHECKMATE:
9907           case MT_STAINMATE:
9908             strcat(parseList[boardIndex - 1], "#");
9909             break;
9910         }
9911     }
9912 }
9913
9914
9915 /* Apply a move to the given board  */
9916 void
9917 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9918 {
9919   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9920   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9921
9922     /* [HGM] compute & store e.p. status and castling rights for new position */
9923     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9924
9925       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9926       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9927       board[EP_STATUS] = EP_NONE;
9928       board[EP_FILE] = board[EP_RANK] = 100;
9929
9930   if (fromY == DROP_RANK) {
9931         /* must be first */
9932         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9933             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9934             return;
9935         }
9936         piece = board[toY][toX] = (ChessSquare) fromX;
9937   } else {
9938 //      ChessSquare victim;
9939       int i;
9940
9941       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9942 //           victim = board[killY][killX],
9943            killed = board[killY][killX],
9944            board[killY][killX] = EmptySquare,
9945            board[EP_STATUS] = EP_CAPTURE;
9946
9947       if( board[toY][toX] != EmptySquare ) {
9948            board[EP_STATUS] = EP_CAPTURE;
9949            if( (fromX != toX || fromY != toY) && // not igui!
9950                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9951                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9952                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9953            }
9954       }
9955
9956       pawn = board[fromY][fromX];
9957       if( pawn == WhiteLance || pawn == BlackLance ) {
9958            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9959                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9960                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9961            }
9962       }
9963       if( pawn == WhitePawn ) {
9964            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9965                board[EP_STATUS] = EP_PAWN_MOVE;
9966            if( toY-fromY>=2) {
9967                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9968                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9969                         gameInfo.variant != VariantBerolina || toX < fromX)
9970                       board[EP_STATUS] = toX | berolina;
9971                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9972                         gameInfo.variant != VariantBerolina || toX > fromX)
9973                       board[EP_STATUS] = toX;
9974            }
9975       } else
9976       if( pawn == BlackPawn ) {
9977            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9978                board[EP_STATUS] = EP_PAWN_MOVE;
9979            if( toY-fromY<= -2) {
9980                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9981                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9982                         gameInfo.variant != VariantBerolina || toX < fromX)
9983                       board[EP_STATUS] = toX | berolina;
9984                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9985                         gameInfo.variant != VariantBerolina || toX > fromX)
9986                       board[EP_STATUS] = toX;
9987            }
9988        }
9989
9990        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9991        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9992        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9993        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9994
9995        for(i=0; i<nrCastlingRights; i++) {
9996            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9997               board[CASTLING][i] == toX   && castlingRank[i] == toY
9998              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9999        }
10000
10001        if(gameInfo.variant == VariantSChess) { // update virginity
10002            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10003            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10004            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10005            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10006        }
10007
10008      if (fromX == toX && fromY == toY) return;
10009
10010      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10011      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10012      if(gameInfo.variant == VariantKnightmate)
10013          king += (int) WhiteUnicorn - (int) WhiteKing;
10014
10015     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10016        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10017         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10018         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10019         board[EP_STATUS] = EP_NONE; // capture was fake!
10020     } else
10021     /* Code added by Tord: */
10022     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10023     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10024         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10025       board[EP_STATUS] = EP_NONE; // capture was fake!
10026       board[fromY][fromX] = EmptySquare;
10027       board[toY][toX] = EmptySquare;
10028       if((toX > fromX) != (piece == WhiteRook)) {
10029         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10030       } else {
10031         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10032       }
10033     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10034                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10035       board[EP_STATUS] = EP_NONE;
10036       board[fromY][fromX] = EmptySquare;
10037       board[toY][toX] = EmptySquare;
10038       if((toX > fromX) != (piece == BlackRook)) {
10039         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10040       } else {
10041         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10042       }
10043     /* End of code added by Tord */
10044
10045     } else if (board[fromY][fromX] == king
10046         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10047         && toY == fromY && toX > fromX+1) {
10048         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10049         board[fromY][toX-1] = board[fromY][rookX];
10050         board[fromY][rookX] = EmptySquare;
10051         board[fromY][fromX] = EmptySquare;
10052         board[toY][toX] = king;
10053     } else if (board[fromY][fromX] == king
10054         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10055                && toY == fromY && toX < fromX-1) {
10056         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10057         board[fromY][toX+1] = board[fromY][rookX];
10058         board[fromY][rookX] = EmptySquare;
10059         board[fromY][fromX] = EmptySquare;
10060         board[toY][toX] = king;
10061     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10062                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10063                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10064                ) {
10065         /* white pawn promotion */
10066         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10067         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10068             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10069         board[fromY][fromX] = EmptySquare;
10070     } else if ((fromY >= BOARD_HEIGHT>>1)
10071                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10072                && (toX != fromX)
10073                && gameInfo.variant != VariantXiangqi
10074                && gameInfo.variant != VariantBerolina
10075                && (pawn == WhitePawn)
10076                && (board[toY][toX] == EmptySquare)) {
10077         board[fromY][fromX] = EmptySquare;
10078         board[toY][toX] = piece;
10079         if(toY == epRank - 128 + 1)
10080             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10081         else
10082             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10083     } else if ((fromY == BOARD_HEIGHT-4)
10084                && (toX == fromX)
10085                && gameInfo.variant == VariantBerolina
10086                && (board[fromY][fromX] == WhitePawn)
10087                && (board[toY][toX] == EmptySquare)) {
10088         board[fromY][fromX] = EmptySquare;
10089         board[toY][toX] = WhitePawn;
10090         if(oldEP & EP_BEROLIN_A) {
10091                 captured = board[fromY][fromX-1];
10092                 board[fromY][fromX-1] = EmptySquare;
10093         }else{  captured = board[fromY][fromX+1];
10094                 board[fromY][fromX+1] = EmptySquare;
10095         }
10096     } else if (board[fromY][fromX] == king
10097         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10098                && toY == fromY && toX > fromX+1) {
10099         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10100         board[fromY][toX-1] = board[fromY][rookX];
10101         board[fromY][rookX] = EmptySquare;
10102         board[fromY][fromX] = EmptySquare;
10103         board[toY][toX] = king;
10104     } else if (board[fromY][fromX] == king
10105         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10106                && toY == fromY && toX < fromX-1) {
10107         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10108         board[fromY][toX+1] = board[fromY][rookX];
10109         board[fromY][rookX] = EmptySquare;
10110         board[fromY][fromX] = EmptySquare;
10111         board[toY][toX] = king;
10112     } else if (fromY == 7 && fromX == 3
10113                && board[fromY][fromX] == BlackKing
10114                && toY == 7 && toX == 5) {
10115         board[fromY][fromX] = EmptySquare;
10116         board[toY][toX] = BlackKing;
10117         board[fromY][7] = EmptySquare;
10118         board[toY][4] = BlackRook;
10119     } else if (fromY == 7 && fromX == 3
10120                && board[fromY][fromX] == BlackKing
10121                && toY == 7 && toX == 1) {
10122         board[fromY][fromX] = EmptySquare;
10123         board[toY][toX] = BlackKing;
10124         board[fromY][0] = EmptySquare;
10125         board[toY][2] = BlackRook;
10126     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10127                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10128                && toY < promoRank && promoChar
10129                ) {
10130         /* black pawn promotion */
10131         board[toY][toX] = CharToPiece(ToLower(promoChar));
10132         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10133             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10134         board[fromY][fromX] = EmptySquare;
10135     } else if ((fromY < BOARD_HEIGHT>>1)
10136                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10137                && (toX != fromX)
10138                && gameInfo.variant != VariantXiangqi
10139                && gameInfo.variant != VariantBerolina
10140                && (pawn == BlackPawn)
10141                && (board[toY][toX] == EmptySquare)) {
10142         board[fromY][fromX] = EmptySquare;
10143         board[toY][toX] = piece;
10144         if(toY == epRank - 128 - 1)
10145             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10146         else
10147             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10148     } else if ((fromY == 3)
10149                && (toX == fromX)
10150                && gameInfo.variant == VariantBerolina
10151                && (board[fromY][fromX] == BlackPawn)
10152                && (board[toY][toX] == EmptySquare)) {
10153         board[fromY][fromX] = EmptySquare;
10154         board[toY][toX] = BlackPawn;
10155         if(oldEP & EP_BEROLIN_A) {
10156                 captured = board[fromY][fromX-1];
10157                 board[fromY][fromX-1] = EmptySquare;
10158         }else{  captured = board[fromY][fromX+1];
10159                 board[fromY][fromX+1] = EmptySquare;
10160         }
10161     } else {
10162         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10163         board[fromY][fromX] = EmptySquare;
10164         board[toY][toX] = piece;
10165     }
10166   }
10167
10168     if (gameInfo.holdingsWidth != 0) {
10169
10170       /* !!A lot more code needs to be written to support holdings  */
10171       /* [HGM] OK, so I have written it. Holdings are stored in the */
10172       /* penultimate board files, so they are automaticlly stored   */
10173       /* in the game history.                                       */
10174       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10175                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10176         /* Delete from holdings, by decreasing count */
10177         /* and erasing image if necessary            */
10178         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10179         if(p < (int) BlackPawn) { /* white drop */
10180              p -= (int)WhitePawn;
10181                  p = PieceToNumber((ChessSquare)p);
10182              if(p >= gameInfo.holdingsSize) p = 0;
10183              if(--board[p][BOARD_WIDTH-2] <= 0)
10184                   board[p][BOARD_WIDTH-1] = EmptySquare;
10185              if((int)board[p][BOARD_WIDTH-2] < 0)
10186                         board[p][BOARD_WIDTH-2] = 0;
10187         } else {                  /* black drop */
10188              p -= (int)BlackPawn;
10189                  p = PieceToNumber((ChessSquare)p);
10190              if(p >= gameInfo.holdingsSize) p = 0;
10191              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10192                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10193              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10194                         board[BOARD_HEIGHT-1-p][1] = 0;
10195         }
10196       }
10197       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10198           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10199         /* [HGM] holdings: Add to holdings, if holdings exist */
10200         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10201                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10202                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10203         }
10204         p = (int) captured;
10205         if (p >= (int) BlackPawn) {
10206           p -= (int)BlackPawn;
10207           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10208                   /* Restore shogi-promoted piece to its original  first */
10209                   captured = (ChessSquare) (DEMOTED captured);
10210                   p = DEMOTED p;
10211           }
10212           p = PieceToNumber((ChessSquare)p);
10213           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10214           board[p][BOARD_WIDTH-2]++;
10215           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10216         } else {
10217           p -= (int)WhitePawn;
10218           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10219                   captured = (ChessSquare) (DEMOTED captured);
10220                   p = DEMOTED p;
10221           }
10222           p = PieceToNumber((ChessSquare)p);
10223           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10224           board[BOARD_HEIGHT-1-p][1]++;
10225           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10226         }
10227       }
10228     } else if (gameInfo.variant == VariantAtomic) {
10229       if (captured != EmptySquare) {
10230         int y, x;
10231         for (y = toY-1; y <= toY+1; y++) {
10232           for (x = toX-1; x <= toX+1; x++) {
10233             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10234                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10235               board[y][x] = EmptySquare;
10236             }
10237           }
10238         }
10239         board[toY][toX] = EmptySquare;
10240       }
10241     }
10242
10243     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10244         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10245     } else
10246     if(promoChar == '+') {
10247         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10248         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10249         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10250           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10251     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10252         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10253         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10254            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10255         board[toY][toX] = newPiece;
10256     }
10257     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10258                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10259         // [HGM] superchess: take promotion piece out of holdings
10260         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10261         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10262             if(!--board[k][BOARD_WIDTH-2])
10263                 board[k][BOARD_WIDTH-1] = EmptySquare;
10264         } else {
10265             if(!--board[BOARD_HEIGHT-1-k][1])
10266                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10267         }
10268     }
10269 }
10270
10271 /* Updates forwardMostMove */
10272 void
10273 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10274 {
10275     int x = toX, y = toY;
10276     char *s = parseList[forwardMostMove];
10277     ChessSquare p = boards[forwardMostMove][toY][toX];
10278 //    forwardMostMove++; // [HGM] bare: moved downstream
10279
10280     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10281     (void) CoordsToAlgebraic(boards[forwardMostMove],
10282                              PosFlags(forwardMostMove),
10283                              fromY, fromX, y, x, promoChar,
10284                              s);
10285     if(killX >= 0 && killY >= 0)
10286         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10287
10288     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10289         int timeLeft; static int lastLoadFlag=0; int king, piece;
10290         piece = boards[forwardMostMove][fromY][fromX];
10291         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10292         if(gameInfo.variant == VariantKnightmate)
10293             king += (int) WhiteUnicorn - (int) WhiteKing;
10294         if(forwardMostMove == 0) {
10295             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10296                 fprintf(serverMoves, "%s;", UserName());
10297             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10298                 fprintf(serverMoves, "%s;", second.tidy);
10299             fprintf(serverMoves, "%s;", first.tidy);
10300             if(gameMode == MachinePlaysWhite)
10301                 fprintf(serverMoves, "%s;", UserName());
10302             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10303                 fprintf(serverMoves, "%s;", second.tidy);
10304         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10305         lastLoadFlag = loadFlag;
10306         // print base move
10307         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10308         // print castling suffix
10309         if( toY == fromY && piece == king ) {
10310             if(toX-fromX > 1)
10311                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10312             if(fromX-toX >1)
10313                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10314         }
10315         // e.p. suffix
10316         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10317              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10318              boards[forwardMostMove][toY][toX] == EmptySquare
10319              && fromX != toX && fromY != toY)
10320                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10321         // promotion suffix
10322         if(promoChar != NULLCHAR) {
10323             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10324                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10325                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10326             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10327         }
10328         if(!loadFlag) {
10329                 char buf[MOVE_LEN*2], *p; int len;
10330             fprintf(serverMoves, "/%d/%d",
10331                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10332             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10333             else                      timeLeft = blackTimeRemaining/1000;
10334             fprintf(serverMoves, "/%d", timeLeft);
10335                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10336                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10337                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10338                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10339             fprintf(serverMoves, "/%s", buf);
10340         }
10341         fflush(serverMoves);
10342     }
10343
10344     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10345         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10346       return;
10347     }
10348     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10349     if (commentList[forwardMostMove+1] != NULL) {
10350         free(commentList[forwardMostMove+1]);
10351         commentList[forwardMostMove+1] = NULL;
10352     }
10353     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10354     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10355     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10356     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10357     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10358     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10359     adjustedClock = FALSE;
10360     gameInfo.result = GameUnfinished;
10361     if (gameInfo.resultDetails != NULL) {
10362         free(gameInfo.resultDetails);
10363         gameInfo.resultDetails = NULL;
10364     }
10365     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10366                               moveList[forwardMostMove - 1]);
10367     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10368       case MT_NONE:
10369       case MT_STALEMATE:
10370       default:
10371         break;
10372       case MT_CHECK:
10373         if(!IS_SHOGI(gameInfo.variant))
10374             strcat(parseList[forwardMostMove - 1], "+");
10375         break;
10376       case MT_CHECKMATE:
10377       case MT_STAINMATE:
10378         strcat(parseList[forwardMostMove - 1], "#");
10379         break;
10380     }
10381 }
10382
10383 /* Updates currentMove if not pausing */
10384 void
10385 ShowMove (int fromX, int fromY, int toX, int toY)
10386 {
10387     int instant = (gameMode == PlayFromGameFile) ?
10388         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10389     if(appData.noGUI) return;
10390     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10391         if (!instant) {
10392             if (forwardMostMove == currentMove + 1) {
10393                 AnimateMove(boards[forwardMostMove - 1],
10394                             fromX, fromY, toX, toY);
10395             }
10396         }
10397         currentMove = forwardMostMove;
10398     }
10399
10400     killX = killY = -1; // [HGM] lion: used up
10401
10402     if (instant) return;
10403
10404     DisplayMove(currentMove - 1);
10405     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10406             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10407                 SetHighlights(fromX, fromY, toX, toY);
10408             }
10409     }
10410     DrawPosition(FALSE, boards[currentMove]);
10411     DisplayBothClocks();
10412     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10413 }
10414
10415 void
10416 SendEgtPath (ChessProgramState *cps)
10417 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10418         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10419
10420         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10421
10422         while(*p) {
10423             char c, *q = name+1, *r, *s;
10424
10425             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10426             while(*p && *p != ',') *q++ = *p++;
10427             *q++ = ':'; *q = 0;
10428             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10429                 strcmp(name, ",nalimov:") == 0 ) {
10430                 // take nalimov path from the menu-changeable option first, if it is defined
10431               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10432                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10433             } else
10434             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10435                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10436                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10437                 s = r = StrStr(s, ":") + 1; // beginning of path info
10438                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10439                 c = *r; *r = 0;             // temporarily null-terminate path info
10440                     *--q = 0;               // strip of trailig ':' from name
10441                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10442                 *r = c;
10443                 SendToProgram(buf,cps);     // send egtbpath command for this format
10444             }
10445             if(*p == ',') p++; // read away comma to position for next format name
10446         }
10447 }
10448
10449 static int
10450 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10451 {
10452       int width = 8, height = 8, holdings = 0;             // most common sizes
10453       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10454       // correct the deviations default for each variant
10455       if( v == VariantXiangqi ) width = 9,  height = 10;
10456       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10457       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10458       if( v == VariantCapablanca || v == VariantCapaRandom ||
10459           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10460                                 width = 10;
10461       if( v == VariantCourier ) width = 12;
10462       if( v == VariantSuper )                            holdings = 8;
10463       if( v == VariantGreat )   width = 10,              holdings = 8;
10464       if( v == VariantSChess )                           holdings = 7;
10465       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10466       if( v == VariantChuChess) width = 10, height = 10;
10467       if( v == VariantChu )     width = 12, height = 12;
10468       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10469              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10470              holdingsSize >= 0 && holdingsSize != holdings;
10471 }
10472
10473 char variantError[MSG_SIZ];
10474
10475 char *
10476 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10477 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10478       char *p, *variant = VariantName(v);
10479       static char b[MSG_SIZ];
10480       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10481            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10482                                                holdingsSize, variant); // cook up sized variant name
10483            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10484            if(StrStr(list, b) == NULL) {
10485                // specific sized variant not known, check if general sizing allowed
10486                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10487                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10488                             boardWidth, boardHeight, holdingsSize, engine);
10489                    return NULL;
10490                }
10491                /* [HGM] here we really should compare with the maximum supported board size */
10492            }
10493       } else snprintf(b, MSG_SIZ,"%s", variant);
10494       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10495       p = StrStr(list, b);
10496       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10497       if(p == NULL) {
10498           // occurs not at all in list, or only as sub-string
10499           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10500           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10501               int l = strlen(variantError);
10502               char *q;
10503               while(p != list && p[-1] != ',') p--;
10504               q = strchr(p, ',');
10505               if(q) *q = NULLCHAR;
10506               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10507               if(q) *q= ',';
10508           }
10509           return NULL;
10510       }
10511       return b;
10512 }
10513
10514 void
10515 InitChessProgram (ChessProgramState *cps, int setup)
10516 /* setup needed to setup FRC opening position */
10517 {
10518     char buf[MSG_SIZ], *b;
10519     if (appData.noChessProgram) return;
10520     hintRequested = FALSE;
10521     bookRequested = FALSE;
10522
10523     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10524     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10525     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10526     if(cps->memSize) { /* [HGM] memory */
10527       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10528         SendToProgram(buf, cps);
10529     }
10530     SendEgtPath(cps); /* [HGM] EGT */
10531     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10532       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10533         SendToProgram(buf, cps);
10534     }
10535
10536     setboardSpoiledMachineBlack = FALSE;
10537     SendToProgram(cps->initString, cps);
10538     if (gameInfo.variant != VariantNormal &&
10539         gameInfo.variant != VariantLoadable
10540         /* [HGM] also send variant if board size non-standard */
10541         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10542
10543       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10544                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10545       if (b == NULL) {
10546         VariantClass v;
10547         char c, *q = cps->variants, *p = strchr(q, ',');
10548         if(p) *p = NULLCHAR;
10549         v = StringToVariant(q);
10550         DisplayError(variantError, 0);
10551         if(v != VariantUnknown && cps == &first) {
10552             int w, h, s;
10553             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10554                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10555             ASSIGN(appData.variant, q);
10556             Reset(TRUE, FALSE);
10557         }
10558         if(p) *p = ',';
10559         return;
10560       }
10561
10562       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10563       SendToProgram(buf, cps);
10564     }
10565     currentlyInitializedVariant = gameInfo.variant;
10566
10567     /* [HGM] send opening position in FRC to first engine */
10568     if(setup) {
10569           SendToProgram("force\n", cps);
10570           SendBoard(cps, 0);
10571           /* engine is now in force mode! Set flag to wake it up after first move. */
10572           setboardSpoiledMachineBlack = 1;
10573     }
10574
10575     if (cps->sendICS) {
10576       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10577       SendToProgram(buf, cps);
10578     }
10579     cps->maybeThinking = FALSE;
10580     cps->offeredDraw = 0;
10581     if (!appData.icsActive) {
10582         SendTimeControl(cps, movesPerSession, timeControl,
10583                         timeIncrement, appData.searchDepth,
10584                         searchTime);
10585     }
10586     if (appData.showThinking
10587         // [HGM] thinking: four options require thinking output to be sent
10588         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10589                                 ) {
10590         SendToProgram("post\n", cps);
10591     }
10592     SendToProgram("hard\n", cps);
10593     if (!appData.ponderNextMove) {
10594         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10595            it without being sure what state we are in first.  "hard"
10596            is not a toggle, so that one is OK.
10597          */
10598         SendToProgram("easy\n", cps);
10599     }
10600     if (cps->usePing) {
10601       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10602       SendToProgram(buf, cps);
10603     }
10604     cps->initDone = TRUE;
10605     ClearEngineOutputPane(cps == &second);
10606 }
10607
10608
10609 void
10610 ResendOptions (ChessProgramState *cps)
10611 { // send the stored value of the options
10612   int i;
10613   char buf[MSG_SIZ];
10614   Option *opt = cps->option;
10615   for(i=0; i<cps->nrOptions; i++, opt++) {
10616       switch(opt->type) {
10617         case Spin:
10618         case Slider:
10619         case CheckBox:
10620             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10621           break;
10622         case ComboBox:
10623           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10624           break;
10625         default:
10626             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10627           break;
10628         case Button:
10629         case SaveButton:
10630           continue;
10631       }
10632       SendToProgram(buf, cps);
10633   }
10634 }
10635
10636 void
10637 StartChessProgram (ChessProgramState *cps)
10638 {
10639     char buf[MSG_SIZ];
10640     int err;
10641
10642     if (appData.noChessProgram) return;
10643     cps->initDone = FALSE;
10644
10645     if (strcmp(cps->host, "localhost") == 0) {
10646         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10647     } else if (*appData.remoteShell == NULLCHAR) {
10648         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10649     } else {
10650         if (*appData.remoteUser == NULLCHAR) {
10651           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10652                     cps->program);
10653         } else {
10654           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10655                     cps->host, appData.remoteUser, cps->program);
10656         }
10657         err = StartChildProcess(buf, "", &cps->pr);
10658     }
10659
10660     if (err != 0) {
10661       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10662         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10663         if(cps != &first) return;
10664         appData.noChessProgram = TRUE;
10665         ThawUI();
10666         SetNCPMode();
10667 //      DisplayFatalError(buf, err, 1);
10668 //      cps->pr = NoProc;
10669 //      cps->isr = NULL;
10670         return;
10671     }
10672
10673     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10674     if (cps->protocolVersion > 1) {
10675       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10676       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10677         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10678         cps->comboCnt = 0;  //                and values of combo boxes
10679       }
10680       SendToProgram(buf, cps);
10681       if(cps->reload) ResendOptions(cps);
10682     } else {
10683       SendToProgram("xboard\n", cps);
10684     }
10685 }
10686
10687 void
10688 TwoMachinesEventIfReady P((void))
10689 {
10690   static int curMess = 0;
10691   if (first.lastPing != first.lastPong) {
10692     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10693     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10694     return;
10695   }
10696   if (second.lastPing != second.lastPong) {
10697     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10698     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10699     return;
10700   }
10701   DisplayMessage("", ""); curMess = 0;
10702   TwoMachinesEvent();
10703 }
10704
10705 char *
10706 MakeName (char *template)
10707 {
10708     time_t clock;
10709     struct tm *tm;
10710     static char buf[MSG_SIZ];
10711     char *p = buf;
10712     int i;
10713
10714     clock = time((time_t *)NULL);
10715     tm = localtime(&clock);
10716
10717     while(*p++ = *template++) if(p[-1] == '%') {
10718         switch(*template++) {
10719           case 0:   *p = 0; return buf;
10720           case 'Y': i = tm->tm_year+1900; break;
10721           case 'y': i = tm->tm_year-100; break;
10722           case 'M': i = tm->tm_mon+1; break;
10723           case 'd': i = tm->tm_mday; break;
10724           case 'h': i = tm->tm_hour; break;
10725           case 'm': i = tm->tm_min; break;
10726           case 's': i = tm->tm_sec; break;
10727           default:  i = 0;
10728         }
10729         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10730     }
10731     return buf;
10732 }
10733
10734 int
10735 CountPlayers (char *p)
10736 {
10737     int n = 0;
10738     while(p = strchr(p, '\n')) p++, n++; // count participants
10739     return n;
10740 }
10741
10742 FILE *
10743 WriteTourneyFile (char *results, FILE *f)
10744 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10745     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10746     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10747         // create a file with tournament description
10748         fprintf(f, "-participants {%s}\n", appData.participants);
10749         fprintf(f, "-seedBase %d\n", appData.seedBase);
10750         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10751         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10752         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10753         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10754         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10755         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10756         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10757         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10758         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10759         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10760         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10761         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10762         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10763         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10764         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10765         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10766         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10767         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10768         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10769         fprintf(f, "-smpCores %d\n", appData.smpCores);
10770         if(searchTime > 0)
10771                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10772         else {
10773                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10774                 fprintf(f, "-tc %s\n", appData.timeControl);
10775                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10776         }
10777         fprintf(f, "-results \"%s\"\n", results);
10778     }
10779     return f;
10780 }
10781
10782 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10783
10784 void
10785 Substitute (char *participants, int expunge)
10786 {
10787     int i, changed, changes=0, nPlayers=0;
10788     char *p, *q, *r, buf[MSG_SIZ];
10789     if(participants == NULL) return;
10790     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10791     r = p = participants; q = appData.participants;
10792     while(*p && *p == *q) {
10793         if(*p == '\n') r = p+1, nPlayers++;
10794         p++; q++;
10795     }
10796     if(*p) { // difference
10797         while(*p && *p++ != '\n');
10798         while(*q && *q++ != '\n');
10799       changed = nPlayers;
10800         changes = 1 + (strcmp(p, q) != 0);
10801     }
10802     if(changes == 1) { // a single engine mnemonic was changed
10803         q = r; while(*q) nPlayers += (*q++ == '\n');
10804         p = buf; while(*r && (*p = *r++) != '\n') p++;
10805         *p = NULLCHAR;
10806         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10807         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10808         if(mnemonic[i]) { // The substitute is valid
10809             FILE *f;
10810             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10811                 flock(fileno(f), LOCK_EX);
10812                 ParseArgsFromFile(f);
10813                 fseek(f, 0, SEEK_SET);
10814                 FREE(appData.participants); appData.participants = participants;
10815                 if(expunge) { // erase results of replaced engine
10816                     int len = strlen(appData.results), w, b, dummy;
10817                     for(i=0; i<len; i++) {
10818                         Pairing(i, nPlayers, &w, &b, &dummy);
10819                         if((w == changed || b == changed) && appData.results[i] == '*') {
10820                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10821                             fclose(f);
10822                             return;
10823                         }
10824                     }
10825                     for(i=0; i<len; i++) {
10826                         Pairing(i, nPlayers, &w, &b, &dummy);
10827                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10828                     }
10829                 }
10830                 WriteTourneyFile(appData.results, f);
10831                 fclose(f); // release lock
10832                 return;
10833             }
10834         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10835     }
10836     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10837     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10838     free(participants);
10839     return;
10840 }
10841
10842 int
10843 CheckPlayers (char *participants)
10844 {
10845         int i;
10846         char buf[MSG_SIZ], *p;
10847         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10848         while(p = strchr(participants, '\n')) {
10849             *p = NULLCHAR;
10850             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10851             if(!mnemonic[i]) {
10852                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10853                 *p = '\n';
10854                 DisplayError(buf, 0);
10855                 return 1;
10856             }
10857             *p = '\n';
10858             participants = p + 1;
10859         }
10860         return 0;
10861 }
10862
10863 int
10864 CreateTourney (char *name)
10865 {
10866         FILE *f;
10867         if(matchMode && strcmp(name, appData.tourneyFile)) {
10868              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10869         }
10870         if(name[0] == NULLCHAR) {
10871             if(appData.participants[0])
10872                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10873             return 0;
10874         }
10875         f = fopen(name, "r");
10876         if(f) { // file exists
10877             ASSIGN(appData.tourneyFile, name);
10878             ParseArgsFromFile(f); // parse it
10879         } else {
10880             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10881             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10882                 DisplayError(_("Not enough participants"), 0);
10883                 return 0;
10884             }
10885             if(CheckPlayers(appData.participants)) return 0;
10886             ASSIGN(appData.tourneyFile, name);
10887             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10888             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10889         }
10890         fclose(f);
10891         appData.noChessProgram = FALSE;
10892         appData.clockMode = TRUE;
10893         SetGNUMode();
10894         return 1;
10895 }
10896
10897 int
10898 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10899 {
10900     char buf[MSG_SIZ], *p, *q;
10901     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10902     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10903     skip = !all && group[0]; // if group requested, we start in skip mode
10904     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10905         p = names; q = buf; header = 0;
10906         while(*p && *p != '\n') *q++ = *p++;
10907         *q = 0;
10908         if(*p == '\n') p++;
10909         if(buf[0] == '#') {
10910             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10911             depth++; // we must be entering a new group
10912             if(all) continue; // suppress printing group headers when complete list requested
10913             header = 1;
10914             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10915         }
10916         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10917         if(engineList[i]) free(engineList[i]);
10918         engineList[i] = strdup(buf);
10919         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10920         if(engineMnemonic[i]) free(engineMnemonic[i]);
10921         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10922             strcat(buf, " (");
10923             sscanf(q + 8, "%s", buf + strlen(buf));
10924             strcat(buf, ")");
10925         }
10926         engineMnemonic[i] = strdup(buf);
10927         i++;
10928     }
10929     engineList[i] = engineMnemonic[i] = NULL;
10930     return i;
10931 }
10932
10933 // following implemented as macro to avoid type limitations
10934 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10935
10936 void
10937 SwapEngines (int n)
10938 {   // swap settings for first engine and other engine (so far only some selected options)
10939     int h;
10940     char *p;
10941     if(n == 0) return;
10942     SWAP(directory, p)
10943     SWAP(chessProgram, p)
10944     SWAP(isUCI, h)
10945     SWAP(hasOwnBookUCI, h)
10946     SWAP(protocolVersion, h)
10947     SWAP(reuse, h)
10948     SWAP(scoreIsAbsolute, h)
10949     SWAP(timeOdds, h)
10950     SWAP(logo, p)
10951     SWAP(pgnName, p)
10952     SWAP(pvSAN, h)
10953     SWAP(engOptions, p)
10954     SWAP(engInitString, p)
10955     SWAP(computerString, p)
10956     SWAP(features, p)
10957     SWAP(fenOverride, p)
10958     SWAP(NPS, h)
10959     SWAP(accumulateTC, h)
10960     SWAP(drawDepth, h)
10961     SWAP(host, p)
10962     SWAP(pseudo, h)
10963 }
10964
10965 int
10966 GetEngineLine (char *s, int n)
10967 {
10968     int i;
10969     char buf[MSG_SIZ];
10970     extern char *icsNames;
10971     if(!s || !*s) return 0;
10972     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10973     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10974     if(!mnemonic[i]) return 0;
10975     if(n == 11) return 1; // just testing if there was a match
10976     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10977     if(n == 1) SwapEngines(n);
10978     ParseArgsFromString(buf);
10979     if(n == 1) SwapEngines(n);
10980     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10981         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10982         ParseArgsFromString(buf);
10983     }
10984     return 1;
10985 }
10986
10987 int
10988 SetPlayer (int player, char *p)
10989 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10990     int i;
10991     char buf[MSG_SIZ], *engineName;
10992     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10993     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10994     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10995     if(mnemonic[i]) {
10996         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10997         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10998         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10999         ParseArgsFromString(buf);
11000     } else { // no engine with this nickname is installed!
11001         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11002         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11003         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11004         ModeHighlight();
11005         DisplayError(buf, 0);
11006         return 0;
11007     }
11008     free(engineName);
11009     return i;
11010 }
11011
11012 char *recentEngines;
11013
11014 void
11015 RecentEngineEvent (int nr)
11016 {
11017     int n;
11018 //    SwapEngines(1); // bump first to second
11019 //    ReplaceEngine(&second, 1); // and load it there
11020     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11021     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11022     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11023         ReplaceEngine(&first, 0);
11024         FloatToFront(&appData.recentEngineList, command[n]);
11025     }
11026 }
11027
11028 int
11029 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11030 {   // determine players from game number
11031     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11032
11033     if(appData.tourneyType == 0) {
11034         roundsPerCycle = (nPlayers - 1) | 1;
11035         pairingsPerRound = nPlayers / 2;
11036     } else if(appData.tourneyType > 0) {
11037         roundsPerCycle = nPlayers - appData.tourneyType;
11038         pairingsPerRound = appData.tourneyType;
11039     }
11040     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11041     gamesPerCycle = gamesPerRound * roundsPerCycle;
11042     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11043     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11044     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11045     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11046     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11047     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11048
11049     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11050     if(appData.roundSync) *syncInterval = gamesPerRound;
11051
11052     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11053
11054     if(appData.tourneyType == 0) {
11055         if(curPairing == (nPlayers-1)/2 ) {
11056             *whitePlayer = curRound;
11057             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11058         } else {
11059             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11060             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11061             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11062             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11063         }
11064     } else if(appData.tourneyType > 1) {
11065         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11066         *whitePlayer = curRound + appData.tourneyType;
11067     } else if(appData.tourneyType > 0) {
11068         *whitePlayer = curPairing;
11069         *blackPlayer = curRound + appData.tourneyType;
11070     }
11071
11072     // take care of white/black alternation per round.
11073     // For cycles and games this is already taken care of by default, derived from matchGame!
11074     return curRound & 1;
11075 }
11076
11077 int
11078 NextTourneyGame (int nr, int *swapColors)
11079 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11080     char *p, *q;
11081     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11082     FILE *tf;
11083     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11084     tf = fopen(appData.tourneyFile, "r");
11085     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11086     ParseArgsFromFile(tf); fclose(tf);
11087     InitTimeControls(); // TC might be altered from tourney file
11088
11089     nPlayers = CountPlayers(appData.participants); // count participants
11090     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11091     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11092
11093     if(syncInterval) {
11094         p = q = appData.results;
11095         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11096         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11097             DisplayMessage(_("Waiting for other game(s)"),"");
11098             waitingForGame = TRUE;
11099             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11100             return 0;
11101         }
11102         waitingForGame = FALSE;
11103     }
11104
11105     if(appData.tourneyType < 0) {
11106         if(nr>=0 && !pairingReceived) {
11107             char buf[1<<16];
11108             if(pairing.pr == NoProc) {
11109                 if(!appData.pairingEngine[0]) {
11110                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11111                     return 0;
11112                 }
11113                 StartChessProgram(&pairing); // starts the pairing engine
11114             }
11115             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11116             SendToProgram(buf, &pairing);
11117             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11118             SendToProgram(buf, &pairing);
11119             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11120         }
11121         pairingReceived = 0;                              // ... so we continue here
11122         *swapColors = 0;
11123         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11124         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11125         matchGame = 1; roundNr = nr / syncInterval + 1;
11126     }
11127
11128     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11129
11130     // redefine engines, engine dir, etc.
11131     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11132     if(first.pr == NoProc) {
11133       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11134       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11135     }
11136     if(second.pr == NoProc) {
11137       SwapEngines(1);
11138       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11139       SwapEngines(1);         // and make that valid for second engine by swapping
11140       InitEngine(&second, 1);
11141     }
11142     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11143     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11144     return OK;
11145 }
11146
11147 void
11148 NextMatchGame ()
11149 {   // performs game initialization that does not invoke engines, and then tries to start the game
11150     int res, firstWhite, swapColors = 0;
11151     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11152     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
11153         char buf[MSG_SIZ];
11154         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11155         if(strcmp(buf, currentDebugFile)) { // name has changed
11156             FILE *f = fopen(buf, "w");
11157             if(f) { // if opening the new file failed, just keep using the old one
11158                 ASSIGN(currentDebugFile, buf);
11159                 fclose(debugFP);
11160                 debugFP = f;
11161             }
11162             if(appData.serverFileName) {
11163                 if(serverFP) fclose(serverFP);
11164                 serverFP = fopen(appData.serverFileName, "w");
11165                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11166                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11167             }
11168         }
11169     }
11170     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11171     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11172     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11173     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11174     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11175     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11176     Reset(FALSE, first.pr != NoProc);
11177     res = LoadGameOrPosition(matchGame); // setup game
11178     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11179     if(!res) return; // abort when bad game/pos file
11180     TwoMachinesEvent();
11181 }
11182
11183 void
11184 UserAdjudicationEvent (int result)
11185 {
11186     ChessMove gameResult = GameIsDrawn;
11187
11188     if( result > 0 ) {
11189         gameResult = WhiteWins;
11190     }
11191     else if( result < 0 ) {
11192         gameResult = BlackWins;
11193     }
11194
11195     if( gameMode == TwoMachinesPlay ) {
11196         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11197     }
11198 }
11199
11200
11201 // [HGM] save: calculate checksum of game to make games easily identifiable
11202 int
11203 StringCheckSum (char *s)
11204 {
11205         int i = 0;
11206         if(s==NULL) return 0;
11207         while(*s) i = i*259 + *s++;
11208         return i;
11209 }
11210
11211 int
11212 GameCheckSum ()
11213 {
11214         int i, sum=0;
11215         for(i=backwardMostMove; i<forwardMostMove; i++) {
11216                 sum += pvInfoList[i].depth;
11217                 sum += StringCheckSum(parseList[i]);
11218                 sum += StringCheckSum(commentList[i]);
11219                 sum *= 261;
11220         }
11221         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11222         return sum + StringCheckSum(commentList[i]);
11223 } // end of save patch
11224
11225 void
11226 GameEnds (ChessMove result, char *resultDetails, int whosays)
11227 {
11228     GameMode nextGameMode;
11229     int isIcsGame;
11230     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11231
11232     if(endingGame) return; /* [HGM] crash: forbid recursion */
11233     endingGame = 1;
11234     if(twoBoards) { // [HGM] dual: switch back to one board
11235         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11236         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11237     }
11238     if (appData.debugMode) {
11239       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11240               result, resultDetails ? resultDetails : "(null)", whosays);
11241     }
11242
11243     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11244
11245     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11246
11247     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11248         /* If we are playing on ICS, the server decides when the
11249            game is over, but the engine can offer to draw, claim
11250            a draw, or resign.
11251          */
11252 #if ZIPPY
11253         if (appData.zippyPlay && first.initDone) {
11254             if (result == GameIsDrawn) {
11255                 /* In case draw still needs to be claimed */
11256                 SendToICS(ics_prefix);
11257                 SendToICS("draw\n");
11258             } else if (StrCaseStr(resultDetails, "resign")) {
11259                 SendToICS(ics_prefix);
11260                 SendToICS("resign\n");
11261             }
11262         }
11263 #endif
11264         endingGame = 0; /* [HGM] crash */
11265         return;
11266     }
11267
11268     /* If we're loading the game from a file, stop */
11269     if (whosays == GE_FILE) {
11270       (void) StopLoadGameTimer();
11271       gameFileFP = NULL;
11272     }
11273
11274     /* Cancel draw offers */
11275     first.offeredDraw = second.offeredDraw = 0;
11276
11277     /* If this is an ICS game, only ICS can really say it's done;
11278        if not, anyone can. */
11279     isIcsGame = (gameMode == IcsPlayingWhite ||
11280                  gameMode == IcsPlayingBlack ||
11281                  gameMode == IcsObserving    ||
11282                  gameMode == IcsExamining);
11283
11284     if (!isIcsGame || whosays == GE_ICS) {
11285         /* OK -- not an ICS game, or ICS said it was done */
11286         StopClocks();
11287         if (!isIcsGame && !appData.noChessProgram)
11288           SetUserThinkingEnables();
11289
11290         /* [HGM] if a machine claims the game end we verify this claim */
11291         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11292             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11293                 char claimer;
11294                 ChessMove trueResult = (ChessMove) -1;
11295
11296                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11297                                             first.twoMachinesColor[0] :
11298                                             second.twoMachinesColor[0] ;
11299
11300                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11301                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11302                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11303                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11304                 } else
11305                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11306                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11307                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11308                 } else
11309                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11310                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11311                 }
11312
11313                 // now verify win claims, but not in drop games, as we don't understand those yet
11314                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11315                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11316                     (result == WhiteWins && claimer == 'w' ||
11317                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11318                       if (appData.debugMode) {
11319                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11320                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11321                       }
11322                       if(result != trueResult) {
11323                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11324                               result = claimer == 'w' ? BlackWins : WhiteWins;
11325                               resultDetails = buf;
11326                       }
11327                 } else
11328                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11329                     && (forwardMostMove <= backwardMostMove ||
11330                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11331                         (claimer=='b')==(forwardMostMove&1))
11332                                                                                   ) {
11333                       /* [HGM] verify: draws that were not flagged are false claims */
11334                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11335                       result = claimer == 'w' ? BlackWins : WhiteWins;
11336                       resultDetails = buf;
11337                 }
11338                 /* (Claiming a loss is accepted no questions asked!) */
11339             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11340                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11341                 result = GameUnfinished;
11342                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11343             }
11344             /* [HGM] bare: don't allow bare King to win */
11345             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11346                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11347                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11348                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11349                && result != GameIsDrawn)
11350             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11351                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11352                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11353                         if(p >= 0 && p <= (int)WhiteKing) k++;
11354                 }
11355                 if (appData.debugMode) {
11356                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11357                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11358                 }
11359                 if(k <= 1) {
11360                         result = GameIsDrawn;
11361                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11362                         resultDetails = buf;
11363                 }
11364             }
11365         }
11366
11367
11368         if(serverMoves != NULL && !loadFlag) { char c = '=';
11369             if(result==WhiteWins) c = '+';
11370             if(result==BlackWins) c = '-';
11371             if(resultDetails != NULL)
11372                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11373         }
11374         if (resultDetails != NULL) {
11375             gameInfo.result = result;
11376             gameInfo.resultDetails = StrSave(resultDetails);
11377
11378             /* display last move only if game was not loaded from file */
11379             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11380                 DisplayMove(currentMove - 1);
11381
11382             if (forwardMostMove != 0) {
11383                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11384                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11385                                                                 ) {
11386                     if (*appData.saveGameFile != NULLCHAR) {
11387                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11388                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11389                         else
11390                         SaveGameToFile(appData.saveGameFile, TRUE);
11391                     } else if (appData.autoSaveGames) {
11392                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11393                     }
11394                     if (*appData.savePositionFile != NULLCHAR) {
11395                         SavePositionToFile(appData.savePositionFile);
11396                     }
11397                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11398                 }
11399             }
11400
11401             /* Tell program how game ended in case it is learning */
11402             /* [HGM] Moved this to after saving the PGN, just in case */
11403             /* engine died and we got here through time loss. In that */
11404             /* case we will get a fatal error writing the pipe, which */
11405             /* would otherwise lose us the PGN.                       */
11406             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11407             /* output during GameEnds should never be fatal anymore   */
11408             if (gameMode == MachinePlaysWhite ||
11409                 gameMode == MachinePlaysBlack ||
11410                 gameMode == TwoMachinesPlay ||
11411                 gameMode == IcsPlayingWhite ||
11412                 gameMode == IcsPlayingBlack ||
11413                 gameMode == BeginningOfGame) {
11414                 char buf[MSG_SIZ];
11415                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11416                         resultDetails);
11417                 if (first.pr != NoProc) {
11418                     SendToProgram(buf, &first);
11419                 }
11420                 if (second.pr != NoProc &&
11421                     gameMode == TwoMachinesPlay) {
11422                     SendToProgram(buf, &second);
11423                 }
11424             }
11425         }
11426
11427         if (appData.icsActive) {
11428             if (appData.quietPlay &&
11429                 (gameMode == IcsPlayingWhite ||
11430                  gameMode == IcsPlayingBlack)) {
11431                 SendToICS(ics_prefix);
11432                 SendToICS("set shout 1\n");
11433             }
11434             nextGameMode = IcsIdle;
11435             ics_user_moved = FALSE;
11436             /* clean up premove.  It's ugly when the game has ended and the
11437              * premove highlights are still on the board.
11438              */
11439             if (gotPremove) {
11440               gotPremove = FALSE;
11441               ClearPremoveHighlights();
11442               DrawPosition(FALSE, boards[currentMove]);
11443             }
11444             if (whosays == GE_ICS) {
11445                 switch (result) {
11446                 case WhiteWins:
11447                     if (gameMode == IcsPlayingWhite)
11448                         PlayIcsWinSound();
11449                     else if(gameMode == IcsPlayingBlack)
11450                         PlayIcsLossSound();
11451                     break;
11452                 case BlackWins:
11453                     if (gameMode == IcsPlayingBlack)
11454                         PlayIcsWinSound();
11455                     else if(gameMode == IcsPlayingWhite)
11456                         PlayIcsLossSound();
11457                     break;
11458                 case GameIsDrawn:
11459                     PlayIcsDrawSound();
11460                     break;
11461                 default:
11462                     PlayIcsUnfinishedSound();
11463                 }
11464             }
11465             if(appData.quitNext) { ExitEvent(0); return; }
11466         } else if (gameMode == EditGame ||
11467                    gameMode == PlayFromGameFile ||
11468                    gameMode == AnalyzeMode ||
11469                    gameMode == AnalyzeFile) {
11470             nextGameMode = gameMode;
11471         } else {
11472             nextGameMode = EndOfGame;
11473         }
11474         pausing = FALSE;
11475         ModeHighlight();
11476     } else {
11477         nextGameMode = gameMode;
11478     }
11479
11480     if (appData.noChessProgram) {
11481         gameMode = nextGameMode;
11482         ModeHighlight();
11483         endingGame = 0; /* [HGM] crash */
11484         return;
11485     }
11486
11487     if (first.reuse) {
11488         /* Put first chess program into idle state */
11489         if (first.pr != NoProc &&
11490             (gameMode == MachinePlaysWhite ||
11491              gameMode == MachinePlaysBlack ||
11492              gameMode == TwoMachinesPlay ||
11493              gameMode == IcsPlayingWhite ||
11494              gameMode == IcsPlayingBlack ||
11495              gameMode == BeginningOfGame)) {
11496             SendToProgram("force\n", &first);
11497             if (first.usePing) {
11498               char buf[MSG_SIZ];
11499               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11500               SendToProgram(buf, &first);
11501             }
11502         }
11503     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11504         /* Kill off first chess program */
11505         if (first.isr != NULL)
11506           RemoveInputSource(first.isr);
11507         first.isr = NULL;
11508
11509         if (first.pr != NoProc) {
11510             ExitAnalyzeMode();
11511             DoSleep( appData.delayBeforeQuit );
11512             SendToProgram("quit\n", &first);
11513             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11514             first.reload = TRUE;
11515         }
11516         first.pr = NoProc;
11517     }
11518     if (second.reuse) {
11519         /* Put second chess program into idle state */
11520         if (second.pr != NoProc &&
11521             gameMode == TwoMachinesPlay) {
11522             SendToProgram("force\n", &second);
11523             if (second.usePing) {
11524               char buf[MSG_SIZ];
11525               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11526               SendToProgram(buf, &second);
11527             }
11528         }
11529     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11530         /* Kill off second chess program */
11531         if (second.isr != NULL)
11532           RemoveInputSource(second.isr);
11533         second.isr = NULL;
11534
11535         if (second.pr != NoProc) {
11536             DoSleep( appData.delayBeforeQuit );
11537             SendToProgram("quit\n", &second);
11538             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11539             second.reload = TRUE;
11540         }
11541         second.pr = NoProc;
11542     }
11543
11544     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11545         char resChar = '=';
11546         switch (result) {
11547         case WhiteWins:
11548           resChar = '+';
11549           if (first.twoMachinesColor[0] == 'w') {
11550             first.matchWins++;
11551           } else {
11552             second.matchWins++;
11553           }
11554           break;
11555         case BlackWins:
11556           resChar = '-';
11557           if (first.twoMachinesColor[0] == 'b') {
11558             first.matchWins++;
11559           } else {
11560             second.matchWins++;
11561           }
11562           break;
11563         case GameUnfinished:
11564           resChar = ' ';
11565         default:
11566           break;
11567         }
11568
11569         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11570         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11571             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11572             ReserveGame(nextGame, resChar); // sets nextGame
11573             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11574             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11575         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11576
11577         if (nextGame <= appData.matchGames && !abortMatch) {
11578             gameMode = nextGameMode;
11579             matchGame = nextGame; // this will be overruled in tourney mode!
11580             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11581             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11582             endingGame = 0; /* [HGM] crash */
11583             return;
11584         } else {
11585             gameMode = nextGameMode;
11586             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11587                      first.tidy, second.tidy,
11588                      first.matchWins, second.matchWins,
11589                      appData.matchGames - (first.matchWins + second.matchWins));
11590             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11591             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11592             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11593             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11594                 first.twoMachinesColor = "black\n";
11595                 second.twoMachinesColor = "white\n";
11596             } else {
11597                 first.twoMachinesColor = "white\n";
11598                 second.twoMachinesColor = "black\n";
11599             }
11600         }
11601     }
11602     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11603         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11604       ExitAnalyzeMode();
11605     gameMode = nextGameMode;
11606     ModeHighlight();
11607     endingGame = 0;  /* [HGM] crash */
11608     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11609         if(matchMode == TRUE) { // match through command line: exit with or without popup
11610             if(ranking) {
11611                 ToNrEvent(forwardMostMove);
11612                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11613                 else ExitEvent(0);
11614             } else DisplayFatalError(buf, 0, 0);
11615         } else { // match through menu; just stop, with or without popup
11616             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11617             ModeHighlight();
11618             if(ranking){
11619                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11620             } else DisplayNote(buf);
11621       }
11622       if(ranking) free(ranking);
11623     }
11624 }
11625
11626 /* Assumes program was just initialized (initString sent).
11627    Leaves program in force mode. */
11628 void
11629 FeedMovesToProgram (ChessProgramState *cps, int upto)
11630 {
11631     int i;
11632
11633     if (appData.debugMode)
11634       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11635               startedFromSetupPosition ? "position and " : "",
11636               backwardMostMove, upto, cps->which);
11637     if(currentlyInitializedVariant != gameInfo.variant) {
11638       char buf[MSG_SIZ];
11639         // [HGM] variantswitch: make engine aware of new variant
11640         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11641                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11642                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11643         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11644         SendToProgram(buf, cps);
11645         currentlyInitializedVariant = gameInfo.variant;
11646     }
11647     SendToProgram("force\n", cps);
11648     if (startedFromSetupPosition) {
11649         SendBoard(cps, backwardMostMove);
11650     if (appData.debugMode) {
11651         fprintf(debugFP, "feedMoves\n");
11652     }
11653     }
11654     for (i = backwardMostMove; i < upto; i++) {
11655         SendMoveToProgram(i, cps);
11656     }
11657 }
11658
11659
11660 int
11661 ResurrectChessProgram ()
11662 {
11663      /* The chess program may have exited.
11664         If so, restart it and feed it all the moves made so far. */
11665     static int doInit = 0;
11666
11667     if (appData.noChessProgram) return 1;
11668
11669     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11670         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11671         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11672         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11673     } else {
11674         if (first.pr != NoProc) return 1;
11675         StartChessProgram(&first);
11676     }
11677     InitChessProgram(&first, FALSE);
11678     FeedMovesToProgram(&first, currentMove);
11679
11680     if (!first.sendTime) {
11681         /* can't tell gnuchess what its clock should read,
11682            so we bow to its notion. */
11683         ResetClocks();
11684         timeRemaining[0][currentMove] = whiteTimeRemaining;
11685         timeRemaining[1][currentMove] = blackTimeRemaining;
11686     }
11687
11688     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11689                 appData.icsEngineAnalyze) && first.analysisSupport) {
11690       SendToProgram("analyze\n", &first);
11691       first.analyzing = TRUE;
11692     }
11693     return 1;
11694 }
11695
11696 /*
11697  * Button procedures
11698  */
11699 void
11700 Reset (int redraw, int init)
11701 {
11702     int i;
11703
11704     if (appData.debugMode) {
11705         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11706                 redraw, init, gameMode);
11707     }
11708     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11709     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11710     CleanupTail(); // [HGM] vari: delete any stored variations
11711     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11712     pausing = pauseExamInvalid = FALSE;
11713     startedFromSetupPosition = blackPlaysFirst = FALSE;
11714     firstMove = TRUE;
11715     whiteFlag = blackFlag = FALSE;
11716     userOfferedDraw = FALSE;
11717     hintRequested = bookRequested = FALSE;
11718     first.maybeThinking = FALSE;
11719     second.maybeThinking = FALSE;
11720     first.bookSuspend = FALSE; // [HGM] book
11721     second.bookSuspend = FALSE;
11722     thinkOutput[0] = NULLCHAR;
11723     lastHint[0] = NULLCHAR;
11724     ClearGameInfo(&gameInfo);
11725     gameInfo.variant = StringToVariant(appData.variant);
11726     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11727     ics_user_moved = ics_clock_paused = FALSE;
11728     ics_getting_history = H_FALSE;
11729     ics_gamenum = -1;
11730     white_holding[0] = black_holding[0] = NULLCHAR;
11731     ClearProgramStats();
11732     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11733
11734     ResetFrontEnd();
11735     ClearHighlights();
11736     flipView = appData.flipView;
11737     ClearPremoveHighlights();
11738     gotPremove = FALSE;
11739     alarmSounded = FALSE;
11740     killX = killY = -1; // [HGM] lion
11741
11742     GameEnds(EndOfFile, NULL, GE_PLAYER);
11743     if(appData.serverMovesName != NULL) {
11744         /* [HGM] prepare to make moves file for broadcasting */
11745         clock_t t = clock();
11746         if(serverMoves != NULL) fclose(serverMoves);
11747         serverMoves = fopen(appData.serverMovesName, "r");
11748         if(serverMoves != NULL) {
11749             fclose(serverMoves);
11750             /* delay 15 sec before overwriting, so all clients can see end */
11751             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11752         }
11753         serverMoves = fopen(appData.serverMovesName, "w");
11754     }
11755
11756     ExitAnalyzeMode();
11757     gameMode = BeginningOfGame;
11758     ModeHighlight();
11759     if(appData.icsActive) gameInfo.variant = VariantNormal;
11760     currentMove = forwardMostMove = backwardMostMove = 0;
11761     MarkTargetSquares(1);
11762     InitPosition(redraw);
11763     for (i = 0; i < MAX_MOVES; i++) {
11764         if (commentList[i] != NULL) {
11765             free(commentList[i]);
11766             commentList[i] = NULL;
11767         }
11768     }
11769     ResetClocks();
11770     timeRemaining[0][0] = whiteTimeRemaining;
11771     timeRemaining[1][0] = blackTimeRemaining;
11772
11773     if (first.pr == NoProc) {
11774         StartChessProgram(&first);
11775     }
11776     if (init) {
11777             InitChessProgram(&first, startedFromSetupPosition);
11778     }
11779     DisplayTitle("");
11780     DisplayMessage("", "");
11781     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11782     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11783     ClearMap();        // [HGM] exclude: invalidate map
11784 }
11785
11786 void
11787 AutoPlayGameLoop ()
11788 {
11789     for (;;) {
11790         if (!AutoPlayOneMove())
11791           return;
11792         if (matchMode || appData.timeDelay == 0)
11793           continue;
11794         if (appData.timeDelay < 0)
11795           return;
11796         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11797         break;
11798     }
11799 }
11800
11801 void
11802 AnalyzeNextGame()
11803 {
11804     ReloadGame(1); // next game
11805 }
11806
11807 int
11808 AutoPlayOneMove ()
11809 {
11810     int fromX, fromY, toX, toY;
11811
11812     if (appData.debugMode) {
11813       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11814     }
11815
11816     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11817       return FALSE;
11818
11819     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11820       pvInfoList[currentMove].depth = programStats.depth;
11821       pvInfoList[currentMove].score = programStats.score;
11822       pvInfoList[currentMove].time  = 0;
11823       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11824       else { // append analysis of final position as comment
11825         char buf[MSG_SIZ];
11826         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11827         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11828       }
11829       programStats.depth = 0;
11830     }
11831
11832     if (currentMove >= forwardMostMove) {
11833       if(gameMode == AnalyzeFile) {
11834           if(appData.loadGameIndex == -1) {
11835             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11836           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11837           } else {
11838           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11839         }
11840       }
11841 //      gameMode = EndOfGame;
11842 //      ModeHighlight();
11843
11844       /* [AS] Clear current move marker at the end of a game */
11845       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11846
11847       return FALSE;
11848     }
11849
11850     toX = moveList[currentMove][2] - AAA;
11851     toY = moveList[currentMove][3] - ONE;
11852
11853     if (moveList[currentMove][1] == '@') {
11854         if (appData.highlightLastMove) {
11855             SetHighlights(-1, -1, toX, toY);
11856         }
11857     } else {
11858         int viaX = moveList[currentMove][5] - AAA;
11859         int viaY = moveList[currentMove][6] - ONE;
11860         fromX = moveList[currentMove][0] - AAA;
11861         fromY = moveList[currentMove][1] - ONE;
11862
11863         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11864
11865         if(moveList[currentMove][4] == ';') { // multi-leg
11866             ChessSquare piece = boards[currentMove][viaY][viaX];
11867             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11868             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11869             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11870             boards[currentMove][viaY][viaX] = piece;
11871         } else
11872         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11873
11874         if (appData.highlightLastMove) {
11875             SetHighlights(fromX, fromY, toX, toY);
11876         }
11877     }
11878     DisplayMove(currentMove);
11879     SendMoveToProgram(currentMove++, &first);
11880     DisplayBothClocks();
11881     DrawPosition(FALSE, boards[currentMove]);
11882     // [HGM] PV info: always display, routine tests if empty
11883     DisplayComment(currentMove - 1, commentList[currentMove]);
11884     return TRUE;
11885 }
11886
11887
11888 int
11889 LoadGameOneMove (ChessMove readAhead)
11890 {
11891     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11892     char promoChar = NULLCHAR;
11893     ChessMove moveType;
11894     char move[MSG_SIZ];
11895     char *p, *q;
11896
11897     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11898         gameMode != AnalyzeMode && gameMode != Training) {
11899         gameFileFP = NULL;
11900         return FALSE;
11901     }
11902
11903     yyboardindex = forwardMostMove;
11904     if (readAhead != EndOfFile) {
11905       moveType = readAhead;
11906     } else {
11907       if (gameFileFP == NULL)
11908           return FALSE;
11909       moveType = (ChessMove) Myylex();
11910     }
11911
11912     done = FALSE;
11913     switch (moveType) {
11914       case Comment:
11915         if (appData.debugMode)
11916           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11917         p = yy_text;
11918
11919         /* append the comment but don't display it */
11920         AppendComment(currentMove, p, FALSE);
11921         return TRUE;
11922
11923       case WhiteCapturesEnPassant:
11924       case BlackCapturesEnPassant:
11925       case WhitePromotion:
11926       case BlackPromotion:
11927       case WhiteNonPromotion:
11928       case BlackNonPromotion:
11929       case NormalMove:
11930       case FirstLeg:
11931       case WhiteKingSideCastle:
11932       case WhiteQueenSideCastle:
11933       case BlackKingSideCastle:
11934       case BlackQueenSideCastle:
11935       case WhiteKingSideCastleWild:
11936       case WhiteQueenSideCastleWild:
11937       case BlackKingSideCastleWild:
11938       case BlackQueenSideCastleWild:
11939       /* PUSH Fabien */
11940       case WhiteHSideCastleFR:
11941       case WhiteASideCastleFR:
11942       case BlackHSideCastleFR:
11943       case BlackASideCastleFR:
11944       /* POP Fabien */
11945         if (appData.debugMode)
11946           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11947         fromX = currentMoveString[0] - AAA;
11948         fromY = currentMoveString[1] - ONE;
11949         toX = currentMoveString[2] - AAA;
11950         toY = currentMoveString[3] - ONE;
11951         promoChar = currentMoveString[4];
11952         if(promoChar == ';') promoChar = NULLCHAR;
11953         break;
11954
11955       case WhiteDrop:
11956       case BlackDrop:
11957         if (appData.debugMode)
11958           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11959         fromX = moveType == WhiteDrop ?
11960           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11961         (int) CharToPiece(ToLower(currentMoveString[0]));
11962         fromY = DROP_RANK;
11963         toX = currentMoveString[2] - AAA;
11964         toY = currentMoveString[3] - ONE;
11965         break;
11966
11967       case WhiteWins:
11968       case BlackWins:
11969       case GameIsDrawn:
11970       case GameUnfinished:
11971         if (appData.debugMode)
11972           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11973         p = strchr(yy_text, '{');
11974         if (p == NULL) p = strchr(yy_text, '(');
11975         if (p == NULL) {
11976             p = yy_text;
11977             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11978         } else {
11979             q = strchr(p, *p == '{' ? '}' : ')');
11980             if (q != NULL) *q = NULLCHAR;
11981             p++;
11982         }
11983         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11984         GameEnds(moveType, p, GE_FILE);
11985         done = TRUE;
11986         if (cmailMsgLoaded) {
11987             ClearHighlights();
11988             flipView = WhiteOnMove(currentMove);
11989             if (moveType == GameUnfinished) flipView = !flipView;
11990             if (appData.debugMode)
11991               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11992         }
11993         break;
11994
11995       case EndOfFile:
11996         if (appData.debugMode)
11997           fprintf(debugFP, "Parser hit end of file\n");
11998         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11999           case MT_NONE:
12000           case MT_CHECK:
12001             break;
12002           case MT_CHECKMATE:
12003           case MT_STAINMATE:
12004             if (WhiteOnMove(currentMove)) {
12005                 GameEnds(BlackWins, "Black mates", GE_FILE);
12006             } else {
12007                 GameEnds(WhiteWins, "White mates", GE_FILE);
12008             }
12009             break;
12010           case MT_STALEMATE:
12011             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12012             break;
12013         }
12014         done = TRUE;
12015         break;
12016
12017       case MoveNumberOne:
12018         if (lastLoadGameStart == GNUChessGame) {
12019             /* GNUChessGames have numbers, but they aren't move numbers */
12020             if (appData.debugMode)
12021               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12022                       yy_text, (int) moveType);
12023             return LoadGameOneMove(EndOfFile); /* tail recursion */
12024         }
12025         /* else fall thru */
12026
12027       case XBoardGame:
12028       case GNUChessGame:
12029       case PGNTag:
12030         /* Reached start of next game in file */
12031         if (appData.debugMode)
12032           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12033         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12034           case MT_NONE:
12035           case MT_CHECK:
12036             break;
12037           case MT_CHECKMATE:
12038           case MT_STAINMATE:
12039             if (WhiteOnMove(currentMove)) {
12040                 GameEnds(BlackWins, "Black mates", GE_FILE);
12041             } else {
12042                 GameEnds(WhiteWins, "White mates", GE_FILE);
12043             }
12044             break;
12045           case MT_STALEMATE:
12046             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12047             break;
12048         }
12049         done = TRUE;
12050         break;
12051
12052       case PositionDiagram:     /* should not happen; ignore */
12053       case ElapsedTime:         /* ignore */
12054       case NAG:                 /* ignore */
12055         if (appData.debugMode)
12056           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12057                   yy_text, (int) moveType);
12058         return LoadGameOneMove(EndOfFile); /* tail recursion */
12059
12060       case IllegalMove:
12061         if (appData.testLegality) {
12062             if (appData.debugMode)
12063               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12064             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12065                     (forwardMostMove / 2) + 1,
12066                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12067             DisplayError(move, 0);
12068             done = TRUE;
12069         } else {
12070             if (appData.debugMode)
12071               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12072                       yy_text, currentMoveString);
12073             fromX = currentMoveString[0] - AAA;
12074             fromY = currentMoveString[1] - ONE;
12075             toX = currentMoveString[2] - AAA;
12076             toY = currentMoveString[3] - ONE;
12077             promoChar = currentMoveString[4];
12078         }
12079         break;
12080
12081       case AmbiguousMove:
12082         if (appData.debugMode)
12083           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12084         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12085                 (forwardMostMove / 2) + 1,
12086                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12087         DisplayError(move, 0);
12088         done = TRUE;
12089         break;
12090
12091       default:
12092       case ImpossibleMove:
12093         if (appData.debugMode)
12094           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12095         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12096                 (forwardMostMove / 2) + 1,
12097                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12098         DisplayError(move, 0);
12099         done = TRUE;
12100         break;
12101     }
12102
12103     if (done) {
12104         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12105             DrawPosition(FALSE, boards[currentMove]);
12106             DisplayBothClocks();
12107             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12108               DisplayComment(currentMove - 1, commentList[currentMove]);
12109         }
12110         (void) StopLoadGameTimer();
12111         gameFileFP = NULL;
12112         cmailOldMove = forwardMostMove;
12113         return FALSE;
12114     } else {
12115         /* currentMoveString is set as a side-effect of yylex */
12116
12117         thinkOutput[0] = NULLCHAR;
12118         MakeMove(fromX, fromY, toX, toY, promoChar);
12119         killX = killY = -1; // [HGM] lion: used up
12120         currentMove = forwardMostMove;
12121         return TRUE;
12122     }
12123 }
12124
12125 /* Load the nth game from the given file */
12126 int
12127 LoadGameFromFile (char *filename, int n, char *title, int useList)
12128 {
12129     FILE *f;
12130     char buf[MSG_SIZ];
12131
12132     if (strcmp(filename, "-") == 0) {
12133         f = stdin;
12134         title = "stdin";
12135     } else {
12136         f = fopen(filename, "rb");
12137         if (f == NULL) {
12138           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12139             DisplayError(buf, errno);
12140             return FALSE;
12141         }
12142     }
12143     if (fseek(f, 0, 0) == -1) {
12144         /* f is not seekable; probably a pipe */
12145         useList = FALSE;
12146     }
12147     if (useList && n == 0) {
12148         int error = GameListBuild(f);
12149         if (error) {
12150             DisplayError(_("Cannot build game list"), error);
12151         } else if (!ListEmpty(&gameList) &&
12152                    ((ListGame *) gameList.tailPred)->number > 1) {
12153             GameListPopUp(f, title);
12154             return TRUE;
12155         }
12156         GameListDestroy();
12157         n = 1;
12158     }
12159     if (n == 0) n = 1;
12160     return LoadGame(f, n, title, FALSE);
12161 }
12162
12163
12164 void
12165 MakeRegisteredMove ()
12166 {
12167     int fromX, fromY, toX, toY;
12168     char promoChar;
12169     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12170         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12171           case CMAIL_MOVE:
12172           case CMAIL_DRAW:
12173             if (appData.debugMode)
12174               fprintf(debugFP, "Restoring %s for game %d\n",
12175                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12176
12177             thinkOutput[0] = NULLCHAR;
12178             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12179             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12180             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12181             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12182             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12183             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12184             MakeMove(fromX, fromY, toX, toY, promoChar);
12185             ShowMove(fromX, fromY, toX, toY);
12186
12187             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12188               case MT_NONE:
12189               case MT_CHECK:
12190                 break;
12191
12192               case MT_CHECKMATE:
12193               case MT_STAINMATE:
12194                 if (WhiteOnMove(currentMove)) {
12195                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12196                 } else {
12197                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12198                 }
12199                 break;
12200
12201               case MT_STALEMATE:
12202                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12203                 break;
12204             }
12205
12206             break;
12207
12208           case CMAIL_RESIGN:
12209             if (WhiteOnMove(currentMove)) {
12210                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12211             } else {
12212                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12213             }
12214             break;
12215
12216           case CMAIL_ACCEPT:
12217             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12218             break;
12219
12220           default:
12221             break;
12222         }
12223     }
12224
12225     return;
12226 }
12227
12228 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12229 int
12230 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12231 {
12232     int retVal;
12233
12234     if (gameNumber > nCmailGames) {
12235         DisplayError(_("No more games in this message"), 0);
12236         return FALSE;
12237     }
12238     if (f == lastLoadGameFP) {
12239         int offset = gameNumber - lastLoadGameNumber;
12240         if (offset == 0) {
12241             cmailMsg[0] = NULLCHAR;
12242             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12243                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12244                 nCmailMovesRegistered--;
12245             }
12246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12247             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12248                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12249             }
12250         } else {
12251             if (! RegisterMove()) return FALSE;
12252         }
12253     }
12254
12255     retVal = LoadGame(f, gameNumber, title, useList);
12256
12257     /* Make move registered during previous look at this game, if any */
12258     MakeRegisteredMove();
12259
12260     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12261         commentList[currentMove]
12262           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12263         DisplayComment(currentMove - 1, commentList[currentMove]);
12264     }
12265
12266     return retVal;
12267 }
12268
12269 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12270 int
12271 ReloadGame (int offset)
12272 {
12273     int gameNumber = lastLoadGameNumber + offset;
12274     if (lastLoadGameFP == NULL) {
12275         DisplayError(_("No game has been loaded yet"), 0);
12276         return FALSE;
12277     }
12278     if (gameNumber <= 0) {
12279         DisplayError(_("Can't back up any further"), 0);
12280         return FALSE;
12281     }
12282     if (cmailMsgLoaded) {
12283         return CmailLoadGame(lastLoadGameFP, gameNumber,
12284                              lastLoadGameTitle, lastLoadGameUseList);
12285     } else {
12286         return LoadGame(lastLoadGameFP, gameNumber,
12287                         lastLoadGameTitle, lastLoadGameUseList);
12288     }
12289 }
12290
12291 int keys[EmptySquare+1];
12292
12293 int
12294 PositionMatches (Board b1, Board b2)
12295 {
12296     int r, f, sum=0;
12297     switch(appData.searchMode) {
12298         case 1: return CompareWithRights(b1, b2);
12299         case 2:
12300             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12301                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12302             }
12303             return TRUE;
12304         case 3:
12305             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12306               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12307                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12308             }
12309             return sum==0;
12310         case 4:
12311             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12312                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12313             }
12314             return sum==0;
12315     }
12316     return TRUE;
12317 }
12318
12319 #define Q_PROMO  4
12320 #define Q_EP     3
12321 #define Q_BCASTL 2
12322 #define Q_WCASTL 1
12323
12324 int pieceList[256], quickBoard[256];
12325 ChessSquare pieceType[256] = { EmptySquare };
12326 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12327 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12328 int soughtTotal, turn;
12329 Boolean epOK, flipSearch;
12330
12331 typedef struct {
12332     unsigned char piece, to;
12333 } Move;
12334
12335 #define DSIZE (250000)
12336
12337 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12338 Move *moveDatabase = initialSpace;
12339 unsigned int movePtr, dataSize = DSIZE;
12340
12341 int
12342 MakePieceList (Board board, int *counts)
12343 {
12344     int r, f, n=Q_PROMO, total=0;
12345     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12346     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12347         int sq = f + (r<<4);
12348         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12349             quickBoard[sq] = ++n;
12350             pieceList[n] = sq;
12351             pieceType[n] = board[r][f];
12352             counts[board[r][f]]++;
12353             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12354             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12355             total++;
12356         }
12357     }
12358     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12359     return total;
12360 }
12361
12362 void
12363 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12364 {
12365     int sq = fromX + (fromY<<4);
12366     int piece = quickBoard[sq], rook;
12367     quickBoard[sq] = 0;
12368     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12369     if(piece == pieceList[1] && fromY == toY) {
12370       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12371         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12372         moveDatabase[movePtr++].piece = Q_WCASTL;
12373         quickBoard[sq] = piece;
12374         piece = quickBoard[from]; quickBoard[from] = 0;
12375         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12376       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12377         quickBoard[sq] = 0; // remove Rook
12378         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12379         moveDatabase[movePtr++].piece = Q_WCASTL;
12380         quickBoard[sq] = pieceList[1]; // put King
12381         piece = rook;
12382         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12383       }
12384     } else
12385     if(piece == pieceList[2] && fromY == toY) {
12386       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12387         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12388         moveDatabase[movePtr++].piece = Q_BCASTL;
12389         quickBoard[sq] = piece;
12390         piece = quickBoard[from]; quickBoard[from] = 0;
12391         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12392       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12393         quickBoard[sq] = 0; // remove Rook
12394         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12395         moveDatabase[movePtr++].piece = Q_BCASTL;
12396         quickBoard[sq] = pieceList[2]; // put King
12397         piece = rook;
12398         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12399       }
12400     } else
12401     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12402         quickBoard[(fromY<<4)+toX] = 0;
12403         moveDatabase[movePtr].piece = Q_EP;
12404         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12405         moveDatabase[movePtr].to = sq;
12406     } else
12407     if(promoPiece != pieceType[piece]) {
12408         moveDatabase[movePtr++].piece = Q_PROMO;
12409         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12410     }
12411     moveDatabase[movePtr].piece = piece;
12412     quickBoard[sq] = piece;
12413     movePtr++;
12414 }
12415
12416 int
12417 PackGame (Board board)
12418 {
12419     Move *newSpace = NULL;
12420     moveDatabase[movePtr].piece = 0; // terminate previous game
12421     if(movePtr > dataSize) {
12422         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12423         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12424         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12425         if(newSpace) {
12426             int i;
12427             Move *p = moveDatabase, *q = newSpace;
12428             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12429             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12430             moveDatabase = newSpace;
12431         } else { // calloc failed, we must be out of memory. Too bad...
12432             dataSize = 0; // prevent calloc events for all subsequent games
12433             return 0;     // and signal this one isn't cached
12434         }
12435     }
12436     movePtr++;
12437     MakePieceList(board, counts);
12438     return movePtr;
12439 }
12440
12441 int
12442 QuickCompare (Board board, int *minCounts, int *maxCounts)
12443 {   // compare according to search mode
12444     int r, f;
12445     switch(appData.searchMode)
12446     {
12447       case 1: // exact position match
12448         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12449         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12450             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12451         }
12452         break;
12453       case 2: // can have extra material on empty squares
12454         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12455             if(board[r][f] == EmptySquare) continue;
12456             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12457         }
12458         break;
12459       case 3: // material with exact Pawn structure
12460         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12461             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12462             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12463         } // fall through to material comparison
12464       case 4: // exact material
12465         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12466         break;
12467       case 6: // material range with given imbalance
12468         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12469         // fall through to range comparison
12470       case 5: // material range
12471         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12472     }
12473     return TRUE;
12474 }
12475
12476 int
12477 QuickScan (Board board, Move *move)
12478 {   // reconstruct game,and compare all positions in it
12479     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12480     do {
12481         int piece = move->piece;
12482         int to = move->to, from = pieceList[piece];
12483         if(found < 0) { // if already found just scan to game end for final piece count
12484           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12485            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12486            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12487                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12488             ) {
12489             static int lastCounts[EmptySquare+1];
12490             int i;
12491             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12492             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12493           } else stretch = 0;
12494           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12495           if(found >= 0 && !appData.minPieces) return found;
12496         }
12497         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12498           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12499           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12500             piece = (++move)->piece;
12501             from = pieceList[piece];
12502             counts[pieceType[piece]]--;
12503             pieceType[piece] = (ChessSquare) move->to;
12504             counts[move->to]++;
12505           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12506             counts[pieceType[quickBoard[to]]]--;
12507             quickBoard[to] = 0; total--;
12508             move++;
12509             continue;
12510           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12511             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12512             from  = pieceList[piece]; // so this must be King
12513             quickBoard[from] = 0;
12514             pieceList[piece] = to;
12515             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12516             quickBoard[from] = 0; // rook
12517             quickBoard[to] = piece;
12518             to = move->to; piece = move->piece;
12519             goto aftercastle;
12520           }
12521         }
12522         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12523         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12524         quickBoard[from] = 0;
12525       aftercastle:
12526         quickBoard[to] = piece;
12527         pieceList[piece] = to;
12528         cnt++; turn ^= 3;
12529         move++;
12530     } while(1);
12531 }
12532
12533 void
12534 InitSearch ()
12535 {
12536     int r, f;
12537     flipSearch = FALSE;
12538     CopyBoard(soughtBoard, boards[currentMove]);
12539     soughtTotal = MakePieceList(soughtBoard, maxSought);
12540     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12541     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12542     CopyBoard(reverseBoard, boards[currentMove]);
12543     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12544         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12545         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12546         reverseBoard[r][f] = piece;
12547     }
12548     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12549     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12550     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12551                  || (boards[currentMove][CASTLING][2] == NoRights ||
12552                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12553                  && (boards[currentMove][CASTLING][5] == NoRights ||
12554                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12555       ) {
12556         flipSearch = TRUE;
12557         CopyBoard(flipBoard, soughtBoard);
12558         CopyBoard(rotateBoard, reverseBoard);
12559         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12560             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12561             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12562         }
12563     }
12564     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12565     if(appData.searchMode >= 5) {
12566         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12567         MakePieceList(soughtBoard, minSought);
12568         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12569     }
12570     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12571         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12572 }
12573
12574 GameInfo dummyInfo;
12575 static int creatingBook;
12576
12577 int
12578 GameContainsPosition (FILE *f, ListGame *lg)
12579 {
12580     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12581     int fromX, fromY, toX, toY;
12582     char promoChar;
12583     static int initDone=FALSE;
12584
12585     // weed out games based on numerical tag comparison
12586     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12587     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12588     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12589     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12590     if(!initDone) {
12591         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12592         initDone = TRUE;
12593     }
12594     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12595     else CopyBoard(boards[scratch], initialPosition); // default start position
12596     if(lg->moves) {
12597         turn = btm + 1;
12598         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12599         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12600     }
12601     if(btm) plyNr++;
12602     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12603     fseek(f, lg->offset, 0);
12604     yynewfile(f);
12605     while(1) {
12606         yyboardindex = scratch;
12607         quickFlag = plyNr+1;
12608         next = Myylex();
12609         quickFlag = 0;
12610         switch(next) {
12611             case PGNTag:
12612                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12613             default:
12614                 continue;
12615
12616             case XBoardGame:
12617             case GNUChessGame:
12618                 if(plyNr) return -1; // after we have seen moves, this is for new game
12619               continue;
12620
12621             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12622             case ImpossibleMove:
12623             case WhiteWins: // game ends here with these four
12624             case BlackWins:
12625             case GameIsDrawn:
12626             case GameUnfinished:
12627                 return -1;
12628
12629             case IllegalMove:
12630                 if(appData.testLegality) return -1;
12631             case WhiteCapturesEnPassant:
12632             case BlackCapturesEnPassant:
12633             case WhitePromotion:
12634             case BlackPromotion:
12635             case WhiteNonPromotion:
12636             case BlackNonPromotion:
12637             case NormalMove:
12638             case FirstLeg:
12639             case WhiteKingSideCastle:
12640             case WhiteQueenSideCastle:
12641             case BlackKingSideCastle:
12642             case BlackQueenSideCastle:
12643             case WhiteKingSideCastleWild:
12644             case WhiteQueenSideCastleWild:
12645             case BlackKingSideCastleWild:
12646             case BlackQueenSideCastleWild:
12647             case WhiteHSideCastleFR:
12648             case WhiteASideCastleFR:
12649             case BlackHSideCastleFR:
12650             case BlackASideCastleFR:
12651                 fromX = currentMoveString[0] - AAA;
12652                 fromY = currentMoveString[1] - ONE;
12653                 toX = currentMoveString[2] - AAA;
12654                 toY = currentMoveString[3] - ONE;
12655                 promoChar = currentMoveString[4];
12656                 break;
12657             case WhiteDrop:
12658             case BlackDrop:
12659                 fromX = next == WhiteDrop ?
12660                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12661                   (int) CharToPiece(ToLower(currentMoveString[0]));
12662                 fromY = DROP_RANK;
12663                 toX = currentMoveString[2] - AAA;
12664                 toY = currentMoveString[3] - ONE;
12665                 promoChar = 0;
12666                 break;
12667         }
12668         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12669         plyNr++;
12670         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12671         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12672         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12673         if(appData.findMirror) {
12674             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12675             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12676         }
12677     }
12678 }
12679
12680 /* Load the nth game from open file f */
12681 int
12682 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12683 {
12684     ChessMove cm;
12685     char buf[MSG_SIZ];
12686     int gn = gameNumber;
12687     ListGame *lg = NULL;
12688     int numPGNTags = 0;
12689     int err, pos = -1;
12690     GameMode oldGameMode;
12691     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12692
12693     if (appData.debugMode)
12694         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12695
12696     if (gameMode == Training )
12697         SetTrainingModeOff();
12698
12699     oldGameMode = gameMode;
12700     if (gameMode != BeginningOfGame) {
12701       Reset(FALSE, TRUE);
12702     }
12703     killX = killY = -1; // [HGM] lion: in case we did not Reset
12704
12705     gameFileFP = f;
12706     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12707         fclose(lastLoadGameFP);
12708     }
12709
12710     if (useList) {
12711         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12712
12713         if (lg) {
12714             fseek(f, lg->offset, 0);
12715             GameListHighlight(gameNumber);
12716             pos = lg->position;
12717             gn = 1;
12718         }
12719         else {
12720             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12721               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12722             else
12723             DisplayError(_("Game number out of range"), 0);
12724             return FALSE;
12725         }
12726     } else {
12727         GameListDestroy();
12728         if (fseek(f, 0, 0) == -1) {
12729             if (f == lastLoadGameFP ?
12730                 gameNumber == lastLoadGameNumber + 1 :
12731                 gameNumber == 1) {
12732                 gn = 1;
12733             } else {
12734                 DisplayError(_("Can't seek on game file"), 0);
12735                 return FALSE;
12736             }
12737         }
12738     }
12739     lastLoadGameFP = f;
12740     lastLoadGameNumber = gameNumber;
12741     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12742     lastLoadGameUseList = useList;
12743
12744     yynewfile(f);
12745
12746     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12747       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12748                 lg->gameInfo.black);
12749             DisplayTitle(buf);
12750     } else if (*title != NULLCHAR) {
12751         if (gameNumber > 1) {
12752           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12753             DisplayTitle(buf);
12754         } else {
12755             DisplayTitle(title);
12756         }
12757     }
12758
12759     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12760         gameMode = PlayFromGameFile;
12761         ModeHighlight();
12762     }
12763
12764     currentMove = forwardMostMove = backwardMostMove = 0;
12765     CopyBoard(boards[0], initialPosition);
12766     StopClocks();
12767
12768     /*
12769      * Skip the first gn-1 games in the file.
12770      * Also skip over anything that precedes an identifiable
12771      * start of game marker, to avoid being confused by
12772      * garbage at the start of the file.  Currently
12773      * recognized start of game markers are the move number "1",
12774      * the pattern "gnuchess .* game", the pattern
12775      * "^[#;%] [^ ]* game file", and a PGN tag block.
12776      * A game that starts with one of the latter two patterns
12777      * will also have a move number 1, possibly
12778      * following a position diagram.
12779      * 5-4-02: Let's try being more lenient and allowing a game to
12780      * start with an unnumbered move.  Does that break anything?
12781      */
12782     cm = lastLoadGameStart = EndOfFile;
12783     while (gn > 0) {
12784         yyboardindex = forwardMostMove;
12785         cm = (ChessMove) Myylex();
12786         switch (cm) {
12787           case EndOfFile:
12788             if (cmailMsgLoaded) {
12789                 nCmailGames = CMAIL_MAX_GAMES - gn;
12790             } else {
12791                 Reset(TRUE, TRUE);
12792                 DisplayError(_("Game not found in file"), 0);
12793             }
12794             return FALSE;
12795
12796           case GNUChessGame:
12797           case XBoardGame:
12798             gn--;
12799             lastLoadGameStart = cm;
12800             break;
12801
12802           case MoveNumberOne:
12803             switch (lastLoadGameStart) {
12804               case GNUChessGame:
12805               case XBoardGame:
12806               case PGNTag:
12807                 break;
12808               case MoveNumberOne:
12809               case EndOfFile:
12810                 gn--;           /* count this game */
12811                 lastLoadGameStart = cm;
12812                 break;
12813               default:
12814                 /* impossible */
12815                 break;
12816             }
12817             break;
12818
12819           case PGNTag:
12820             switch (lastLoadGameStart) {
12821               case GNUChessGame:
12822               case PGNTag:
12823               case MoveNumberOne:
12824               case EndOfFile:
12825                 gn--;           /* count this game */
12826                 lastLoadGameStart = cm;
12827                 break;
12828               case XBoardGame:
12829                 lastLoadGameStart = cm; /* game counted already */
12830                 break;
12831               default:
12832                 /* impossible */
12833                 break;
12834             }
12835             if (gn > 0) {
12836                 do {
12837                     yyboardindex = forwardMostMove;
12838                     cm = (ChessMove) Myylex();
12839                 } while (cm == PGNTag || cm == Comment);
12840             }
12841             break;
12842
12843           case WhiteWins:
12844           case BlackWins:
12845           case GameIsDrawn:
12846             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12847                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12848                     != CMAIL_OLD_RESULT) {
12849                     nCmailResults ++ ;
12850                     cmailResult[  CMAIL_MAX_GAMES
12851                                 - gn - 1] = CMAIL_OLD_RESULT;
12852                 }
12853             }
12854             break;
12855
12856           case NormalMove:
12857           case FirstLeg:
12858             /* Only a NormalMove can be at the start of a game
12859              * without a position diagram. */
12860             if (lastLoadGameStart == EndOfFile ) {
12861               gn--;
12862               lastLoadGameStart = MoveNumberOne;
12863             }
12864             break;
12865
12866           default:
12867             break;
12868         }
12869     }
12870
12871     if (appData.debugMode)
12872       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12873
12874     if (cm == XBoardGame) {
12875         /* Skip any header junk before position diagram and/or move 1 */
12876         for (;;) {
12877             yyboardindex = forwardMostMove;
12878             cm = (ChessMove) Myylex();
12879
12880             if (cm == EndOfFile ||
12881                 cm == GNUChessGame || cm == XBoardGame) {
12882                 /* Empty game; pretend end-of-file and handle later */
12883                 cm = EndOfFile;
12884                 break;
12885             }
12886
12887             if (cm == MoveNumberOne || cm == PositionDiagram ||
12888                 cm == PGNTag || cm == Comment)
12889               break;
12890         }
12891     } else if (cm == GNUChessGame) {
12892         if (gameInfo.event != NULL) {
12893             free(gameInfo.event);
12894         }
12895         gameInfo.event = StrSave(yy_text);
12896     }
12897
12898     startedFromSetupPosition = FALSE;
12899     while (cm == PGNTag) {
12900         if (appData.debugMode)
12901           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12902         err = ParsePGNTag(yy_text, &gameInfo);
12903         if (!err) numPGNTags++;
12904
12905         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12906         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12907             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12908             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12909             InitPosition(TRUE);
12910             oldVariant = gameInfo.variant;
12911             if (appData.debugMode)
12912               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12913         }
12914
12915
12916         if (gameInfo.fen != NULL) {
12917           Board initial_position;
12918           startedFromSetupPosition = TRUE;
12919           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12920             Reset(TRUE, TRUE);
12921             DisplayError(_("Bad FEN position in file"), 0);
12922             return FALSE;
12923           }
12924           CopyBoard(boards[0], initial_position);
12925           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12926             CopyBoard(initialPosition, initial_position);
12927           if (blackPlaysFirst) {
12928             currentMove = forwardMostMove = backwardMostMove = 1;
12929             CopyBoard(boards[1], initial_position);
12930             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12931             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12932             timeRemaining[0][1] = whiteTimeRemaining;
12933             timeRemaining[1][1] = blackTimeRemaining;
12934             if (commentList[0] != NULL) {
12935               commentList[1] = commentList[0];
12936               commentList[0] = NULL;
12937             }
12938           } else {
12939             currentMove = forwardMostMove = backwardMostMove = 0;
12940           }
12941           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12942           {   int i;
12943               initialRulePlies = FENrulePlies;
12944               for( i=0; i< nrCastlingRights; i++ )
12945                   initialRights[i] = initial_position[CASTLING][i];
12946           }
12947           yyboardindex = forwardMostMove;
12948           free(gameInfo.fen);
12949           gameInfo.fen = NULL;
12950         }
12951
12952         yyboardindex = forwardMostMove;
12953         cm = (ChessMove) Myylex();
12954
12955         /* Handle comments interspersed among the tags */
12956         while (cm == Comment) {
12957             char *p;
12958             if (appData.debugMode)
12959               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12960             p = yy_text;
12961             AppendComment(currentMove, p, FALSE);
12962             yyboardindex = forwardMostMove;
12963             cm = (ChessMove) Myylex();
12964         }
12965     }
12966
12967     /* don't rely on existence of Event tag since if game was
12968      * pasted from clipboard the Event tag may not exist
12969      */
12970     if (numPGNTags > 0){
12971         char *tags;
12972         if (gameInfo.variant == VariantNormal) {
12973           VariantClass v = StringToVariant(gameInfo.event);
12974           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12975           if(v < VariantShogi) gameInfo.variant = v;
12976         }
12977         if (!matchMode) {
12978           if( appData.autoDisplayTags ) {
12979             tags = PGNTags(&gameInfo);
12980             TagsPopUp(tags, CmailMsg());
12981             free(tags);
12982           }
12983         }
12984     } else {
12985         /* Make something up, but don't display it now */
12986         SetGameInfo();
12987         TagsPopDown();
12988     }
12989
12990     if (cm == PositionDiagram) {
12991         int i, j;
12992         char *p;
12993         Board initial_position;
12994
12995         if (appData.debugMode)
12996           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12997
12998         if (!startedFromSetupPosition) {
12999             p = yy_text;
13000             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13001               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13002                 switch (*p) {
13003                   case '{':
13004                   case '[':
13005                   case '-':
13006                   case ' ':
13007                   case '\t':
13008                   case '\n':
13009                   case '\r':
13010                     break;
13011                   default:
13012                     initial_position[i][j++] = CharToPiece(*p);
13013                     break;
13014                 }
13015             while (*p == ' ' || *p == '\t' ||
13016                    *p == '\n' || *p == '\r') p++;
13017
13018             if (strncmp(p, "black", strlen("black"))==0)
13019               blackPlaysFirst = TRUE;
13020             else
13021               blackPlaysFirst = FALSE;
13022             startedFromSetupPosition = TRUE;
13023
13024             CopyBoard(boards[0], initial_position);
13025             if (blackPlaysFirst) {
13026                 currentMove = forwardMostMove = backwardMostMove = 1;
13027                 CopyBoard(boards[1], initial_position);
13028                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13029                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13030                 timeRemaining[0][1] = whiteTimeRemaining;
13031                 timeRemaining[1][1] = blackTimeRemaining;
13032                 if (commentList[0] != NULL) {
13033                     commentList[1] = commentList[0];
13034                     commentList[0] = NULL;
13035                 }
13036             } else {
13037                 currentMove = forwardMostMove = backwardMostMove = 0;
13038             }
13039         }
13040         yyboardindex = forwardMostMove;
13041         cm = (ChessMove) Myylex();
13042     }
13043
13044   if(!creatingBook) {
13045     if (first.pr == NoProc) {
13046         StartChessProgram(&first);
13047     }
13048     InitChessProgram(&first, FALSE);
13049     SendToProgram("force\n", &first);
13050     if (startedFromSetupPosition) {
13051         SendBoard(&first, forwardMostMove);
13052     if (appData.debugMode) {
13053         fprintf(debugFP, "Load Game\n");
13054     }
13055         DisplayBothClocks();
13056     }
13057   }
13058
13059     /* [HGM] server: flag to write setup moves in broadcast file as one */
13060     loadFlag = appData.suppressLoadMoves;
13061
13062     while (cm == Comment) {
13063         char *p;
13064         if (appData.debugMode)
13065           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13066         p = yy_text;
13067         AppendComment(currentMove, p, FALSE);
13068         yyboardindex = forwardMostMove;
13069         cm = (ChessMove) Myylex();
13070     }
13071
13072     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13073         cm == WhiteWins || cm == BlackWins ||
13074         cm == GameIsDrawn || cm == GameUnfinished) {
13075         DisplayMessage("", _("No moves in game"));
13076         if (cmailMsgLoaded) {
13077             if (appData.debugMode)
13078               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13079             ClearHighlights();
13080             flipView = FALSE;
13081         }
13082         DrawPosition(FALSE, boards[currentMove]);
13083         DisplayBothClocks();
13084         gameMode = EditGame;
13085         ModeHighlight();
13086         gameFileFP = NULL;
13087         cmailOldMove = 0;
13088         return TRUE;
13089     }
13090
13091     // [HGM] PV info: routine tests if comment empty
13092     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13093         DisplayComment(currentMove - 1, commentList[currentMove]);
13094     }
13095     if (!matchMode && appData.timeDelay != 0)
13096       DrawPosition(FALSE, boards[currentMove]);
13097
13098     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13099       programStats.ok_to_send = 1;
13100     }
13101
13102     /* if the first token after the PGN tags is a move
13103      * and not move number 1, retrieve it from the parser
13104      */
13105     if (cm != MoveNumberOne)
13106         LoadGameOneMove(cm);
13107
13108     /* load the remaining moves from the file */
13109     while (LoadGameOneMove(EndOfFile)) {
13110       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13111       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13112     }
13113
13114     /* rewind to the start of the game */
13115     currentMove = backwardMostMove;
13116
13117     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13118
13119     if (oldGameMode == AnalyzeFile) {
13120       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13121       AnalyzeFileEvent();
13122     } else
13123     if (oldGameMode == AnalyzeMode) {
13124       AnalyzeFileEvent();
13125     }
13126
13127     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13128         long int w, b; // [HGM] adjourn: restore saved clock times
13129         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13130         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13131             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13132             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13133         }
13134     }
13135
13136     if(creatingBook) return TRUE;
13137     if (!matchMode && pos > 0) {
13138         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13139     } else
13140     if (matchMode || appData.timeDelay == 0) {
13141       ToEndEvent();
13142     } else if (appData.timeDelay > 0) {
13143       AutoPlayGameLoop();
13144     }
13145
13146     if (appData.debugMode)
13147         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13148
13149     loadFlag = 0; /* [HGM] true game starts */
13150     return TRUE;
13151 }
13152
13153 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13154 int
13155 ReloadPosition (int offset)
13156 {
13157     int positionNumber = lastLoadPositionNumber + offset;
13158     if (lastLoadPositionFP == NULL) {
13159         DisplayError(_("No position has been loaded yet"), 0);
13160         return FALSE;
13161     }
13162     if (positionNumber <= 0) {
13163         DisplayError(_("Can't back up any further"), 0);
13164         return FALSE;
13165     }
13166     return LoadPosition(lastLoadPositionFP, positionNumber,
13167                         lastLoadPositionTitle);
13168 }
13169
13170 /* Load the nth position from the given file */
13171 int
13172 LoadPositionFromFile (char *filename, int n, char *title)
13173 {
13174     FILE *f;
13175     char buf[MSG_SIZ];
13176
13177     if (strcmp(filename, "-") == 0) {
13178         return LoadPosition(stdin, n, "stdin");
13179     } else {
13180         f = fopen(filename, "rb");
13181         if (f == NULL) {
13182             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13183             DisplayError(buf, errno);
13184             return FALSE;
13185         } else {
13186             return LoadPosition(f, n, title);
13187         }
13188     }
13189 }
13190
13191 /* Load the nth position from the given open file, and close it */
13192 int
13193 LoadPosition (FILE *f, int positionNumber, char *title)
13194 {
13195     char *p, line[MSG_SIZ];
13196     Board initial_position;
13197     int i, j, fenMode, pn;
13198
13199     if (gameMode == Training )
13200         SetTrainingModeOff();
13201
13202     if (gameMode != BeginningOfGame) {
13203         Reset(FALSE, TRUE);
13204     }
13205     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13206         fclose(lastLoadPositionFP);
13207     }
13208     if (positionNumber == 0) positionNumber = 1;
13209     lastLoadPositionFP = f;
13210     lastLoadPositionNumber = positionNumber;
13211     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13212     if (first.pr == NoProc && !appData.noChessProgram) {
13213       StartChessProgram(&first);
13214       InitChessProgram(&first, FALSE);
13215     }
13216     pn = positionNumber;
13217     if (positionNumber < 0) {
13218         /* Negative position number means to seek to that byte offset */
13219         if (fseek(f, -positionNumber, 0) == -1) {
13220             DisplayError(_("Can't seek on position file"), 0);
13221             return FALSE;
13222         };
13223         pn = 1;
13224     } else {
13225         if (fseek(f, 0, 0) == -1) {
13226             if (f == lastLoadPositionFP ?
13227                 positionNumber == lastLoadPositionNumber + 1 :
13228                 positionNumber == 1) {
13229                 pn = 1;
13230             } else {
13231                 DisplayError(_("Can't seek on position file"), 0);
13232                 return FALSE;
13233             }
13234         }
13235     }
13236     /* See if this file is FEN or old-style xboard */
13237     if (fgets(line, MSG_SIZ, f) == NULL) {
13238         DisplayError(_("Position not found in file"), 0);
13239         return FALSE;
13240     }
13241     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13242     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13243
13244     if (pn >= 2) {
13245         if (fenMode || line[0] == '#') pn--;
13246         while (pn > 0) {
13247             /* skip positions before number pn */
13248             if (fgets(line, MSG_SIZ, f) == NULL) {
13249                 Reset(TRUE, TRUE);
13250                 DisplayError(_("Position not found in file"), 0);
13251                 return FALSE;
13252             }
13253             if (fenMode || line[0] == '#') pn--;
13254         }
13255     }
13256
13257     if (fenMode) {
13258         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13259             DisplayError(_("Bad FEN position in file"), 0);
13260             return FALSE;
13261         }
13262     } else {
13263         (void) fgets(line, MSG_SIZ, f);
13264         (void) fgets(line, MSG_SIZ, f);
13265
13266         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13267             (void) fgets(line, MSG_SIZ, f);
13268             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13269                 if (*p == ' ')
13270                   continue;
13271                 initial_position[i][j++] = CharToPiece(*p);
13272             }
13273         }
13274
13275         blackPlaysFirst = FALSE;
13276         if (!feof(f)) {
13277             (void) fgets(line, MSG_SIZ, f);
13278             if (strncmp(line, "black", strlen("black"))==0)
13279               blackPlaysFirst = TRUE;
13280         }
13281     }
13282     startedFromSetupPosition = TRUE;
13283
13284     CopyBoard(boards[0], initial_position);
13285     if (blackPlaysFirst) {
13286         currentMove = forwardMostMove = backwardMostMove = 1;
13287         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13288         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13289         CopyBoard(boards[1], initial_position);
13290         DisplayMessage("", _("Black to play"));
13291     } else {
13292         currentMove = forwardMostMove = backwardMostMove = 0;
13293         DisplayMessage("", _("White to play"));
13294     }
13295     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13296     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13297         SendToProgram("force\n", &first);
13298         SendBoard(&first, forwardMostMove);
13299     }
13300     if (appData.debugMode) {
13301 int i, j;
13302   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13303   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13304         fprintf(debugFP, "Load Position\n");
13305     }
13306
13307     if (positionNumber > 1) {
13308       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13309         DisplayTitle(line);
13310     } else {
13311         DisplayTitle(title);
13312     }
13313     gameMode = EditGame;
13314     ModeHighlight();
13315     ResetClocks();
13316     timeRemaining[0][1] = whiteTimeRemaining;
13317     timeRemaining[1][1] = blackTimeRemaining;
13318     DrawPosition(FALSE, boards[currentMove]);
13319
13320     return TRUE;
13321 }
13322
13323
13324 void
13325 CopyPlayerNameIntoFileName (char **dest, char *src)
13326 {
13327     while (*src != NULLCHAR && *src != ',') {
13328         if (*src == ' ') {
13329             *(*dest)++ = '_';
13330             src++;
13331         } else {
13332             *(*dest)++ = *src++;
13333         }
13334     }
13335 }
13336
13337 char *
13338 DefaultFileName (char *ext)
13339 {
13340     static char def[MSG_SIZ];
13341     char *p;
13342
13343     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13344         p = def;
13345         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13346         *p++ = '-';
13347         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13348         *p++ = '.';
13349         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13350     } else {
13351         def[0] = NULLCHAR;
13352     }
13353     return def;
13354 }
13355
13356 /* Save the current game to the given file */
13357 int
13358 SaveGameToFile (char *filename, int append)
13359 {
13360     FILE *f;
13361     char buf[MSG_SIZ];
13362     int result, i, t,tot=0;
13363
13364     if (strcmp(filename, "-") == 0) {
13365         return SaveGame(stdout, 0, NULL);
13366     } else {
13367         for(i=0; i<10; i++) { // upto 10 tries
13368              f = fopen(filename, append ? "a" : "w");
13369              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13370              if(f || errno != 13) break;
13371              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13372              tot += t;
13373         }
13374         if (f == NULL) {
13375             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13376             DisplayError(buf, errno);
13377             return FALSE;
13378         } else {
13379             safeStrCpy(buf, lastMsg, MSG_SIZ);
13380             DisplayMessage(_("Waiting for access to save file"), "");
13381             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13382             DisplayMessage(_("Saving game"), "");
13383             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13384             result = SaveGame(f, 0, NULL);
13385             DisplayMessage(buf, "");
13386             return result;
13387         }
13388     }
13389 }
13390
13391 char *
13392 SavePart (char *str)
13393 {
13394     static char buf[MSG_SIZ];
13395     char *p;
13396
13397     p = strchr(str, ' ');
13398     if (p == NULL) return str;
13399     strncpy(buf, str, p - str);
13400     buf[p - str] = NULLCHAR;
13401     return buf;
13402 }
13403
13404 #define PGN_MAX_LINE 75
13405
13406 #define PGN_SIDE_WHITE  0
13407 #define PGN_SIDE_BLACK  1
13408
13409 static int
13410 FindFirstMoveOutOfBook (int side)
13411 {
13412     int result = -1;
13413
13414     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13415         int index = backwardMostMove;
13416         int has_book_hit = 0;
13417
13418         if( (index % 2) != side ) {
13419             index++;
13420         }
13421
13422         while( index < forwardMostMove ) {
13423             /* Check to see if engine is in book */
13424             int depth = pvInfoList[index].depth;
13425             int score = pvInfoList[index].score;
13426             int in_book = 0;
13427
13428             if( depth <= 2 ) {
13429                 in_book = 1;
13430             }
13431             else if( score == 0 && depth == 63 ) {
13432                 in_book = 1; /* Zappa */
13433             }
13434             else if( score == 2 && depth == 99 ) {
13435                 in_book = 1; /* Abrok */
13436             }
13437
13438             has_book_hit += in_book;
13439
13440             if( ! in_book ) {
13441                 result = index;
13442
13443                 break;
13444             }
13445
13446             index += 2;
13447         }
13448     }
13449
13450     return result;
13451 }
13452
13453 void
13454 GetOutOfBookInfo (char * buf)
13455 {
13456     int oob[2];
13457     int i;
13458     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13459
13460     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13461     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13462
13463     *buf = '\0';
13464
13465     if( oob[0] >= 0 || oob[1] >= 0 ) {
13466         for( i=0; i<2; i++ ) {
13467             int idx = oob[i];
13468
13469             if( idx >= 0 ) {
13470                 if( i > 0 && oob[0] >= 0 ) {
13471                     strcat( buf, "   " );
13472                 }
13473
13474                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13475                 sprintf( buf+strlen(buf), "%s%.2f",
13476                     pvInfoList[idx].score >= 0 ? "+" : "",
13477                     pvInfoList[idx].score / 100.0 );
13478             }
13479         }
13480     }
13481 }
13482
13483 /* Save game in PGN style */
13484 static void
13485 SaveGamePGN2 (FILE *f)
13486 {
13487     int i, offset, linelen, newblock;
13488 //    char *movetext;
13489     char numtext[32];
13490     int movelen, numlen, blank;
13491     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13492
13493     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13494
13495     PrintPGNTags(f, &gameInfo);
13496
13497     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13498
13499     if (backwardMostMove > 0 || startedFromSetupPosition) {
13500         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13501         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13502         fprintf(f, "\n{--------------\n");
13503         PrintPosition(f, backwardMostMove);
13504         fprintf(f, "--------------}\n");
13505         free(fen);
13506     }
13507     else {
13508         /* [AS] Out of book annotation */
13509         if( appData.saveOutOfBookInfo ) {
13510             char buf[64];
13511
13512             GetOutOfBookInfo( buf );
13513
13514             if( buf[0] != '\0' ) {
13515                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13516             }
13517         }
13518
13519         fprintf(f, "\n");
13520     }
13521
13522     i = backwardMostMove;
13523     linelen = 0;
13524     newblock = TRUE;
13525
13526     while (i < forwardMostMove) {
13527         /* Print comments preceding this move */
13528         if (commentList[i] != NULL) {
13529             if (linelen > 0) fprintf(f, "\n");
13530             fprintf(f, "%s", commentList[i]);
13531             linelen = 0;
13532             newblock = TRUE;
13533         }
13534
13535         /* Format move number */
13536         if ((i % 2) == 0)
13537           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13538         else
13539           if (newblock)
13540             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13541           else
13542             numtext[0] = NULLCHAR;
13543
13544         numlen = strlen(numtext);
13545         newblock = FALSE;
13546
13547         /* Print move number */
13548         blank = linelen > 0 && numlen > 0;
13549         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13550             fprintf(f, "\n");
13551             linelen = 0;
13552             blank = 0;
13553         }
13554         if (blank) {
13555             fprintf(f, " ");
13556             linelen++;
13557         }
13558         fprintf(f, "%s", numtext);
13559         linelen += numlen;
13560
13561         /* Get move */
13562         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13563         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13564
13565         /* Print move */
13566         blank = linelen > 0 && movelen > 0;
13567         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13568             fprintf(f, "\n");
13569             linelen = 0;
13570             blank = 0;
13571         }
13572         if (blank) {
13573             fprintf(f, " ");
13574             linelen++;
13575         }
13576         fprintf(f, "%s", move_buffer);
13577         linelen += movelen;
13578
13579         /* [AS] Add PV info if present */
13580         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13581             /* [HGM] add time */
13582             char buf[MSG_SIZ]; int seconds;
13583
13584             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13585
13586             if( seconds <= 0)
13587               buf[0] = 0;
13588             else
13589               if( seconds < 30 )
13590                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13591               else
13592                 {
13593                   seconds = (seconds + 4)/10; // round to full seconds
13594                   if( seconds < 60 )
13595                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13596                   else
13597                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13598                 }
13599
13600             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13601                       pvInfoList[i].score >= 0 ? "+" : "",
13602                       pvInfoList[i].score / 100.0,
13603                       pvInfoList[i].depth,
13604                       buf );
13605
13606             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13607
13608             /* Print score/depth */
13609             blank = linelen > 0 && movelen > 0;
13610             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13611                 fprintf(f, "\n");
13612                 linelen = 0;
13613                 blank = 0;
13614             }
13615             if (blank) {
13616                 fprintf(f, " ");
13617                 linelen++;
13618             }
13619             fprintf(f, "%s", move_buffer);
13620             linelen += movelen;
13621         }
13622
13623         i++;
13624     }
13625
13626     /* Start a new line */
13627     if (linelen > 0) fprintf(f, "\n");
13628
13629     /* Print comments after last move */
13630     if (commentList[i] != NULL) {
13631         fprintf(f, "%s\n", commentList[i]);
13632     }
13633
13634     /* Print result */
13635     if (gameInfo.resultDetails != NULL &&
13636         gameInfo.resultDetails[0] != NULLCHAR) {
13637         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13638         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13639            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13640             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13641         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13642     } else {
13643         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13644     }
13645 }
13646
13647 /* Save game in PGN style and close the file */
13648 int
13649 SaveGamePGN (FILE *f)
13650 {
13651     SaveGamePGN2(f);
13652     fclose(f);
13653     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13654     return TRUE;
13655 }
13656
13657 /* Save game in old style and close the file */
13658 int
13659 SaveGameOldStyle (FILE *f)
13660 {
13661     int i, offset;
13662     time_t tm;
13663
13664     tm = time((time_t *) NULL);
13665
13666     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13667     PrintOpponents(f);
13668
13669     if (backwardMostMove > 0 || startedFromSetupPosition) {
13670         fprintf(f, "\n[--------------\n");
13671         PrintPosition(f, backwardMostMove);
13672         fprintf(f, "--------------]\n");
13673     } else {
13674         fprintf(f, "\n");
13675     }
13676
13677     i = backwardMostMove;
13678     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13679
13680     while (i < forwardMostMove) {
13681         if (commentList[i] != NULL) {
13682             fprintf(f, "[%s]\n", commentList[i]);
13683         }
13684
13685         if ((i % 2) == 1) {
13686             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13687             i++;
13688         } else {
13689             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13690             i++;
13691             if (commentList[i] != NULL) {
13692                 fprintf(f, "\n");
13693                 continue;
13694             }
13695             if (i >= forwardMostMove) {
13696                 fprintf(f, "\n");
13697                 break;
13698             }
13699             fprintf(f, "%s\n", parseList[i]);
13700             i++;
13701         }
13702     }
13703
13704     if (commentList[i] != NULL) {
13705         fprintf(f, "[%s]\n", commentList[i]);
13706     }
13707
13708     /* This isn't really the old style, but it's close enough */
13709     if (gameInfo.resultDetails != NULL &&
13710         gameInfo.resultDetails[0] != NULLCHAR) {
13711         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13712                 gameInfo.resultDetails);
13713     } else {
13714         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13715     }
13716
13717     fclose(f);
13718     return TRUE;
13719 }
13720
13721 /* Save the current game to open file f and close the file */
13722 int
13723 SaveGame (FILE *f, int dummy, char *dummy2)
13724 {
13725     if (gameMode == EditPosition) EditPositionDone(TRUE);
13726     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13727     if (appData.oldSaveStyle)
13728       return SaveGameOldStyle(f);
13729     else
13730       return SaveGamePGN(f);
13731 }
13732
13733 /* Save the current position to the given file */
13734 int
13735 SavePositionToFile (char *filename)
13736 {
13737     FILE *f;
13738     char buf[MSG_SIZ];
13739
13740     if (strcmp(filename, "-") == 0) {
13741         return SavePosition(stdout, 0, NULL);
13742     } else {
13743         f = fopen(filename, "a");
13744         if (f == NULL) {
13745             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13746             DisplayError(buf, errno);
13747             return FALSE;
13748         } else {
13749             safeStrCpy(buf, lastMsg, MSG_SIZ);
13750             DisplayMessage(_("Waiting for access to save file"), "");
13751             flock(fileno(f), LOCK_EX); // [HGM] lock
13752             DisplayMessage(_("Saving position"), "");
13753             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13754             SavePosition(f, 0, NULL);
13755             DisplayMessage(buf, "");
13756             return TRUE;
13757         }
13758     }
13759 }
13760
13761 /* Save the current position to the given open file and close the file */
13762 int
13763 SavePosition (FILE *f, int dummy, char *dummy2)
13764 {
13765     time_t tm;
13766     char *fen;
13767
13768     if (gameMode == EditPosition) EditPositionDone(TRUE);
13769     if (appData.oldSaveStyle) {
13770         tm = time((time_t *) NULL);
13771
13772         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13773         PrintOpponents(f);
13774         fprintf(f, "[--------------\n");
13775         PrintPosition(f, currentMove);
13776         fprintf(f, "--------------]\n");
13777     } else {
13778         fen = PositionToFEN(currentMove, NULL, 1);
13779         fprintf(f, "%s\n", fen);
13780         free(fen);
13781     }
13782     fclose(f);
13783     return TRUE;
13784 }
13785
13786 void
13787 ReloadCmailMsgEvent (int unregister)
13788 {
13789 #if !WIN32
13790     static char *inFilename = NULL;
13791     static char *outFilename;
13792     int i;
13793     struct stat inbuf, outbuf;
13794     int status;
13795
13796     /* Any registered moves are unregistered if unregister is set, */
13797     /* i.e. invoked by the signal handler */
13798     if (unregister) {
13799         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13800             cmailMoveRegistered[i] = FALSE;
13801             if (cmailCommentList[i] != NULL) {
13802                 free(cmailCommentList[i]);
13803                 cmailCommentList[i] = NULL;
13804             }
13805         }
13806         nCmailMovesRegistered = 0;
13807     }
13808
13809     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13810         cmailResult[i] = CMAIL_NOT_RESULT;
13811     }
13812     nCmailResults = 0;
13813
13814     if (inFilename == NULL) {
13815         /* Because the filenames are static they only get malloced once  */
13816         /* and they never get freed                                      */
13817         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13818         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13819
13820         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13821         sprintf(outFilename, "%s.out", appData.cmailGameName);
13822     }
13823
13824     status = stat(outFilename, &outbuf);
13825     if (status < 0) {
13826         cmailMailedMove = FALSE;
13827     } else {
13828         status = stat(inFilename, &inbuf);
13829         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13830     }
13831
13832     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13833        counts the games, notes how each one terminated, etc.
13834
13835        It would be nice to remove this kludge and instead gather all
13836        the information while building the game list.  (And to keep it
13837        in the game list nodes instead of having a bunch of fixed-size
13838        parallel arrays.)  Note this will require getting each game's
13839        termination from the PGN tags, as the game list builder does
13840        not process the game moves.  --mann
13841        */
13842     cmailMsgLoaded = TRUE;
13843     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13844
13845     /* Load first game in the file or popup game menu */
13846     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13847
13848 #endif /* !WIN32 */
13849     return;
13850 }
13851
13852 int
13853 RegisterMove ()
13854 {
13855     FILE *f;
13856     char string[MSG_SIZ];
13857
13858     if (   cmailMailedMove
13859         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13860         return TRUE;            /* Allow free viewing  */
13861     }
13862
13863     /* Unregister move to ensure that we don't leave RegisterMove        */
13864     /* with the move registered when the conditions for registering no   */
13865     /* longer hold                                                       */
13866     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13867         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13868         nCmailMovesRegistered --;
13869
13870         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13871           {
13872               free(cmailCommentList[lastLoadGameNumber - 1]);
13873               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13874           }
13875     }
13876
13877     if (cmailOldMove == -1) {
13878         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13879         return FALSE;
13880     }
13881
13882     if (currentMove > cmailOldMove + 1) {
13883         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13884         return FALSE;
13885     }
13886
13887     if (currentMove < cmailOldMove) {
13888         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13889         return FALSE;
13890     }
13891
13892     if (forwardMostMove > currentMove) {
13893         /* Silently truncate extra moves */
13894         TruncateGame();
13895     }
13896
13897     if (   (currentMove == cmailOldMove + 1)
13898         || (   (currentMove == cmailOldMove)
13899             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13900                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13901         if (gameInfo.result != GameUnfinished) {
13902             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13903         }
13904
13905         if (commentList[currentMove] != NULL) {
13906             cmailCommentList[lastLoadGameNumber - 1]
13907               = StrSave(commentList[currentMove]);
13908         }
13909         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13910
13911         if (appData.debugMode)
13912           fprintf(debugFP, "Saving %s for game %d\n",
13913                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13914
13915         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13916
13917         f = fopen(string, "w");
13918         if (appData.oldSaveStyle) {
13919             SaveGameOldStyle(f); /* also closes the file */
13920
13921             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13922             f = fopen(string, "w");
13923             SavePosition(f, 0, NULL); /* also closes the file */
13924         } else {
13925             fprintf(f, "{--------------\n");
13926             PrintPosition(f, currentMove);
13927             fprintf(f, "--------------}\n\n");
13928
13929             SaveGame(f, 0, NULL); /* also closes the file*/
13930         }
13931
13932         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13933         nCmailMovesRegistered ++;
13934     } else if (nCmailGames == 1) {
13935         DisplayError(_("You have not made a move yet"), 0);
13936         return FALSE;
13937     }
13938
13939     return TRUE;
13940 }
13941
13942 void
13943 MailMoveEvent ()
13944 {
13945 #if !WIN32
13946     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13947     FILE *commandOutput;
13948     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13949     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13950     int nBuffers;
13951     int i;
13952     int archived;
13953     char *arcDir;
13954
13955     if (! cmailMsgLoaded) {
13956         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13957         return;
13958     }
13959
13960     if (nCmailGames == nCmailResults) {
13961         DisplayError(_("No unfinished games"), 0);
13962         return;
13963     }
13964
13965 #if CMAIL_PROHIBIT_REMAIL
13966     if (cmailMailedMove) {
13967       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);
13968         DisplayError(msg, 0);
13969         return;
13970     }
13971 #endif
13972
13973     if (! (cmailMailedMove || RegisterMove())) return;
13974
13975     if (   cmailMailedMove
13976         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13977       snprintf(string, MSG_SIZ, partCommandString,
13978                appData.debugMode ? " -v" : "", appData.cmailGameName);
13979         commandOutput = popen(string, "r");
13980
13981         if (commandOutput == NULL) {
13982             DisplayError(_("Failed to invoke cmail"), 0);
13983         } else {
13984             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13985                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13986             }
13987             if (nBuffers > 1) {
13988                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13989                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13990                 nBytes = MSG_SIZ - 1;
13991             } else {
13992                 (void) memcpy(msg, buffer, nBytes);
13993             }
13994             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13995
13996             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13997                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13998
13999                 archived = TRUE;
14000                 for (i = 0; i < nCmailGames; i ++) {
14001                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14002                         archived = FALSE;
14003                     }
14004                 }
14005                 if (   archived
14006                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14007                         != NULL)) {
14008                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14009                            arcDir,
14010                            appData.cmailGameName,
14011                            gameInfo.date);
14012                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14013                     cmailMsgLoaded = FALSE;
14014                 }
14015             }
14016
14017             DisplayInformation(msg);
14018             pclose(commandOutput);
14019         }
14020     } else {
14021         if ((*cmailMsg) != '\0') {
14022             DisplayInformation(cmailMsg);
14023         }
14024     }
14025
14026     return;
14027 #endif /* !WIN32 */
14028 }
14029
14030 char *
14031 CmailMsg ()
14032 {
14033 #if WIN32
14034     return NULL;
14035 #else
14036     int  prependComma = 0;
14037     char number[5];
14038     char string[MSG_SIZ];       /* Space for game-list */
14039     int  i;
14040
14041     if (!cmailMsgLoaded) return "";
14042
14043     if (cmailMailedMove) {
14044       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14045     } else {
14046         /* Create a list of games left */
14047       snprintf(string, MSG_SIZ, "[");
14048         for (i = 0; i < nCmailGames; i ++) {
14049             if (! (   cmailMoveRegistered[i]
14050                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14051                 if (prependComma) {
14052                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14053                 } else {
14054                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14055                     prependComma = 1;
14056                 }
14057
14058                 strcat(string, number);
14059             }
14060         }
14061         strcat(string, "]");
14062
14063         if (nCmailMovesRegistered + nCmailResults == 0) {
14064             switch (nCmailGames) {
14065               case 1:
14066                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14067                 break;
14068
14069               case 2:
14070                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14071                 break;
14072
14073               default:
14074                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14075                          nCmailGames);
14076                 break;
14077             }
14078         } else {
14079             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14080               case 1:
14081                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14082                          string);
14083                 break;
14084
14085               case 0:
14086                 if (nCmailResults == nCmailGames) {
14087                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14088                 } else {
14089                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14090                 }
14091                 break;
14092
14093               default:
14094                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14095                          string);
14096             }
14097         }
14098     }
14099     return cmailMsg;
14100 #endif /* WIN32 */
14101 }
14102
14103 void
14104 ResetGameEvent ()
14105 {
14106     if (gameMode == Training)
14107       SetTrainingModeOff();
14108
14109     Reset(TRUE, TRUE);
14110     cmailMsgLoaded = FALSE;
14111     if (appData.icsActive) {
14112       SendToICS(ics_prefix);
14113       SendToICS("refresh\n");
14114     }
14115 }
14116
14117 void
14118 ExitEvent (int status)
14119 {
14120     exiting++;
14121     if (exiting > 2) {
14122       /* Give up on clean exit */
14123       exit(status);
14124     }
14125     if (exiting > 1) {
14126       /* Keep trying for clean exit */
14127       return;
14128     }
14129
14130     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14131     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14132
14133     if (telnetISR != NULL) {
14134       RemoveInputSource(telnetISR);
14135     }
14136     if (icsPR != NoProc) {
14137       DestroyChildProcess(icsPR, TRUE);
14138     }
14139
14140     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14141     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14142
14143     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14144     /* make sure this other one finishes before killing it!                  */
14145     if(endingGame) { int count = 0;
14146         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14147         while(endingGame && count++ < 10) DoSleep(1);
14148         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14149     }
14150
14151     /* Kill off chess programs */
14152     if (first.pr != NoProc) {
14153         ExitAnalyzeMode();
14154
14155         DoSleep( appData.delayBeforeQuit );
14156         SendToProgram("quit\n", &first);
14157         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14158     }
14159     if (second.pr != NoProc) {
14160         DoSleep( appData.delayBeforeQuit );
14161         SendToProgram("quit\n", &second);
14162         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14163     }
14164     if (first.isr != NULL) {
14165         RemoveInputSource(first.isr);
14166     }
14167     if (second.isr != NULL) {
14168         RemoveInputSource(second.isr);
14169     }
14170
14171     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14172     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14173
14174     ShutDownFrontEnd();
14175     exit(status);
14176 }
14177
14178 void
14179 PauseEngine (ChessProgramState *cps)
14180 {
14181     SendToProgram("pause\n", cps);
14182     cps->pause = 2;
14183 }
14184
14185 void
14186 UnPauseEngine (ChessProgramState *cps)
14187 {
14188     SendToProgram("resume\n", cps);
14189     cps->pause = 1;
14190 }
14191
14192 void
14193 PauseEvent ()
14194 {
14195     if (appData.debugMode)
14196         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14197     if (pausing) {
14198         pausing = FALSE;
14199         ModeHighlight();
14200         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14201             StartClocks();
14202             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14203                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14204                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14205             }
14206             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14207             HandleMachineMove(stashedInputMove, stalledEngine);
14208             stalledEngine = NULL;
14209             return;
14210         }
14211         if (gameMode == MachinePlaysWhite ||
14212             gameMode == TwoMachinesPlay   ||
14213             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14214             if(first.pause)  UnPauseEngine(&first);
14215             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14216             if(second.pause) UnPauseEngine(&second);
14217             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14218             StartClocks();
14219         } else {
14220             DisplayBothClocks();
14221         }
14222         if (gameMode == PlayFromGameFile) {
14223             if (appData.timeDelay >= 0)
14224                 AutoPlayGameLoop();
14225         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14226             Reset(FALSE, TRUE);
14227             SendToICS(ics_prefix);
14228             SendToICS("refresh\n");
14229         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14230             ForwardInner(forwardMostMove);
14231         }
14232         pauseExamInvalid = FALSE;
14233     } else {
14234         switch (gameMode) {
14235           default:
14236             return;
14237           case IcsExamining:
14238             pauseExamForwardMostMove = forwardMostMove;
14239             pauseExamInvalid = FALSE;
14240             /* fall through */
14241           case IcsObserving:
14242           case IcsPlayingWhite:
14243           case IcsPlayingBlack:
14244             pausing = TRUE;
14245             ModeHighlight();
14246             return;
14247           case PlayFromGameFile:
14248             (void) StopLoadGameTimer();
14249             pausing = TRUE;
14250             ModeHighlight();
14251             break;
14252           case BeginningOfGame:
14253             if (appData.icsActive) return;
14254             /* else fall through */
14255           case MachinePlaysWhite:
14256           case MachinePlaysBlack:
14257           case TwoMachinesPlay:
14258             if (forwardMostMove == 0)
14259               return;           /* don't pause if no one has moved */
14260             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14261                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14262                 if(onMove->pause) {           // thinking engine can be paused
14263                     PauseEngine(onMove);      // do it
14264                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14265                         PauseEngine(onMove->other);
14266                     else
14267                         SendToProgram("easy\n", onMove->other);
14268                     StopClocks();
14269                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14270             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14271                 if(first.pause) {
14272                     PauseEngine(&first);
14273                     StopClocks();
14274                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14275             } else { // human on move, pause pondering by either method
14276                 if(first.pause)
14277                     PauseEngine(&first);
14278                 else if(appData.ponderNextMove)
14279                     SendToProgram("easy\n", &first);
14280                 StopClocks();
14281             }
14282             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14283           case AnalyzeMode:
14284             pausing = TRUE;
14285             ModeHighlight();
14286             break;
14287         }
14288     }
14289 }
14290
14291 void
14292 EditCommentEvent ()
14293 {
14294     char title[MSG_SIZ];
14295
14296     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14297       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14298     } else {
14299       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14300                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14301                parseList[currentMove - 1]);
14302     }
14303
14304     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14305 }
14306
14307
14308 void
14309 EditTagsEvent ()
14310 {
14311     char *tags = PGNTags(&gameInfo);
14312     bookUp = FALSE;
14313     EditTagsPopUp(tags, NULL);
14314     free(tags);
14315 }
14316
14317 void
14318 ToggleSecond ()
14319 {
14320   if(second.analyzing) {
14321     SendToProgram("exit\n", &second);
14322     second.analyzing = FALSE;
14323   } else {
14324     if (second.pr == NoProc) StartChessProgram(&second);
14325     InitChessProgram(&second, FALSE);
14326     FeedMovesToProgram(&second, currentMove);
14327
14328     SendToProgram("analyze\n", &second);
14329     second.analyzing = TRUE;
14330   }
14331 }
14332
14333 /* Toggle ShowThinking */
14334 void
14335 ToggleShowThinking()
14336 {
14337   appData.showThinking = !appData.showThinking;
14338   ShowThinkingEvent();
14339 }
14340
14341 int
14342 AnalyzeModeEvent ()
14343 {
14344     char buf[MSG_SIZ];
14345
14346     if (!first.analysisSupport) {
14347       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14348       DisplayError(buf, 0);
14349       return 0;
14350     }
14351     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14352     if (appData.icsActive) {
14353         if (gameMode != IcsObserving) {
14354           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14355             DisplayError(buf, 0);
14356             /* secure check */
14357             if (appData.icsEngineAnalyze) {
14358                 if (appData.debugMode)
14359                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14360                 ExitAnalyzeMode();
14361                 ModeHighlight();
14362             }
14363             return 0;
14364         }
14365         /* if enable, user wants to disable icsEngineAnalyze */
14366         if (appData.icsEngineAnalyze) {
14367                 ExitAnalyzeMode();
14368                 ModeHighlight();
14369                 return 0;
14370         }
14371         appData.icsEngineAnalyze = TRUE;
14372         if (appData.debugMode)
14373             fprintf(debugFP, "ICS engine analyze starting... \n");
14374     }
14375
14376     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14377     if (appData.noChessProgram || gameMode == AnalyzeMode)
14378       return 0;
14379
14380     if (gameMode != AnalyzeFile) {
14381         if (!appData.icsEngineAnalyze) {
14382                EditGameEvent();
14383                if (gameMode != EditGame) return 0;
14384         }
14385         if (!appData.showThinking) ToggleShowThinking();
14386         ResurrectChessProgram();
14387         SendToProgram("analyze\n", &first);
14388         first.analyzing = TRUE;
14389         /*first.maybeThinking = TRUE;*/
14390         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14391         EngineOutputPopUp();
14392     }
14393     if (!appData.icsEngineAnalyze) {
14394         gameMode = AnalyzeMode;
14395         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14396     }
14397     pausing = FALSE;
14398     ModeHighlight();
14399     SetGameInfo();
14400
14401     StartAnalysisClock();
14402     GetTimeMark(&lastNodeCountTime);
14403     lastNodeCount = 0;
14404     return 1;
14405 }
14406
14407 void
14408 AnalyzeFileEvent ()
14409 {
14410     if (appData.noChessProgram || gameMode == AnalyzeFile)
14411       return;
14412
14413     if (!first.analysisSupport) {
14414       char buf[MSG_SIZ];
14415       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14416       DisplayError(buf, 0);
14417       return;
14418     }
14419
14420     if (gameMode != AnalyzeMode) {
14421         keepInfo = 1; // mere annotating should not alter PGN tags
14422         EditGameEvent();
14423         keepInfo = 0;
14424         if (gameMode != EditGame) return;
14425         if (!appData.showThinking) ToggleShowThinking();
14426         ResurrectChessProgram();
14427         SendToProgram("analyze\n", &first);
14428         first.analyzing = TRUE;
14429         /*first.maybeThinking = TRUE;*/
14430         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14431         EngineOutputPopUp();
14432     }
14433     gameMode = AnalyzeFile;
14434     pausing = FALSE;
14435     ModeHighlight();
14436
14437     StartAnalysisClock();
14438     GetTimeMark(&lastNodeCountTime);
14439     lastNodeCount = 0;
14440     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14441     AnalysisPeriodicEvent(1);
14442 }
14443
14444 void
14445 MachineWhiteEvent ()
14446 {
14447     char buf[MSG_SIZ];
14448     char *bookHit = NULL;
14449
14450     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14451       return;
14452
14453
14454     if (gameMode == PlayFromGameFile ||
14455         gameMode == TwoMachinesPlay  ||
14456         gameMode == Training         ||
14457         gameMode == AnalyzeMode      ||
14458         gameMode == EndOfGame)
14459         EditGameEvent();
14460
14461     if (gameMode == EditPosition)
14462         EditPositionDone(TRUE);
14463
14464     if (!WhiteOnMove(currentMove)) {
14465         DisplayError(_("It is not White's turn"), 0);
14466         return;
14467     }
14468
14469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14470       ExitAnalyzeMode();
14471
14472     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14473         gameMode == AnalyzeFile)
14474         TruncateGame();
14475
14476     ResurrectChessProgram();    /* in case it isn't running */
14477     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14478         gameMode = MachinePlaysWhite;
14479         ResetClocks();
14480     } else
14481     gameMode = MachinePlaysWhite;
14482     pausing = FALSE;
14483     ModeHighlight();
14484     SetGameInfo();
14485     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14486     DisplayTitle(buf);
14487     if (first.sendName) {
14488       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14489       SendToProgram(buf, &first);
14490     }
14491     if (first.sendTime) {
14492       if (first.useColors) {
14493         SendToProgram("black\n", &first); /*gnu kludge*/
14494       }
14495       SendTimeRemaining(&first, TRUE);
14496     }
14497     if (first.useColors) {
14498       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14499     }
14500     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14501     SetMachineThinkingEnables();
14502     first.maybeThinking = TRUE;
14503     StartClocks();
14504     firstMove = FALSE;
14505
14506     if (appData.autoFlipView && !flipView) {
14507       flipView = !flipView;
14508       DrawPosition(FALSE, NULL);
14509       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14510     }
14511
14512     if(bookHit) { // [HGM] book: simulate book reply
14513         static char bookMove[MSG_SIZ]; // a bit generous?
14514
14515         programStats.nodes = programStats.depth = programStats.time =
14516         programStats.score = programStats.got_only_move = 0;
14517         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14518
14519         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14520         strcat(bookMove, bookHit);
14521         HandleMachineMove(bookMove, &first);
14522     }
14523 }
14524
14525 void
14526 MachineBlackEvent ()
14527 {
14528   char buf[MSG_SIZ];
14529   char *bookHit = NULL;
14530
14531     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14532         return;
14533
14534
14535     if (gameMode == PlayFromGameFile ||
14536         gameMode == TwoMachinesPlay  ||
14537         gameMode == Training         ||
14538         gameMode == AnalyzeMode      ||
14539         gameMode == EndOfGame)
14540         EditGameEvent();
14541
14542     if (gameMode == EditPosition)
14543         EditPositionDone(TRUE);
14544
14545     if (WhiteOnMove(currentMove)) {
14546         DisplayError(_("It is not Black's turn"), 0);
14547         return;
14548     }
14549
14550     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14551       ExitAnalyzeMode();
14552
14553     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14554         gameMode == AnalyzeFile)
14555         TruncateGame();
14556
14557     ResurrectChessProgram();    /* in case it isn't running */
14558     gameMode = MachinePlaysBlack;
14559     pausing = FALSE;
14560     ModeHighlight();
14561     SetGameInfo();
14562     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14563     DisplayTitle(buf);
14564     if (first.sendName) {
14565       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14566       SendToProgram(buf, &first);
14567     }
14568     if (first.sendTime) {
14569       if (first.useColors) {
14570         SendToProgram("white\n", &first); /*gnu kludge*/
14571       }
14572       SendTimeRemaining(&first, FALSE);
14573     }
14574     if (first.useColors) {
14575       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14576     }
14577     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14578     SetMachineThinkingEnables();
14579     first.maybeThinking = TRUE;
14580     StartClocks();
14581
14582     if (appData.autoFlipView && flipView) {
14583       flipView = !flipView;
14584       DrawPosition(FALSE, NULL);
14585       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14586     }
14587     if(bookHit) { // [HGM] book: simulate book reply
14588         static char bookMove[MSG_SIZ]; // a bit generous?
14589
14590         programStats.nodes = programStats.depth = programStats.time =
14591         programStats.score = programStats.got_only_move = 0;
14592         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14593
14594         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14595         strcat(bookMove, bookHit);
14596         HandleMachineMove(bookMove, &first);
14597     }
14598 }
14599
14600
14601 void
14602 DisplayTwoMachinesTitle ()
14603 {
14604     char buf[MSG_SIZ];
14605     if (appData.matchGames > 0) {
14606         if(appData.tourneyFile[0]) {
14607           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14608                    gameInfo.white, _("vs."), gameInfo.black,
14609                    nextGame+1, appData.matchGames+1,
14610                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14611         } else
14612         if (first.twoMachinesColor[0] == 'w') {
14613           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14614                    gameInfo.white, _("vs."),  gameInfo.black,
14615                    first.matchWins, second.matchWins,
14616                    matchGame - 1 - (first.matchWins + second.matchWins));
14617         } else {
14618           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14619                    gameInfo.white, _("vs."), gameInfo.black,
14620                    second.matchWins, first.matchWins,
14621                    matchGame - 1 - (first.matchWins + second.matchWins));
14622         }
14623     } else {
14624       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14625     }
14626     DisplayTitle(buf);
14627 }
14628
14629 void
14630 SettingsMenuIfReady ()
14631 {
14632   if (second.lastPing != second.lastPong) {
14633     DisplayMessage("", _("Waiting for second chess program"));
14634     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14635     return;
14636   }
14637   ThawUI();
14638   DisplayMessage("", "");
14639   SettingsPopUp(&second);
14640 }
14641
14642 int
14643 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14644 {
14645     char buf[MSG_SIZ];
14646     if (cps->pr == NoProc) {
14647         StartChessProgram(cps);
14648         if (cps->protocolVersion == 1) {
14649           retry();
14650           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14651         } else {
14652           /* kludge: allow timeout for initial "feature" command */
14653           if(retry != TwoMachinesEventIfReady) FreezeUI();
14654           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14655           DisplayMessage("", buf);
14656           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14657         }
14658         return 1;
14659     }
14660     return 0;
14661 }
14662
14663 void
14664 TwoMachinesEvent P((void))
14665 {
14666     int i;
14667     char buf[MSG_SIZ];
14668     ChessProgramState *onmove;
14669     char *bookHit = NULL;
14670     static int stalling = 0;
14671     TimeMark now;
14672     long wait;
14673
14674     if (appData.noChessProgram) return;
14675
14676     switch (gameMode) {
14677       case TwoMachinesPlay:
14678         return;
14679       case MachinePlaysWhite:
14680       case MachinePlaysBlack:
14681         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14682             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14683             return;
14684         }
14685         /* fall through */
14686       case BeginningOfGame:
14687       case PlayFromGameFile:
14688       case EndOfGame:
14689         EditGameEvent();
14690         if (gameMode != EditGame) return;
14691         break;
14692       case EditPosition:
14693         EditPositionDone(TRUE);
14694         break;
14695       case AnalyzeMode:
14696       case AnalyzeFile:
14697         ExitAnalyzeMode();
14698         break;
14699       case EditGame:
14700       default:
14701         break;
14702     }
14703
14704 //    forwardMostMove = currentMove;
14705     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14706     startingEngine = TRUE;
14707
14708     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14709
14710     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14711     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14712       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14713       return;
14714     }
14715     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14716
14717     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14718                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14719         startingEngine = matchMode = FALSE;
14720         DisplayError("second engine does not play this", 0);
14721         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14722         EditGameEvent(); // switch back to EditGame mode
14723         return;
14724     }
14725
14726     if(!stalling) {
14727       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14728       SendToProgram("force\n", &second);
14729       stalling = 1;
14730       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14731       return;
14732     }
14733     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14734     if(appData.matchPause>10000 || appData.matchPause<10)
14735                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14736     wait = SubtractTimeMarks(&now, &pauseStart);
14737     if(wait < appData.matchPause) {
14738         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14739         return;
14740     }
14741     // we are now committed to starting the game
14742     stalling = 0;
14743     DisplayMessage("", "");
14744     if (startedFromSetupPosition) {
14745         SendBoard(&second, backwardMostMove);
14746     if (appData.debugMode) {
14747         fprintf(debugFP, "Two Machines\n");
14748     }
14749     }
14750     for (i = backwardMostMove; i < forwardMostMove; i++) {
14751         SendMoveToProgram(i, &second);
14752     }
14753
14754     gameMode = TwoMachinesPlay;
14755     pausing = startingEngine = FALSE;
14756     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14757     SetGameInfo();
14758     DisplayTwoMachinesTitle();
14759     firstMove = TRUE;
14760     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14761         onmove = &first;
14762     } else {
14763         onmove = &second;
14764     }
14765     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14766     SendToProgram(first.computerString, &first);
14767     if (first.sendName) {
14768       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14769       SendToProgram(buf, &first);
14770     }
14771     SendToProgram(second.computerString, &second);
14772     if (second.sendName) {
14773       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14774       SendToProgram(buf, &second);
14775     }
14776
14777     ResetClocks();
14778     if (!first.sendTime || !second.sendTime) {
14779         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14780         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14781     }
14782     if (onmove->sendTime) {
14783       if (onmove->useColors) {
14784         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14785       }
14786       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14787     }
14788     if (onmove->useColors) {
14789       SendToProgram(onmove->twoMachinesColor, onmove);
14790     }
14791     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14792 //    SendToProgram("go\n", onmove);
14793     onmove->maybeThinking = TRUE;
14794     SetMachineThinkingEnables();
14795
14796     StartClocks();
14797
14798     if(bookHit) { // [HGM] book: simulate book reply
14799         static char bookMove[MSG_SIZ]; // a bit generous?
14800
14801         programStats.nodes = programStats.depth = programStats.time =
14802         programStats.score = programStats.got_only_move = 0;
14803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14804
14805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14806         strcat(bookMove, bookHit);
14807         savedMessage = bookMove; // args for deferred call
14808         savedState = onmove;
14809         ScheduleDelayedEvent(DeferredBookMove, 1);
14810     }
14811 }
14812
14813 void
14814 TrainingEvent ()
14815 {
14816     if (gameMode == Training) {
14817       SetTrainingModeOff();
14818       gameMode = PlayFromGameFile;
14819       DisplayMessage("", _("Training mode off"));
14820     } else {
14821       gameMode = Training;
14822       animateTraining = appData.animate;
14823
14824       /* make sure we are not already at the end of the game */
14825       if (currentMove < forwardMostMove) {
14826         SetTrainingModeOn();
14827         DisplayMessage("", _("Training mode on"));
14828       } else {
14829         gameMode = PlayFromGameFile;
14830         DisplayError(_("Already at end of game"), 0);
14831       }
14832     }
14833     ModeHighlight();
14834 }
14835
14836 void
14837 IcsClientEvent ()
14838 {
14839     if (!appData.icsActive) return;
14840     switch (gameMode) {
14841       case IcsPlayingWhite:
14842       case IcsPlayingBlack:
14843       case IcsObserving:
14844       case IcsIdle:
14845       case BeginningOfGame:
14846       case IcsExamining:
14847         return;
14848
14849       case EditGame:
14850         break;
14851
14852       case EditPosition:
14853         EditPositionDone(TRUE);
14854         break;
14855
14856       case AnalyzeMode:
14857       case AnalyzeFile:
14858         ExitAnalyzeMode();
14859         break;
14860
14861       default:
14862         EditGameEvent();
14863         break;
14864     }
14865
14866     gameMode = IcsIdle;
14867     ModeHighlight();
14868     return;
14869 }
14870
14871 void
14872 EditGameEvent ()
14873 {
14874     int i;
14875
14876     switch (gameMode) {
14877       case Training:
14878         SetTrainingModeOff();
14879         break;
14880       case MachinePlaysWhite:
14881       case MachinePlaysBlack:
14882       case BeginningOfGame:
14883         SendToProgram("force\n", &first);
14884         SetUserThinkingEnables();
14885         break;
14886       case PlayFromGameFile:
14887         (void) StopLoadGameTimer();
14888         if (gameFileFP != NULL) {
14889             gameFileFP = NULL;
14890         }
14891         break;
14892       case EditPosition:
14893         EditPositionDone(TRUE);
14894         break;
14895       case AnalyzeMode:
14896       case AnalyzeFile:
14897         ExitAnalyzeMode();
14898         SendToProgram("force\n", &first);
14899         break;
14900       case TwoMachinesPlay:
14901         GameEnds(EndOfFile, NULL, GE_PLAYER);
14902         ResurrectChessProgram();
14903         SetUserThinkingEnables();
14904         break;
14905       case EndOfGame:
14906         ResurrectChessProgram();
14907         break;
14908       case IcsPlayingBlack:
14909       case IcsPlayingWhite:
14910         DisplayError(_("Warning: You are still playing a game"), 0);
14911         break;
14912       case IcsObserving:
14913         DisplayError(_("Warning: You are still observing a game"), 0);
14914         break;
14915       case IcsExamining:
14916         DisplayError(_("Warning: You are still examining a game"), 0);
14917         break;
14918       case IcsIdle:
14919         break;
14920       case EditGame:
14921       default:
14922         return;
14923     }
14924
14925     pausing = FALSE;
14926     StopClocks();
14927     first.offeredDraw = second.offeredDraw = 0;
14928
14929     if (gameMode == PlayFromGameFile) {
14930         whiteTimeRemaining = timeRemaining[0][currentMove];
14931         blackTimeRemaining = timeRemaining[1][currentMove];
14932         DisplayTitle("");
14933     }
14934
14935     if (gameMode == MachinePlaysWhite ||
14936         gameMode == MachinePlaysBlack ||
14937         gameMode == TwoMachinesPlay ||
14938         gameMode == EndOfGame) {
14939         i = forwardMostMove;
14940         while (i > currentMove) {
14941             SendToProgram("undo\n", &first);
14942             i--;
14943         }
14944         if(!adjustedClock) {
14945         whiteTimeRemaining = timeRemaining[0][currentMove];
14946         blackTimeRemaining = timeRemaining[1][currentMove];
14947         DisplayBothClocks();
14948         }
14949         if (whiteFlag || blackFlag) {
14950             whiteFlag = blackFlag = 0;
14951         }
14952         DisplayTitle("");
14953     }
14954
14955     gameMode = EditGame;
14956     ModeHighlight();
14957     SetGameInfo();
14958 }
14959
14960
14961 void
14962 EditPositionEvent ()
14963 {
14964     if (gameMode == EditPosition) {
14965         EditGameEvent();
14966         return;
14967     }
14968
14969     EditGameEvent();
14970     if (gameMode != EditGame) return;
14971
14972     gameMode = EditPosition;
14973     ModeHighlight();
14974     SetGameInfo();
14975     if (currentMove > 0)
14976       CopyBoard(boards[0], boards[currentMove]);
14977
14978     blackPlaysFirst = !WhiteOnMove(currentMove);
14979     ResetClocks();
14980     currentMove = forwardMostMove = backwardMostMove = 0;
14981     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14982     DisplayMove(-1);
14983     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14984 }
14985
14986 void
14987 ExitAnalyzeMode ()
14988 {
14989     /* [DM] icsEngineAnalyze - possible call from other functions */
14990     if (appData.icsEngineAnalyze) {
14991         appData.icsEngineAnalyze = FALSE;
14992
14993         DisplayMessage("",_("Close ICS engine analyze..."));
14994     }
14995     if (first.analysisSupport && first.analyzing) {
14996       SendToBoth("exit\n");
14997       first.analyzing = second.analyzing = FALSE;
14998     }
14999     thinkOutput[0] = NULLCHAR;
15000 }
15001
15002 void
15003 EditPositionDone (Boolean fakeRights)
15004 {
15005     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15006
15007     startedFromSetupPosition = TRUE;
15008     InitChessProgram(&first, FALSE);
15009     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15010       boards[0][EP_STATUS] = EP_NONE;
15011       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15012       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15013         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15014         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15015       } else boards[0][CASTLING][2] = NoRights;
15016       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15017         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15018         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15019       } else boards[0][CASTLING][5] = NoRights;
15020       if(gameInfo.variant == VariantSChess) {
15021         int i;
15022         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15023           boards[0][VIRGIN][i] = 0;
15024           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15025           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15026         }
15027       }
15028     }
15029     SendToProgram("force\n", &first);
15030     if (blackPlaysFirst) {
15031         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15032         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15033         currentMove = forwardMostMove = backwardMostMove = 1;
15034         CopyBoard(boards[1], boards[0]);
15035     } else {
15036         currentMove = forwardMostMove = backwardMostMove = 0;
15037     }
15038     SendBoard(&first, forwardMostMove);
15039     if (appData.debugMode) {
15040         fprintf(debugFP, "EditPosDone\n");
15041     }
15042     DisplayTitle("");
15043     DisplayMessage("", "");
15044     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15045     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15046     gameMode = EditGame;
15047     ModeHighlight();
15048     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15049     ClearHighlights(); /* [AS] */
15050 }
15051
15052 /* Pause for `ms' milliseconds */
15053 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15054 void
15055 TimeDelay (long ms)
15056 {
15057     TimeMark m1, m2;
15058
15059     GetTimeMark(&m1);
15060     do {
15061         GetTimeMark(&m2);
15062     } while (SubtractTimeMarks(&m2, &m1) < ms);
15063 }
15064
15065 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15066 void
15067 SendMultiLineToICS (char *buf)
15068 {
15069     char temp[MSG_SIZ+1], *p;
15070     int len;
15071
15072     len = strlen(buf);
15073     if (len > MSG_SIZ)
15074       len = MSG_SIZ;
15075
15076     strncpy(temp, buf, len);
15077     temp[len] = 0;
15078
15079     p = temp;
15080     while (*p) {
15081         if (*p == '\n' || *p == '\r')
15082           *p = ' ';
15083         ++p;
15084     }
15085
15086     strcat(temp, "\n");
15087     SendToICS(temp);
15088     SendToPlayer(temp, strlen(temp));
15089 }
15090
15091 void
15092 SetWhiteToPlayEvent ()
15093 {
15094     if (gameMode == EditPosition) {
15095         blackPlaysFirst = FALSE;
15096         DisplayBothClocks();    /* works because currentMove is 0 */
15097     } else if (gameMode == IcsExamining) {
15098         SendToICS(ics_prefix);
15099         SendToICS("tomove white\n");
15100     }
15101 }
15102
15103 void
15104 SetBlackToPlayEvent ()
15105 {
15106     if (gameMode == EditPosition) {
15107         blackPlaysFirst = TRUE;
15108         currentMove = 1;        /* kludge */
15109         DisplayBothClocks();
15110         currentMove = 0;
15111     } else if (gameMode == IcsExamining) {
15112         SendToICS(ics_prefix);
15113         SendToICS("tomove black\n");
15114     }
15115 }
15116
15117 void
15118 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15119 {
15120     char buf[MSG_SIZ];
15121     ChessSquare piece = boards[0][y][x];
15122     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15123     static int lastVariant;
15124
15125     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15126
15127     switch (selection) {
15128       case ClearBoard:
15129         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15130         MarkTargetSquares(1);
15131         CopyBoard(currentBoard, boards[0]);
15132         CopyBoard(menuBoard, initialPosition);
15133         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15134             SendToICS(ics_prefix);
15135             SendToICS("bsetup clear\n");
15136         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15137             SendToICS(ics_prefix);
15138             SendToICS("clearboard\n");
15139         } else {
15140             int nonEmpty = 0;
15141             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15142                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15143                 for (y = 0; y < BOARD_HEIGHT; y++) {
15144                     if (gameMode == IcsExamining) {
15145                         if (boards[currentMove][y][x] != EmptySquare) {
15146                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15147                                     AAA + x, ONE + y);
15148                             SendToICS(buf);
15149                         }
15150                     } else if(boards[0][y][x] != DarkSquare) {
15151                         if(boards[0][y][x] != p) nonEmpty++;
15152                         boards[0][y][x] = p;
15153                     }
15154                 }
15155             }
15156             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15157                 int r;
15158                 for(r = 0; r < BOARD_HEIGHT; r++) {
15159                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15160                     ChessSquare p = menuBoard[r][x];
15161                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15162                   }
15163                 }
15164                 DisplayMessage("Clicking clock again restores position", "");
15165                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15166                 if(!nonEmpty) { // asked to clear an empty board
15167                     CopyBoard(boards[0], menuBoard);
15168                 } else
15169                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15170                     CopyBoard(boards[0], initialPosition);
15171                 } else
15172                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15173                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15174                     CopyBoard(boards[0], erasedBoard);
15175                 } else
15176                     CopyBoard(erasedBoard, currentBoard);
15177
15178             }
15179         }
15180         if (gameMode == EditPosition) {
15181             DrawPosition(FALSE, boards[0]);
15182         }
15183         break;
15184
15185       case WhitePlay:
15186         SetWhiteToPlayEvent();
15187         break;
15188
15189       case BlackPlay:
15190         SetBlackToPlayEvent();
15191         break;
15192
15193       case EmptySquare:
15194         if (gameMode == IcsExamining) {
15195             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15196             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15197             SendToICS(buf);
15198         } else {
15199             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15200                 if(x == BOARD_LEFT-2) {
15201                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15202                     boards[0][y][1] = 0;
15203                 } else
15204                 if(x == BOARD_RGHT+1) {
15205                     if(y >= gameInfo.holdingsSize) break;
15206                     boards[0][y][BOARD_WIDTH-2] = 0;
15207                 } else break;
15208             }
15209             boards[0][y][x] = EmptySquare;
15210             DrawPosition(FALSE, boards[0]);
15211         }
15212         break;
15213
15214       case PromotePiece:
15215         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15216            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15217             selection = (ChessSquare) (PROMOTED piece);
15218         } else if(piece == EmptySquare) selection = WhiteSilver;
15219         else selection = (ChessSquare)((int)piece - 1);
15220         goto defaultlabel;
15221
15222       case DemotePiece:
15223         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15224            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15225             selection = (ChessSquare) (DEMOTED piece);
15226         } else if(piece == EmptySquare) selection = BlackSilver;
15227         else selection = (ChessSquare)((int)piece + 1);
15228         goto defaultlabel;
15229
15230       case WhiteQueen:
15231       case BlackQueen:
15232         if(gameInfo.variant == VariantShatranj ||
15233            gameInfo.variant == VariantXiangqi  ||
15234            gameInfo.variant == VariantCourier  ||
15235            gameInfo.variant == VariantASEAN    ||
15236            gameInfo.variant == VariantMakruk     )
15237             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15238         goto defaultlabel;
15239
15240       case WhiteKing:
15241       case BlackKing:
15242         if(gameInfo.variant == VariantXiangqi)
15243             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15244         if(gameInfo.variant == VariantKnightmate)
15245             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15246       default:
15247         defaultlabel:
15248         if (gameMode == IcsExamining) {
15249             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15250             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15251                      PieceToChar(selection), AAA + x, ONE + y);
15252             SendToICS(buf);
15253         } else {
15254             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15255                 int n;
15256                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15257                     n = PieceToNumber(selection - BlackPawn);
15258                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15259                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15260                     boards[0][BOARD_HEIGHT-1-n][1]++;
15261                 } else
15262                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15263                     n = PieceToNumber(selection);
15264                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15265                     boards[0][n][BOARD_WIDTH-1] = selection;
15266                     boards[0][n][BOARD_WIDTH-2]++;
15267                 }
15268             } else
15269             boards[0][y][x] = selection;
15270             DrawPosition(TRUE, boards[0]);
15271             ClearHighlights();
15272             fromX = fromY = -1;
15273         }
15274         break;
15275     }
15276 }
15277
15278
15279 void
15280 DropMenuEvent (ChessSquare selection, int x, int y)
15281 {
15282     ChessMove moveType;
15283
15284     switch (gameMode) {
15285       case IcsPlayingWhite:
15286       case MachinePlaysBlack:
15287         if (!WhiteOnMove(currentMove)) {
15288             DisplayMoveError(_("It is Black's turn"));
15289             return;
15290         }
15291         moveType = WhiteDrop;
15292         break;
15293       case IcsPlayingBlack:
15294       case MachinePlaysWhite:
15295         if (WhiteOnMove(currentMove)) {
15296             DisplayMoveError(_("It is White's turn"));
15297             return;
15298         }
15299         moveType = BlackDrop;
15300         break;
15301       case EditGame:
15302         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15303         break;
15304       default:
15305         return;
15306     }
15307
15308     if (moveType == BlackDrop && selection < BlackPawn) {
15309       selection = (ChessSquare) ((int) selection
15310                                  + (int) BlackPawn - (int) WhitePawn);
15311     }
15312     if (boards[currentMove][y][x] != EmptySquare) {
15313         DisplayMoveError(_("That square is occupied"));
15314         return;
15315     }
15316
15317     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15318 }
15319
15320 void
15321 AcceptEvent ()
15322 {
15323     /* Accept a pending offer of any kind from opponent */
15324
15325     if (appData.icsActive) {
15326         SendToICS(ics_prefix);
15327         SendToICS("accept\n");
15328     } else if (cmailMsgLoaded) {
15329         if (currentMove == cmailOldMove &&
15330             commentList[cmailOldMove] != NULL &&
15331             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15332                    "Black offers a draw" : "White offers a draw")) {
15333             TruncateGame();
15334             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15335             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15336         } else {
15337             DisplayError(_("There is no pending offer on this move"), 0);
15338             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15339         }
15340     } else {
15341         /* Not used for offers from chess program */
15342     }
15343 }
15344
15345 void
15346 DeclineEvent ()
15347 {
15348     /* Decline a pending offer of any kind from opponent */
15349
15350     if (appData.icsActive) {
15351         SendToICS(ics_prefix);
15352         SendToICS("decline\n");
15353     } else if (cmailMsgLoaded) {
15354         if (currentMove == cmailOldMove &&
15355             commentList[cmailOldMove] != NULL &&
15356             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15357                    "Black offers a draw" : "White offers a draw")) {
15358 #ifdef NOTDEF
15359             AppendComment(cmailOldMove, "Draw declined", TRUE);
15360             DisplayComment(cmailOldMove - 1, "Draw declined");
15361 #endif /*NOTDEF*/
15362         } else {
15363             DisplayError(_("There is no pending offer on this move"), 0);
15364         }
15365     } else {
15366         /* Not used for offers from chess program */
15367     }
15368 }
15369
15370 void
15371 RematchEvent ()
15372 {
15373     /* Issue ICS rematch command */
15374     if (appData.icsActive) {
15375         SendToICS(ics_prefix);
15376         SendToICS("rematch\n");
15377     }
15378 }
15379
15380 void
15381 CallFlagEvent ()
15382 {
15383     /* Call your opponent's flag (claim a win on time) */
15384     if (appData.icsActive) {
15385         SendToICS(ics_prefix);
15386         SendToICS("flag\n");
15387     } else {
15388         switch (gameMode) {
15389           default:
15390             return;
15391           case MachinePlaysWhite:
15392             if (whiteFlag) {
15393                 if (blackFlag)
15394                   GameEnds(GameIsDrawn, "Both players ran out of time",
15395                            GE_PLAYER);
15396                 else
15397                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15398             } else {
15399                 DisplayError(_("Your opponent is not out of time"), 0);
15400             }
15401             break;
15402           case MachinePlaysBlack:
15403             if (blackFlag) {
15404                 if (whiteFlag)
15405                   GameEnds(GameIsDrawn, "Both players ran out of time",
15406                            GE_PLAYER);
15407                 else
15408                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15409             } else {
15410                 DisplayError(_("Your opponent is not out of time"), 0);
15411             }
15412             break;
15413         }
15414     }
15415 }
15416
15417 void
15418 ClockClick (int which)
15419 {       // [HGM] code moved to back-end from winboard.c
15420         if(which) { // black clock
15421           if (gameMode == EditPosition || gameMode == IcsExamining) {
15422             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15423             SetBlackToPlayEvent();
15424           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15425                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15426           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15427           } else if (shiftKey) {
15428             AdjustClock(which, -1);
15429           } else if (gameMode == IcsPlayingWhite ||
15430                      gameMode == MachinePlaysBlack) {
15431             CallFlagEvent();
15432           }
15433         } else { // white clock
15434           if (gameMode == EditPosition || gameMode == IcsExamining) {
15435             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15436             SetWhiteToPlayEvent();
15437           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15438                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15439           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15440           } else if (shiftKey) {
15441             AdjustClock(which, -1);
15442           } else if (gameMode == IcsPlayingBlack ||
15443                    gameMode == MachinePlaysWhite) {
15444             CallFlagEvent();
15445           }
15446         }
15447 }
15448
15449 void
15450 DrawEvent ()
15451 {
15452     /* Offer draw or accept pending draw offer from opponent */
15453
15454     if (appData.icsActive) {
15455         /* Note: tournament rules require draw offers to be
15456            made after you make your move but before you punch
15457            your clock.  Currently ICS doesn't let you do that;
15458            instead, you immediately punch your clock after making
15459            a move, but you can offer a draw at any time. */
15460
15461         SendToICS(ics_prefix);
15462         SendToICS("draw\n");
15463         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15464     } else if (cmailMsgLoaded) {
15465         if (currentMove == cmailOldMove &&
15466             commentList[cmailOldMove] != NULL &&
15467             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15468                    "Black offers a draw" : "White offers a draw")) {
15469             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15470             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15471         } else if (currentMove == cmailOldMove + 1) {
15472             char *offer = WhiteOnMove(cmailOldMove) ?
15473               "White offers a draw" : "Black offers a draw";
15474             AppendComment(currentMove, offer, TRUE);
15475             DisplayComment(currentMove - 1, offer);
15476             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15477         } else {
15478             DisplayError(_("You must make your move before offering a draw"), 0);
15479             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15480         }
15481     } else if (first.offeredDraw) {
15482         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15483     } else {
15484         if (first.sendDrawOffers) {
15485             SendToProgram("draw\n", &first);
15486             userOfferedDraw = TRUE;
15487         }
15488     }
15489 }
15490
15491 void
15492 AdjournEvent ()
15493 {
15494     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15495
15496     if (appData.icsActive) {
15497         SendToICS(ics_prefix);
15498         SendToICS("adjourn\n");
15499     } else {
15500         /* Currently GNU Chess doesn't offer or accept Adjourns */
15501     }
15502 }
15503
15504
15505 void
15506 AbortEvent ()
15507 {
15508     /* Offer Abort or accept pending Abort offer from opponent */
15509
15510     if (appData.icsActive) {
15511         SendToICS(ics_prefix);
15512         SendToICS("abort\n");
15513     } else {
15514         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15515     }
15516 }
15517
15518 void
15519 ResignEvent ()
15520 {
15521     /* Resign.  You can do this even if it's not your turn. */
15522
15523     if (appData.icsActive) {
15524         SendToICS(ics_prefix);
15525         SendToICS("resign\n");
15526     } else {
15527         switch (gameMode) {
15528           case MachinePlaysWhite:
15529             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15530             break;
15531           case MachinePlaysBlack:
15532             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15533             break;
15534           case EditGame:
15535             if (cmailMsgLoaded) {
15536                 TruncateGame();
15537                 if (WhiteOnMove(cmailOldMove)) {
15538                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15539                 } else {
15540                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15541                 }
15542                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15543             }
15544             break;
15545           default:
15546             break;
15547         }
15548     }
15549 }
15550
15551
15552 void
15553 StopObservingEvent ()
15554 {
15555     /* Stop observing current games */
15556     SendToICS(ics_prefix);
15557     SendToICS("unobserve\n");
15558 }
15559
15560 void
15561 StopExaminingEvent ()
15562 {
15563     /* Stop observing current game */
15564     SendToICS(ics_prefix);
15565     SendToICS("unexamine\n");
15566 }
15567
15568 void
15569 ForwardInner (int target)
15570 {
15571     int limit; int oldSeekGraphUp = seekGraphUp;
15572
15573     if (appData.debugMode)
15574         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15575                 target, currentMove, forwardMostMove);
15576
15577     if (gameMode == EditPosition)
15578       return;
15579
15580     seekGraphUp = FALSE;
15581     MarkTargetSquares(1);
15582     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15583
15584     if (gameMode == PlayFromGameFile && !pausing)
15585       PauseEvent();
15586
15587     if (gameMode == IcsExamining && pausing)
15588       limit = pauseExamForwardMostMove;
15589     else
15590       limit = forwardMostMove;
15591
15592     if (target > limit) target = limit;
15593
15594     if (target > 0 && moveList[target - 1][0]) {
15595         int fromX, fromY, toX, toY;
15596         toX = moveList[target - 1][2] - AAA;
15597         toY = moveList[target - 1][3] - ONE;
15598         if (moveList[target - 1][1] == '@') {
15599             if (appData.highlightLastMove) {
15600                 SetHighlights(-1, -1, toX, toY);
15601             }
15602         } else {
15603             int viaX = moveList[target - 1][5] - AAA;
15604             int viaY = moveList[target - 1][6] - ONE;
15605             fromX = moveList[target - 1][0] - AAA;
15606             fromY = moveList[target - 1][1] - ONE;
15607             if (target == currentMove + 1) {
15608                 if(moveList[target - 1][4] == ';') { // multi-leg
15609                     ChessSquare piece = boards[currentMove][viaY][viaX];
15610                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15611                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15612                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15613                     boards[currentMove][viaY][viaX] = piece;
15614                 } else
15615                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15616             }
15617             if (appData.highlightLastMove) {
15618                 SetHighlights(fromX, fromY, toX, toY);
15619             }
15620         }
15621     }
15622     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15623         gameMode == Training || gameMode == PlayFromGameFile ||
15624         gameMode == AnalyzeFile) {
15625         while (currentMove < target) {
15626             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15627             SendMoveToProgram(currentMove++, &first);
15628         }
15629     } else {
15630         currentMove = target;
15631     }
15632
15633     if (gameMode == EditGame || gameMode == EndOfGame) {
15634         whiteTimeRemaining = timeRemaining[0][currentMove];
15635         blackTimeRemaining = timeRemaining[1][currentMove];
15636     }
15637     DisplayBothClocks();
15638     DisplayMove(currentMove - 1);
15639     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15640     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15641     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15642         DisplayComment(currentMove - 1, commentList[currentMove]);
15643     }
15644     ClearMap(); // [HGM] exclude: invalidate map
15645 }
15646
15647
15648 void
15649 ForwardEvent ()
15650 {
15651     if (gameMode == IcsExamining && !pausing) {
15652         SendToICS(ics_prefix);
15653         SendToICS("forward\n");
15654     } else {
15655         ForwardInner(currentMove + 1);
15656     }
15657 }
15658
15659 void
15660 ToEndEvent ()
15661 {
15662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15663         /* to optimze, we temporarily turn off analysis mode while we feed
15664          * the remaining moves to the engine. Otherwise we get analysis output
15665          * after each move.
15666          */
15667         if (first.analysisSupport) {
15668           SendToProgram("exit\nforce\n", &first);
15669           first.analyzing = FALSE;
15670         }
15671     }
15672
15673     if (gameMode == IcsExamining && !pausing) {
15674         SendToICS(ics_prefix);
15675         SendToICS("forward 999999\n");
15676     } else {
15677         ForwardInner(forwardMostMove);
15678     }
15679
15680     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15681         /* we have fed all the moves, so reactivate analysis mode */
15682         SendToProgram("analyze\n", &first);
15683         first.analyzing = TRUE;
15684         /*first.maybeThinking = TRUE;*/
15685         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15686     }
15687 }
15688
15689 void
15690 BackwardInner (int target)
15691 {
15692     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15693
15694     if (appData.debugMode)
15695         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15696                 target, currentMove, forwardMostMove);
15697
15698     if (gameMode == EditPosition) return;
15699     seekGraphUp = FALSE;
15700     MarkTargetSquares(1);
15701     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15702     if (currentMove <= backwardMostMove) {
15703         ClearHighlights();
15704         DrawPosition(full_redraw, boards[currentMove]);
15705         return;
15706     }
15707     if (gameMode == PlayFromGameFile && !pausing)
15708       PauseEvent();
15709
15710     if (moveList[target][0]) {
15711         int fromX, fromY, toX, toY;
15712         toX = moveList[target][2] - AAA;
15713         toY = moveList[target][3] - ONE;
15714         if (moveList[target][1] == '@') {
15715             if (appData.highlightLastMove) {
15716                 SetHighlights(-1, -1, toX, toY);
15717             }
15718         } else {
15719             fromX = moveList[target][0] - AAA;
15720             fromY = moveList[target][1] - ONE;
15721             if (target == currentMove - 1) {
15722                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15723             }
15724             if (appData.highlightLastMove) {
15725                 SetHighlights(fromX, fromY, toX, toY);
15726             }
15727         }
15728     }
15729     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15730         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15731         while (currentMove > target) {
15732             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15733                 // null move cannot be undone. Reload program with move history before it.
15734                 int i;
15735                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15736                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15737                 }
15738                 SendBoard(&first, i);
15739               if(second.analyzing) SendBoard(&second, i);
15740                 for(currentMove=i; currentMove<target; currentMove++) {
15741                     SendMoveToProgram(currentMove, &first);
15742                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15743                 }
15744                 break;
15745             }
15746             SendToBoth("undo\n");
15747             currentMove--;
15748         }
15749     } else {
15750         currentMove = target;
15751     }
15752
15753     if (gameMode == EditGame || gameMode == EndOfGame) {
15754         whiteTimeRemaining = timeRemaining[0][currentMove];
15755         blackTimeRemaining = timeRemaining[1][currentMove];
15756     }
15757     DisplayBothClocks();
15758     DisplayMove(currentMove - 1);
15759     DrawPosition(full_redraw, boards[currentMove]);
15760     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15761     // [HGM] PV info: routine tests if comment empty
15762     DisplayComment(currentMove - 1, commentList[currentMove]);
15763     ClearMap(); // [HGM] exclude: invalidate map
15764 }
15765
15766 void
15767 BackwardEvent ()
15768 {
15769     if (gameMode == IcsExamining && !pausing) {
15770         SendToICS(ics_prefix);
15771         SendToICS("backward\n");
15772     } else {
15773         BackwardInner(currentMove - 1);
15774     }
15775 }
15776
15777 void
15778 ToStartEvent ()
15779 {
15780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15781         /* to optimize, we temporarily turn off analysis mode while we undo
15782          * all the moves. Otherwise we get analysis output after each undo.
15783          */
15784         if (first.analysisSupport) {
15785           SendToProgram("exit\nforce\n", &first);
15786           first.analyzing = FALSE;
15787         }
15788     }
15789
15790     if (gameMode == IcsExamining && !pausing) {
15791         SendToICS(ics_prefix);
15792         SendToICS("backward 999999\n");
15793     } else {
15794         BackwardInner(backwardMostMove);
15795     }
15796
15797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15798         /* we have fed all the moves, so reactivate analysis mode */
15799         SendToProgram("analyze\n", &first);
15800         first.analyzing = TRUE;
15801         /*first.maybeThinking = TRUE;*/
15802         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15803     }
15804 }
15805
15806 void
15807 ToNrEvent (int to)
15808 {
15809   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15810   if (to >= forwardMostMove) to = forwardMostMove;
15811   if (to <= backwardMostMove) to = backwardMostMove;
15812   if (to < currentMove) {
15813     BackwardInner(to);
15814   } else {
15815     ForwardInner(to);
15816   }
15817 }
15818
15819 void
15820 RevertEvent (Boolean annotate)
15821 {
15822     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15823         return;
15824     }
15825     if (gameMode != IcsExamining) {
15826         DisplayError(_("You are not examining a game"), 0);
15827         return;
15828     }
15829     if (pausing) {
15830         DisplayError(_("You can't revert while pausing"), 0);
15831         return;
15832     }
15833     SendToICS(ics_prefix);
15834     SendToICS("revert\n");
15835 }
15836
15837 void
15838 RetractMoveEvent ()
15839 {
15840     switch (gameMode) {
15841       case MachinePlaysWhite:
15842       case MachinePlaysBlack:
15843         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15844             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15845             return;
15846         }
15847         if (forwardMostMove < 2) return;
15848         currentMove = forwardMostMove = forwardMostMove - 2;
15849         whiteTimeRemaining = timeRemaining[0][currentMove];
15850         blackTimeRemaining = timeRemaining[1][currentMove];
15851         DisplayBothClocks();
15852         DisplayMove(currentMove - 1);
15853         ClearHighlights();/*!! could figure this out*/
15854         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15855         SendToProgram("remove\n", &first);
15856         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15857         break;
15858
15859       case BeginningOfGame:
15860       default:
15861         break;
15862
15863       case IcsPlayingWhite:
15864       case IcsPlayingBlack:
15865         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15866             SendToICS(ics_prefix);
15867             SendToICS("takeback 2\n");
15868         } else {
15869             SendToICS(ics_prefix);
15870             SendToICS("takeback 1\n");
15871         }
15872         break;
15873     }
15874 }
15875
15876 void
15877 MoveNowEvent ()
15878 {
15879     ChessProgramState *cps;
15880
15881     switch (gameMode) {
15882       case MachinePlaysWhite:
15883         if (!WhiteOnMove(forwardMostMove)) {
15884             DisplayError(_("It is your turn"), 0);
15885             return;
15886         }
15887         cps = &first;
15888         break;
15889       case MachinePlaysBlack:
15890         if (WhiteOnMove(forwardMostMove)) {
15891             DisplayError(_("It is your turn"), 0);
15892             return;
15893         }
15894         cps = &first;
15895         break;
15896       case TwoMachinesPlay:
15897         if (WhiteOnMove(forwardMostMove) ==
15898             (first.twoMachinesColor[0] == 'w')) {
15899             cps = &first;
15900         } else {
15901             cps = &second;
15902         }
15903         break;
15904       case BeginningOfGame:
15905       default:
15906         return;
15907     }
15908     SendToProgram("?\n", cps);
15909 }
15910
15911 void
15912 TruncateGameEvent ()
15913 {
15914     EditGameEvent();
15915     if (gameMode != EditGame) return;
15916     TruncateGame();
15917 }
15918
15919 void
15920 TruncateGame ()
15921 {
15922     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15923     if (forwardMostMove > currentMove) {
15924         if (gameInfo.resultDetails != NULL) {
15925             free(gameInfo.resultDetails);
15926             gameInfo.resultDetails = NULL;
15927             gameInfo.result = GameUnfinished;
15928         }
15929         forwardMostMove = currentMove;
15930         HistorySet(parseList, backwardMostMove, forwardMostMove,
15931                    currentMove-1);
15932     }
15933 }
15934
15935 void
15936 HintEvent ()
15937 {
15938     if (appData.noChessProgram) return;
15939     switch (gameMode) {
15940       case MachinePlaysWhite:
15941         if (WhiteOnMove(forwardMostMove)) {
15942             DisplayError(_("Wait until your turn."), 0);
15943             return;
15944         }
15945         break;
15946       case BeginningOfGame:
15947       case MachinePlaysBlack:
15948         if (!WhiteOnMove(forwardMostMove)) {
15949             DisplayError(_("Wait until your turn."), 0);
15950             return;
15951         }
15952         break;
15953       default:
15954         DisplayError(_("No hint available"), 0);
15955         return;
15956     }
15957     SendToProgram("hint\n", &first);
15958     hintRequested = TRUE;
15959 }
15960
15961 int
15962 SaveSelected (FILE *g, int dummy, char *dummy2)
15963 {
15964     ListGame * lg = (ListGame *) gameList.head;
15965     int nItem, cnt=0;
15966     FILE *f;
15967
15968     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15969         DisplayError(_("Game list not loaded or empty"), 0);
15970         return 0;
15971     }
15972
15973     creatingBook = TRUE; // suppresses stuff during load game
15974
15975     /* Get list size */
15976     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15977         if(lg->position >= 0) { // selected?
15978             LoadGame(f, nItem, "", TRUE);
15979             SaveGamePGN2(g); // leaves g open
15980             cnt++; DoEvents();
15981         }
15982         lg = (ListGame *) lg->node.succ;
15983     }
15984
15985     fclose(g);
15986     creatingBook = FALSE;
15987
15988     return cnt;
15989 }
15990
15991 void
15992 CreateBookEvent ()
15993 {
15994     ListGame * lg = (ListGame *) gameList.head;
15995     FILE *f, *g;
15996     int nItem;
15997     static int secondTime = FALSE;
15998
15999     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16000         DisplayError(_("Game list not loaded or empty"), 0);
16001         return;
16002     }
16003
16004     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16005         fclose(g);
16006         secondTime++;
16007         DisplayNote(_("Book file exists! Try again for overwrite."));
16008         return;
16009     }
16010
16011     creatingBook = TRUE;
16012     secondTime = FALSE;
16013
16014     /* Get list size */
16015     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16016         if(lg->position >= 0) {
16017             LoadGame(f, nItem, "", TRUE);
16018             AddGameToBook(TRUE);
16019             DoEvents();
16020         }
16021         lg = (ListGame *) lg->node.succ;
16022     }
16023
16024     creatingBook = FALSE;
16025     FlushBook();
16026 }
16027
16028 void
16029 BookEvent ()
16030 {
16031     if (appData.noChessProgram) return;
16032     switch (gameMode) {
16033       case MachinePlaysWhite:
16034         if (WhiteOnMove(forwardMostMove)) {
16035             DisplayError(_("Wait until your turn."), 0);
16036             return;
16037         }
16038         break;
16039       case BeginningOfGame:
16040       case MachinePlaysBlack:
16041         if (!WhiteOnMove(forwardMostMove)) {
16042             DisplayError(_("Wait until your turn."), 0);
16043             return;
16044         }
16045         break;
16046       case EditPosition:
16047         EditPositionDone(TRUE);
16048         break;
16049       case TwoMachinesPlay:
16050         return;
16051       default:
16052         break;
16053     }
16054     SendToProgram("bk\n", &first);
16055     bookOutput[0] = NULLCHAR;
16056     bookRequested = TRUE;
16057 }
16058
16059 void
16060 AboutGameEvent ()
16061 {
16062     char *tags = PGNTags(&gameInfo);
16063     TagsPopUp(tags, CmailMsg());
16064     free(tags);
16065 }
16066
16067 /* end button procedures */
16068
16069 void
16070 PrintPosition (FILE *fp, int move)
16071 {
16072     int i, j;
16073
16074     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16075         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16076             char c = PieceToChar(boards[move][i][j]);
16077             fputc(c == 'x' ? '.' : c, fp);
16078             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16079         }
16080     }
16081     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16082       fprintf(fp, "white to play\n");
16083     else
16084       fprintf(fp, "black to play\n");
16085 }
16086
16087 void
16088 PrintOpponents (FILE *fp)
16089 {
16090     if (gameInfo.white != NULL) {
16091         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16092     } else {
16093         fprintf(fp, "\n");
16094     }
16095 }
16096
16097 /* Find last component of program's own name, using some heuristics */
16098 void
16099 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16100 {
16101     char *p, *q, c;
16102     int local = (strcmp(host, "localhost") == 0);
16103     while (!local && (p = strchr(prog, ';')) != NULL) {
16104         p++;
16105         while (*p == ' ') p++;
16106         prog = p;
16107     }
16108     if (*prog == '"' || *prog == '\'') {
16109         q = strchr(prog + 1, *prog);
16110     } else {
16111         q = strchr(prog, ' ');
16112     }
16113     if (q == NULL) q = prog + strlen(prog);
16114     p = q;
16115     while (p >= prog && *p != '/' && *p != '\\') p--;
16116     p++;
16117     if(p == prog && *p == '"') p++;
16118     c = *q; *q = 0;
16119     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16120     memcpy(buf, p, q - p);
16121     buf[q - p] = NULLCHAR;
16122     if (!local) {
16123         strcat(buf, "@");
16124         strcat(buf, host);
16125     }
16126 }
16127
16128 char *
16129 TimeControlTagValue ()
16130 {
16131     char buf[MSG_SIZ];
16132     if (!appData.clockMode) {
16133       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16134     } else if (movesPerSession > 0) {
16135       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16136     } else if (timeIncrement == 0) {
16137       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16138     } else {
16139       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16140     }
16141     return StrSave(buf);
16142 }
16143
16144 void
16145 SetGameInfo ()
16146 {
16147     /* This routine is used only for certain modes */
16148     VariantClass v = gameInfo.variant;
16149     ChessMove r = GameUnfinished;
16150     char *p = NULL;
16151
16152     if(keepInfo) return;
16153
16154     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16155         r = gameInfo.result;
16156         p = gameInfo.resultDetails;
16157         gameInfo.resultDetails = NULL;
16158     }
16159     ClearGameInfo(&gameInfo);
16160     gameInfo.variant = v;
16161
16162     switch (gameMode) {
16163       case MachinePlaysWhite:
16164         gameInfo.event = StrSave( appData.pgnEventHeader );
16165         gameInfo.site = StrSave(HostName());
16166         gameInfo.date = PGNDate();
16167         gameInfo.round = StrSave("-");
16168         gameInfo.white = StrSave(first.tidy);
16169         gameInfo.black = StrSave(UserName());
16170         gameInfo.timeControl = TimeControlTagValue();
16171         break;
16172
16173       case MachinePlaysBlack:
16174         gameInfo.event = StrSave( appData.pgnEventHeader );
16175         gameInfo.site = StrSave(HostName());
16176         gameInfo.date = PGNDate();
16177         gameInfo.round = StrSave("-");
16178         gameInfo.white = StrSave(UserName());
16179         gameInfo.black = StrSave(first.tidy);
16180         gameInfo.timeControl = TimeControlTagValue();
16181         break;
16182
16183       case TwoMachinesPlay:
16184         gameInfo.event = StrSave( appData.pgnEventHeader );
16185         gameInfo.site = StrSave(HostName());
16186         gameInfo.date = PGNDate();
16187         if (roundNr > 0) {
16188             char buf[MSG_SIZ];
16189             snprintf(buf, MSG_SIZ, "%d", roundNr);
16190             gameInfo.round = StrSave(buf);
16191         } else {
16192             gameInfo.round = StrSave("-");
16193         }
16194         if (first.twoMachinesColor[0] == 'w') {
16195             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16196             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16197         } else {
16198             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16199             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16200         }
16201         gameInfo.timeControl = TimeControlTagValue();
16202         break;
16203
16204       case EditGame:
16205         gameInfo.event = StrSave("Edited game");
16206         gameInfo.site = StrSave(HostName());
16207         gameInfo.date = PGNDate();
16208         gameInfo.round = StrSave("-");
16209         gameInfo.white = StrSave("-");
16210         gameInfo.black = StrSave("-");
16211         gameInfo.result = r;
16212         gameInfo.resultDetails = p;
16213         break;
16214
16215       case EditPosition:
16216         gameInfo.event = StrSave("Edited position");
16217         gameInfo.site = StrSave(HostName());
16218         gameInfo.date = PGNDate();
16219         gameInfo.round = StrSave("-");
16220         gameInfo.white = StrSave("-");
16221         gameInfo.black = StrSave("-");
16222         break;
16223
16224       case IcsPlayingWhite:
16225       case IcsPlayingBlack:
16226       case IcsObserving:
16227       case IcsExamining:
16228         break;
16229
16230       case PlayFromGameFile:
16231         gameInfo.event = StrSave("Game from non-PGN file");
16232         gameInfo.site = StrSave(HostName());
16233         gameInfo.date = PGNDate();
16234         gameInfo.round = StrSave("-");
16235         gameInfo.white = StrSave("?");
16236         gameInfo.black = StrSave("?");
16237         break;
16238
16239       default:
16240         break;
16241     }
16242 }
16243
16244 void
16245 ReplaceComment (int index, char *text)
16246 {
16247     int len;
16248     char *p;
16249     float score;
16250
16251     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16252        pvInfoList[index-1].depth == len &&
16253        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16254        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16255     while (*text == '\n') text++;
16256     len = strlen(text);
16257     while (len > 0 && text[len - 1] == '\n') len--;
16258
16259     if (commentList[index] != NULL)
16260       free(commentList[index]);
16261
16262     if (len == 0) {
16263         commentList[index] = NULL;
16264         return;
16265     }
16266   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16267       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16268       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16269     commentList[index] = (char *) malloc(len + 2);
16270     strncpy(commentList[index], text, len);
16271     commentList[index][len] = '\n';
16272     commentList[index][len + 1] = NULLCHAR;
16273   } else {
16274     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16275     char *p;
16276     commentList[index] = (char *) malloc(len + 7);
16277     safeStrCpy(commentList[index], "{\n", 3);
16278     safeStrCpy(commentList[index]+2, text, len+1);
16279     commentList[index][len+2] = NULLCHAR;
16280     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16281     strcat(commentList[index], "\n}\n");
16282   }
16283 }
16284
16285 void
16286 CrushCRs (char *text)
16287 {
16288   char *p = text;
16289   char *q = text;
16290   char ch;
16291
16292   do {
16293     ch = *p++;
16294     if (ch == '\r') continue;
16295     *q++ = ch;
16296   } while (ch != '\0');
16297 }
16298
16299 void
16300 AppendComment (int index, char *text, Boolean addBraces)
16301 /* addBraces  tells if we should add {} */
16302 {
16303     int oldlen, len;
16304     char *old;
16305
16306 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16307     if(addBraces == 3) addBraces = 0; else // force appending literally
16308     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16309
16310     CrushCRs(text);
16311     while (*text == '\n') text++;
16312     len = strlen(text);
16313     while (len > 0 && text[len - 1] == '\n') len--;
16314     text[len] = NULLCHAR;
16315
16316     if (len == 0) return;
16317
16318     if (commentList[index] != NULL) {
16319       Boolean addClosingBrace = addBraces;
16320         old = commentList[index];
16321         oldlen = strlen(old);
16322         while(commentList[index][oldlen-1] ==  '\n')
16323           commentList[index][--oldlen] = NULLCHAR;
16324         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16325         safeStrCpy(commentList[index], old, oldlen + len + 6);
16326         free(old);
16327         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16328         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16329           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16330           while (*text == '\n') { text++; len--; }
16331           commentList[index][--oldlen] = NULLCHAR;
16332       }
16333         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16334         else          strcat(commentList[index], "\n");
16335         strcat(commentList[index], text);
16336         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16337         else          strcat(commentList[index], "\n");
16338     } else {
16339         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16340         if(addBraces)
16341           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16342         else commentList[index][0] = NULLCHAR;
16343         strcat(commentList[index], text);
16344         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16345         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16346     }
16347 }
16348
16349 static char *
16350 FindStr (char * text, char * sub_text)
16351 {
16352     char * result = strstr( text, sub_text );
16353
16354     if( result != NULL ) {
16355         result += strlen( sub_text );
16356     }
16357
16358     return result;
16359 }
16360
16361 /* [AS] Try to extract PV info from PGN comment */
16362 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16363 char *
16364 GetInfoFromComment (int index, char * text)
16365 {
16366     char * sep = text, *p;
16367
16368     if( text != NULL && index > 0 ) {
16369         int score = 0;
16370         int depth = 0;
16371         int time = -1, sec = 0, deci;
16372         char * s_eval = FindStr( text, "[%eval " );
16373         char * s_emt = FindStr( text, "[%emt " );
16374 #if 0
16375         if( s_eval != NULL || s_emt != NULL ) {
16376 #else
16377         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16378 #endif
16379             /* New style */
16380             char delim;
16381
16382             if( s_eval != NULL ) {
16383                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16384                     return text;
16385                 }
16386
16387                 if( delim != ']' ) {
16388                     return text;
16389                 }
16390             }
16391
16392             if( s_emt != NULL ) {
16393             }
16394                 return text;
16395         }
16396         else {
16397             /* We expect something like: [+|-]nnn.nn/dd */
16398             int score_lo = 0;
16399
16400             if(*text != '{') return text; // [HGM] braces: must be normal comment
16401
16402             sep = strchr( text, '/' );
16403             if( sep == NULL || sep < (text+4) ) {
16404                 return text;
16405             }
16406
16407             p = text;
16408             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16409             if(p[1] == '(') { // comment starts with PV
16410                p = strchr(p, ')'); // locate end of PV
16411                if(p == NULL || sep < p+5) return text;
16412                // at this point we have something like "{(.*) +0.23/6 ..."
16413                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16414                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16415                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16416             }
16417             time = -1; sec = -1; deci = -1;
16418             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16419                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16420                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16421                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16422                 return text;
16423             }
16424
16425             if( score_lo < 0 || score_lo >= 100 ) {
16426                 return text;
16427             }
16428
16429             if(sec >= 0) time = 600*time + 10*sec; else
16430             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16431
16432             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16433
16434             /* [HGM] PV time: now locate end of PV info */
16435             while( *++sep >= '0' && *sep <= '9'); // strip depth
16436             if(time >= 0)
16437             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16438             if(sec >= 0)
16439             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16440             if(deci >= 0)
16441             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16442             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16443         }
16444
16445         if( depth <= 0 ) {
16446             return text;
16447         }
16448
16449         if( time < 0 ) {
16450             time = -1;
16451         }
16452
16453         pvInfoList[index-1].depth = depth;
16454         pvInfoList[index-1].score = score;
16455         pvInfoList[index-1].time  = 10*time; // centi-sec
16456         if(*sep == '}') *sep = 0; else *--sep = '{';
16457         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16458     }
16459     return sep;
16460 }
16461
16462 void
16463 SendToProgram (char *message, ChessProgramState *cps)
16464 {
16465     int count, outCount, error;
16466     char buf[MSG_SIZ];
16467
16468     if (cps->pr == NoProc) return;
16469     Attention(cps);
16470
16471     if (appData.debugMode) {
16472         TimeMark now;
16473         GetTimeMark(&now);
16474         fprintf(debugFP, "%ld >%-6s: %s",
16475                 SubtractTimeMarks(&now, &programStartTime),
16476                 cps->which, message);
16477         if(serverFP)
16478             fprintf(serverFP, "%ld >%-6s: %s",
16479                 SubtractTimeMarks(&now, &programStartTime),
16480                 cps->which, message), fflush(serverFP);
16481     }
16482
16483     count = strlen(message);
16484     outCount = OutputToProcess(cps->pr, message, count, &error);
16485     if (outCount < count && !exiting
16486                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16487       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16488       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16489         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16490             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16491                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16492                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16493                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16494             } else {
16495                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16496                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16497                 gameInfo.result = res;
16498             }
16499             gameInfo.resultDetails = StrSave(buf);
16500         }
16501         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16502         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16503     }
16504 }
16505
16506 void
16507 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16508 {
16509     char *end_str;
16510     char buf[MSG_SIZ];
16511     ChessProgramState *cps = (ChessProgramState *)closure;
16512
16513     if (isr != cps->isr) return; /* Killed intentionally */
16514     if (count <= 0) {
16515         if (count == 0) {
16516             RemoveInputSource(cps->isr);
16517             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16518                     _(cps->which), cps->program);
16519             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16520             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16521                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16522                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16523                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16524                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16525                 } else {
16526                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16527                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16528                     gameInfo.result = res;
16529                 }
16530                 gameInfo.resultDetails = StrSave(buf);
16531             }
16532             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16533             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16534         } else {
16535             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16536                     _(cps->which), cps->program);
16537             RemoveInputSource(cps->isr);
16538
16539             /* [AS] Program is misbehaving badly... kill it */
16540             if( count == -2 ) {
16541                 DestroyChildProcess( cps->pr, 9 );
16542                 cps->pr = NoProc;
16543             }
16544
16545             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16546         }
16547         return;
16548     }
16549
16550     if ((end_str = strchr(message, '\r')) != NULL)
16551       *end_str = NULLCHAR;
16552     if ((end_str = strchr(message, '\n')) != NULL)
16553       *end_str = NULLCHAR;
16554
16555     if (appData.debugMode) {
16556         TimeMark now; int print = 1;
16557         char *quote = ""; char c; int i;
16558
16559         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16560                 char start = message[0];
16561                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16562                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16563                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16564                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16565                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16566                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16567                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16568                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16569                    sscanf(message, "hint: %c", &c)!=1 &&
16570                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16571                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16572                     print = (appData.engineComments >= 2);
16573                 }
16574                 message[0] = start; // restore original message
16575         }
16576         if(print) {
16577                 GetTimeMark(&now);
16578                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16579                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16580                         quote,
16581                         message);
16582                 if(serverFP)
16583                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16584                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16585                         quote,
16586                         message), fflush(serverFP);
16587         }
16588     }
16589
16590     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16591     if (appData.icsEngineAnalyze) {
16592         if (strstr(message, "whisper") != NULL ||
16593              strstr(message, "kibitz") != NULL ||
16594             strstr(message, "tellics") != NULL) return;
16595     }
16596
16597     HandleMachineMove(message, cps);
16598 }
16599
16600
16601 void
16602 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16603 {
16604     char buf[MSG_SIZ];
16605     int seconds;
16606
16607     if( timeControl_2 > 0 ) {
16608         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16609             tc = timeControl_2;
16610         }
16611     }
16612     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16613     inc /= cps->timeOdds;
16614     st  /= cps->timeOdds;
16615
16616     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16617
16618     if (st > 0) {
16619       /* Set exact time per move, normally using st command */
16620       if (cps->stKludge) {
16621         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16622         seconds = st % 60;
16623         if (seconds == 0) {
16624           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16625         } else {
16626           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16627         }
16628       } else {
16629         snprintf(buf, MSG_SIZ, "st %d\n", st);
16630       }
16631     } else {
16632       /* Set conventional or incremental time control, using level command */
16633       if (seconds == 0) {
16634         /* Note old gnuchess bug -- minutes:seconds used to not work.
16635            Fixed in later versions, but still avoid :seconds
16636            when seconds is 0. */
16637         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16638       } else {
16639         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16640                  seconds, inc/1000.);
16641       }
16642     }
16643     SendToProgram(buf, cps);
16644
16645     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16646     /* Orthogonally, limit search to given depth */
16647     if (sd > 0) {
16648       if (cps->sdKludge) {
16649         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16650       } else {
16651         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16652       }
16653       SendToProgram(buf, cps);
16654     }
16655
16656     if(cps->nps >= 0) { /* [HGM] nps */
16657         if(cps->supportsNPS == FALSE)
16658           cps->nps = -1; // don't use if engine explicitly says not supported!
16659         else {
16660           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16661           SendToProgram(buf, cps);
16662         }
16663     }
16664 }
16665
16666 ChessProgramState *
16667 WhitePlayer ()
16668 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16669 {
16670     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16671        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16672         return &second;
16673     return &first;
16674 }
16675
16676 void
16677 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16678 {
16679     char message[MSG_SIZ];
16680     long time, otime;
16681
16682     /* Note: this routine must be called when the clocks are stopped
16683        or when they have *just* been set or switched; otherwise
16684        it will be off by the time since the current tick started.
16685     */
16686     if (machineWhite) {
16687         time = whiteTimeRemaining / 10;
16688         otime = blackTimeRemaining / 10;
16689     } else {
16690         time = blackTimeRemaining / 10;
16691         otime = whiteTimeRemaining / 10;
16692     }
16693     /* [HGM] translate opponent's time by time-odds factor */
16694     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16695
16696     if (time <= 0) time = 1;
16697     if (otime <= 0) otime = 1;
16698
16699     snprintf(message, MSG_SIZ, "time %ld\n", time);
16700     SendToProgram(message, cps);
16701
16702     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16703     SendToProgram(message, cps);
16704 }
16705
16706 char *
16707 EngineDefinedVariant (ChessProgramState *cps, int n)
16708 {   // return name of n-th unknown variant that engine supports
16709     static char buf[MSG_SIZ];
16710     char *p, *s = cps->variants;
16711     if(!s) return NULL;
16712     do { // parse string from variants feature
16713       VariantClass v;
16714         p = strchr(s, ',');
16715         if(p) *p = NULLCHAR;
16716       v = StringToVariant(s);
16717       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16718         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16719             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16720                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16721                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16722                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16723             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16724         }
16725         if(p) *p++ = ',';
16726         if(n < 0) return buf;
16727     } while(s = p);
16728     return NULL;
16729 }
16730
16731 int
16732 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16733 {
16734   char buf[MSG_SIZ];
16735   int len = strlen(name);
16736   int val;
16737
16738   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16739     (*p) += len + 1;
16740     sscanf(*p, "%d", &val);
16741     *loc = (val != 0);
16742     while (**p && **p != ' ')
16743       (*p)++;
16744     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16745     SendToProgram(buf, cps);
16746     return TRUE;
16747   }
16748   return FALSE;
16749 }
16750
16751 int
16752 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16753 {
16754   char buf[MSG_SIZ];
16755   int len = strlen(name);
16756   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16757     (*p) += len + 1;
16758     sscanf(*p, "%d", loc);
16759     while (**p && **p != ' ') (*p)++;
16760     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16761     SendToProgram(buf, cps);
16762     return TRUE;
16763   }
16764   return FALSE;
16765 }
16766
16767 int
16768 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16769 {
16770   char buf[MSG_SIZ];
16771   int len = strlen(name);
16772   if (strncmp((*p), name, len) == 0
16773       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16774     (*p) += len + 2;
16775     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16776     sscanf(*p, "%[^\"]", *loc);
16777     while (**p && **p != '\"') (*p)++;
16778     if (**p == '\"') (*p)++;
16779     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16780     SendToProgram(buf, cps);
16781     return TRUE;
16782   }
16783   return FALSE;
16784 }
16785
16786 int
16787 ParseOption (Option *opt, ChessProgramState *cps)
16788 // [HGM] options: process the string that defines an engine option, and determine
16789 // name, type, default value, and allowed value range
16790 {
16791         char *p, *q, buf[MSG_SIZ];
16792         int n, min = (-1)<<31, max = 1<<31, def;
16793
16794         if(p = strstr(opt->name, " -spin ")) {
16795             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16796             if(max < min) max = min; // enforce consistency
16797             if(def < min) def = min;
16798             if(def > max) def = max;
16799             opt->value = def;
16800             opt->min = min;
16801             opt->max = max;
16802             opt->type = Spin;
16803         } else if((p = strstr(opt->name, " -slider "))) {
16804             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16805             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16806             if(max < min) max = min; // enforce consistency
16807             if(def < min) def = min;
16808             if(def > max) def = max;
16809             opt->value = def;
16810             opt->min = min;
16811             opt->max = max;
16812             opt->type = Spin; // Slider;
16813         } else if((p = strstr(opt->name, " -string "))) {
16814             opt->textValue = p+9;
16815             opt->type = TextBox;
16816         } else if((p = strstr(opt->name, " -file "))) {
16817             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16818             opt->textValue = p+7;
16819             opt->type = FileName; // FileName;
16820         } else if((p = strstr(opt->name, " -path "))) {
16821             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16822             opt->textValue = p+7;
16823             opt->type = PathName; // PathName;
16824         } else if(p = strstr(opt->name, " -check ")) {
16825             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16826             opt->value = (def != 0);
16827             opt->type = CheckBox;
16828         } else if(p = strstr(opt->name, " -combo ")) {
16829             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16830             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16831             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16832             opt->value = n = 0;
16833             while(q = StrStr(q, " /// ")) {
16834                 n++; *q = 0;    // count choices, and null-terminate each of them
16835                 q += 5;
16836                 if(*q == '*') { // remember default, which is marked with * prefix
16837                     q++;
16838                     opt->value = n;
16839                 }
16840                 cps->comboList[cps->comboCnt++] = q;
16841             }
16842             cps->comboList[cps->comboCnt++] = NULL;
16843             opt->max = n + 1;
16844             opt->type = ComboBox;
16845         } else if(p = strstr(opt->name, " -button")) {
16846             opt->type = Button;
16847         } else if(p = strstr(opt->name, " -save")) {
16848             opt->type = SaveButton;
16849         } else return FALSE;
16850         *p = 0; // terminate option name
16851         // now look if the command-line options define a setting for this engine option.
16852         if(cps->optionSettings && cps->optionSettings[0])
16853             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16854         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16855           snprintf(buf, MSG_SIZ, "option %s", p);
16856                 if(p = strstr(buf, ",")) *p = 0;
16857                 if(q = strchr(buf, '=')) switch(opt->type) {
16858                     case ComboBox:
16859                         for(n=0; n<opt->max; n++)
16860                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16861                         break;
16862                     case TextBox:
16863                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16864                         break;
16865                     case Spin:
16866                     case CheckBox:
16867                         opt->value = atoi(q+1);
16868                     default:
16869                         break;
16870                 }
16871                 strcat(buf, "\n");
16872                 SendToProgram(buf, cps);
16873         }
16874         return TRUE;
16875 }
16876
16877 void
16878 FeatureDone (ChessProgramState *cps, int val)
16879 {
16880   DelayedEventCallback cb = GetDelayedEvent();
16881   if ((cb == InitBackEnd3 && cps == &first) ||
16882       (cb == SettingsMenuIfReady && cps == &second) ||
16883       (cb == LoadEngine) ||
16884       (cb == TwoMachinesEventIfReady)) {
16885     CancelDelayedEvent();
16886     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16887   }
16888   cps->initDone = val;
16889   if(val) cps->reload = FALSE;
16890 }
16891
16892 /* Parse feature command from engine */
16893 void
16894 ParseFeatures (char *args, ChessProgramState *cps)
16895 {
16896   char *p = args;
16897   char *q = NULL;
16898   int val;
16899   char buf[MSG_SIZ];
16900
16901   for (;;) {
16902     while (*p == ' ') p++;
16903     if (*p == NULLCHAR) return;
16904
16905     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16906     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16907     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16908     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16909     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16910     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16911     if (BoolFeature(&p, "reuse", &val, cps)) {
16912       /* Engine can disable reuse, but can't enable it if user said no */
16913       if (!val) cps->reuse = FALSE;
16914       continue;
16915     }
16916     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16917     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16918       if (gameMode == TwoMachinesPlay) {
16919         DisplayTwoMachinesTitle();
16920       } else {
16921         DisplayTitle("");
16922       }
16923       continue;
16924     }
16925     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16926     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16927     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16928     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16929     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16930     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16931     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16932     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16933     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16934     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16935     if (IntFeature(&p, "done", &val, cps)) {
16936       FeatureDone(cps, val);
16937       continue;
16938     }
16939     /* Added by Tord: */
16940     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16941     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16942     /* End of additions by Tord */
16943
16944     /* [HGM] added features: */
16945     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16946     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16947     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16948     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16949     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16950     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16951     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16952     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16953         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16954         FREE(cps->option[cps->nrOptions].name);
16955         cps->option[cps->nrOptions].name = q; q = NULL;
16956         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16957           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16958             SendToProgram(buf, cps);
16959             continue;
16960         }
16961         if(cps->nrOptions >= MAX_OPTIONS) {
16962             cps->nrOptions--;
16963             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16964             DisplayError(buf, 0);
16965         }
16966         continue;
16967     }
16968     /* End of additions by HGM */
16969
16970     /* unknown feature: complain and skip */
16971     q = p;
16972     while (*q && *q != '=') q++;
16973     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16974     SendToProgram(buf, cps);
16975     p = q;
16976     if (*p == '=') {
16977       p++;
16978       if (*p == '\"') {
16979         p++;
16980         while (*p && *p != '\"') p++;
16981         if (*p == '\"') p++;
16982       } else {
16983         while (*p && *p != ' ') p++;
16984       }
16985     }
16986   }
16987
16988 }
16989
16990 void
16991 PeriodicUpdatesEvent (int newState)
16992 {
16993     if (newState == appData.periodicUpdates)
16994       return;
16995
16996     appData.periodicUpdates=newState;
16997
16998     /* Display type changes, so update it now */
16999 //    DisplayAnalysis();
17000
17001     /* Get the ball rolling again... */
17002     if (newState) {
17003         AnalysisPeriodicEvent(1);
17004         StartAnalysisClock();
17005     }
17006 }
17007
17008 void
17009 PonderNextMoveEvent (int newState)
17010 {
17011     if (newState == appData.ponderNextMove) return;
17012     if (gameMode == EditPosition) EditPositionDone(TRUE);
17013     if (newState) {
17014         SendToProgram("hard\n", &first);
17015         if (gameMode == TwoMachinesPlay) {
17016             SendToProgram("hard\n", &second);
17017         }
17018     } else {
17019         SendToProgram("easy\n", &first);
17020         thinkOutput[0] = NULLCHAR;
17021         if (gameMode == TwoMachinesPlay) {
17022             SendToProgram("easy\n", &second);
17023         }
17024     }
17025     appData.ponderNextMove = newState;
17026 }
17027
17028 void
17029 NewSettingEvent (int option, int *feature, char *command, int value)
17030 {
17031     char buf[MSG_SIZ];
17032
17033     if (gameMode == EditPosition) EditPositionDone(TRUE);
17034     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17035     if(feature == NULL || *feature) SendToProgram(buf, &first);
17036     if (gameMode == TwoMachinesPlay) {
17037         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17038     }
17039 }
17040
17041 void
17042 ShowThinkingEvent ()
17043 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17044 {
17045     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17046     int newState = appData.showThinking
17047         // [HGM] thinking: other features now need thinking output as well
17048         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17049
17050     if (oldState == newState) return;
17051     oldState = newState;
17052     if (gameMode == EditPosition) EditPositionDone(TRUE);
17053     if (oldState) {
17054         SendToProgram("post\n", &first);
17055         if (gameMode == TwoMachinesPlay) {
17056             SendToProgram("post\n", &second);
17057         }
17058     } else {
17059         SendToProgram("nopost\n", &first);
17060         thinkOutput[0] = NULLCHAR;
17061         if (gameMode == TwoMachinesPlay) {
17062             SendToProgram("nopost\n", &second);
17063         }
17064     }
17065 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17066 }
17067
17068 void
17069 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17070 {
17071   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17072   if (pr == NoProc) return;
17073   AskQuestion(title, question, replyPrefix, pr);
17074 }
17075
17076 void
17077 TypeInEvent (char firstChar)
17078 {
17079     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17080         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17081         gameMode == AnalyzeMode || gameMode == EditGame ||
17082         gameMode == EditPosition || gameMode == IcsExamining ||
17083         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17084         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17085                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17086                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17087         gameMode == Training) PopUpMoveDialog(firstChar);
17088 }
17089
17090 void
17091 TypeInDoneEvent (char *move)
17092 {
17093         Board board;
17094         int n, fromX, fromY, toX, toY;
17095         char promoChar;
17096         ChessMove moveType;
17097
17098         // [HGM] FENedit
17099         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17100                 EditPositionPasteFEN(move);
17101                 return;
17102         }
17103         // [HGM] movenum: allow move number to be typed in any mode
17104         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17105           ToNrEvent(2*n-1);
17106           return;
17107         }
17108         // undocumented kludge: allow command-line option to be typed in!
17109         // (potentially fatal, and does not implement the effect of the option.)
17110         // should only be used for options that are values on which future decisions will be made,
17111         // and definitely not on options that would be used during initialization.
17112         if(strstr(move, "!!! -") == move) {
17113             ParseArgsFromString(move+4);
17114             return;
17115         }
17116
17117       if (gameMode != EditGame && currentMove != forwardMostMove &&
17118         gameMode != Training) {
17119         DisplayMoveError(_("Displayed move is not current"));
17120       } else {
17121         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17122           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17123         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17124         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17125           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17126           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17127         } else {
17128           DisplayMoveError(_("Could not parse move"));
17129         }
17130       }
17131 }
17132
17133 void
17134 DisplayMove (int moveNumber)
17135 {
17136     char message[MSG_SIZ];
17137     char res[MSG_SIZ];
17138     char cpThinkOutput[MSG_SIZ];
17139
17140     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17141
17142     if (moveNumber == forwardMostMove - 1 ||
17143         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17144
17145         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17146
17147         if (strchr(cpThinkOutput, '\n')) {
17148             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17149         }
17150     } else {
17151         *cpThinkOutput = NULLCHAR;
17152     }
17153
17154     /* [AS] Hide thinking from human user */
17155     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17156         *cpThinkOutput = NULLCHAR;
17157         if( thinkOutput[0] != NULLCHAR ) {
17158             int i;
17159
17160             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17161                 cpThinkOutput[i] = '.';
17162             }
17163             cpThinkOutput[i] = NULLCHAR;
17164             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17165         }
17166     }
17167
17168     if (moveNumber == forwardMostMove - 1 &&
17169         gameInfo.resultDetails != NULL) {
17170         if (gameInfo.resultDetails[0] == NULLCHAR) {
17171           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17172         } else {
17173           snprintf(res, MSG_SIZ, " {%s} %s",
17174                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17175         }
17176     } else {
17177         res[0] = NULLCHAR;
17178     }
17179
17180     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17181         DisplayMessage(res, cpThinkOutput);
17182     } else {
17183       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17184                 WhiteOnMove(moveNumber) ? " " : ".. ",
17185                 parseList[moveNumber], res);
17186         DisplayMessage(message, cpThinkOutput);
17187     }
17188 }
17189
17190 void
17191 DisplayComment (int moveNumber, char *text)
17192 {
17193     char title[MSG_SIZ];
17194
17195     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17196       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17197     } else {
17198       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17199               WhiteOnMove(moveNumber) ? " " : ".. ",
17200               parseList[moveNumber]);
17201     }
17202     if (text != NULL && (appData.autoDisplayComment || commentUp))
17203         CommentPopUp(title, text);
17204 }
17205
17206 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17207  * might be busy thinking or pondering.  It can be omitted if your
17208  * gnuchess is configured to stop thinking immediately on any user
17209  * input.  However, that gnuchess feature depends on the FIONREAD
17210  * ioctl, which does not work properly on some flavors of Unix.
17211  */
17212 void
17213 Attention (ChessProgramState *cps)
17214 {
17215 #if ATTENTION
17216     if (!cps->useSigint) return;
17217     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17218     switch (gameMode) {
17219       case MachinePlaysWhite:
17220       case MachinePlaysBlack:
17221       case TwoMachinesPlay:
17222       case IcsPlayingWhite:
17223       case IcsPlayingBlack:
17224       case AnalyzeMode:
17225       case AnalyzeFile:
17226         /* Skip if we know it isn't thinking */
17227         if (!cps->maybeThinking) return;
17228         if (appData.debugMode)
17229           fprintf(debugFP, "Interrupting %s\n", cps->which);
17230         InterruptChildProcess(cps->pr);
17231         cps->maybeThinking = FALSE;
17232         break;
17233       default:
17234         break;
17235     }
17236 #endif /*ATTENTION*/
17237 }
17238
17239 int
17240 CheckFlags ()
17241 {
17242     if (whiteTimeRemaining <= 0) {
17243         if (!whiteFlag) {
17244             whiteFlag = TRUE;
17245             if (appData.icsActive) {
17246                 if (appData.autoCallFlag &&
17247                     gameMode == IcsPlayingBlack && !blackFlag) {
17248                   SendToICS(ics_prefix);
17249                   SendToICS("flag\n");
17250                 }
17251             } else {
17252                 if (blackFlag) {
17253                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17254                 } else {
17255                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17256                     if (appData.autoCallFlag) {
17257                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17258                         return TRUE;
17259                     }
17260                 }
17261             }
17262         }
17263     }
17264     if (blackTimeRemaining <= 0) {
17265         if (!blackFlag) {
17266             blackFlag = TRUE;
17267             if (appData.icsActive) {
17268                 if (appData.autoCallFlag &&
17269                     gameMode == IcsPlayingWhite && !whiteFlag) {
17270                   SendToICS(ics_prefix);
17271                   SendToICS("flag\n");
17272                 }
17273             } else {
17274                 if (whiteFlag) {
17275                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17276                 } else {
17277                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17278                     if (appData.autoCallFlag) {
17279                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17280                         return TRUE;
17281                     }
17282                 }
17283             }
17284         }
17285     }
17286     return FALSE;
17287 }
17288
17289 void
17290 CheckTimeControl ()
17291 {
17292     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17293         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17294
17295     /*
17296      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17297      */
17298     if ( !WhiteOnMove(forwardMostMove) ) {
17299         /* White made time control */
17300         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17301         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17302         /* [HGM] time odds: correct new time quota for time odds! */
17303                                             / WhitePlayer()->timeOdds;
17304         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17305     } else {
17306         lastBlack -= blackTimeRemaining;
17307         /* Black made time control */
17308         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17309                                             / WhitePlayer()->other->timeOdds;
17310         lastWhite = whiteTimeRemaining;
17311     }
17312 }
17313
17314 void
17315 DisplayBothClocks ()
17316 {
17317     int wom = gameMode == EditPosition ?
17318       !blackPlaysFirst : WhiteOnMove(currentMove);
17319     DisplayWhiteClock(whiteTimeRemaining, wom);
17320     DisplayBlackClock(blackTimeRemaining, !wom);
17321 }
17322
17323
17324 /* Timekeeping seems to be a portability nightmare.  I think everyone
17325    has ftime(), but I'm really not sure, so I'm including some ifdefs
17326    to use other calls if you don't.  Clocks will be less accurate if
17327    you have neither ftime nor gettimeofday.
17328 */
17329
17330 /* VS 2008 requires the #include outside of the function */
17331 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17332 #include <sys/timeb.h>
17333 #endif
17334
17335 /* Get the current time as a TimeMark */
17336 void
17337 GetTimeMark (TimeMark *tm)
17338 {
17339 #if HAVE_GETTIMEOFDAY
17340
17341     struct timeval timeVal;
17342     struct timezone timeZone;
17343
17344     gettimeofday(&timeVal, &timeZone);
17345     tm->sec = (long) timeVal.tv_sec;
17346     tm->ms = (int) (timeVal.tv_usec / 1000L);
17347
17348 #else /*!HAVE_GETTIMEOFDAY*/
17349 #if HAVE_FTIME
17350
17351 // include <sys/timeb.h> / moved to just above start of function
17352     struct timeb timeB;
17353
17354     ftime(&timeB);
17355     tm->sec = (long) timeB.time;
17356     tm->ms = (int) timeB.millitm;
17357
17358 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17359     tm->sec = (long) time(NULL);
17360     tm->ms = 0;
17361 #endif
17362 #endif
17363 }
17364
17365 /* Return the difference in milliseconds between two
17366    time marks.  We assume the difference will fit in a long!
17367 */
17368 long
17369 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17370 {
17371     return 1000L*(tm2->sec - tm1->sec) +
17372            (long) (tm2->ms - tm1->ms);
17373 }
17374
17375
17376 /*
17377  * Code to manage the game clocks.
17378  *
17379  * In tournament play, black starts the clock and then white makes a move.
17380  * We give the human user a slight advantage if he is playing white---the
17381  * clocks don't run until he makes his first move, so it takes zero time.
17382  * Also, we don't account for network lag, so we could get out of sync
17383  * with GNU Chess's clock -- but then, referees are always right.
17384  */
17385
17386 static TimeMark tickStartTM;
17387 static long intendedTickLength;
17388
17389 long
17390 NextTickLength (long timeRemaining)
17391 {
17392     long nominalTickLength, nextTickLength;
17393
17394     if (timeRemaining > 0L && timeRemaining <= 10000L)
17395       nominalTickLength = 100L;
17396     else
17397       nominalTickLength = 1000L;
17398     nextTickLength = timeRemaining % nominalTickLength;
17399     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17400
17401     return nextTickLength;
17402 }
17403
17404 /* Adjust clock one minute up or down */
17405 void
17406 AdjustClock (Boolean which, int dir)
17407 {
17408     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17409     if(which) blackTimeRemaining += 60000*dir;
17410     else      whiteTimeRemaining += 60000*dir;
17411     DisplayBothClocks();
17412     adjustedClock = TRUE;
17413 }
17414
17415 /* Stop clocks and reset to a fresh time control */
17416 void
17417 ResetClocks ()
17418 {
17419     (void) StopClockTimer();
17420     if (appData.icsActive) {
17421         whiteTimeRemaining = blackTimeRemaining = 0;
17422     } else if (searchTime) {
17423         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17424         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17425     } else { /* [HGM] correct new time quote for time odds */
17426         whiteTC = blackTC = fullTimeControlString;
17427         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17428         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17429     }
17430     if (whiteFlag || blackFlag) {
17431         DisplayTitle("");
17432         whiteFlag = blackFlag = FALSE;
17433     }
17434     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17435     DisplayBothClocks();
17436     adjustedClock = FALSE;
17437 }
17438
17439 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17440
17441 /* Decrement running clock by amount of time that has passed */
17442 void
17443 DecrementClocks ()
17444 {
17445     long timeRemaining;
17446     long lastTickLength, fudge;
17447     TimeMark now;
17448
17449     if (!appData.clockMode) return;
17450     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17451
17452     GetTimeMark(&now);
17453
17454     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17455
17456     /* Fudge if we woke up a little too soon */
17457     fudge = intendedTickLength - lastTickLength;
17458     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17459
17460     if (WhiteOnMove(forwardMostMove)) {
17461         if(whiteNPS >= 0) lastTickLength = 0;
17462         timeRemaining = whiteTimeRemaining -= lastTickLength;
17463         if(timeRemaining < 0 && !appData.icsActive) {
17464             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17465             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17466                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17467                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17468             }
17469         }
17470         DisplayWhiteClock(whiteTimeRemaining - fudge,
17471                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17472     } else {
17473         if(blackNPS >= 0) lastTickLength = 0;
17474         timeRemaining = blackTimeRemaining -= lastTickLength;
17475         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17476             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17477             if(suddenDeath) {
17478                 blackStartMove = forwardMostMove;
17479                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17480             }
17481         }
17482         DisplayBlackClock(blackTimeRemaining - fudge,
17483                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17484     }
17485     if (CheckFlags()) return;
17486
17487     if(twoBoards) { // count down secondary board's clocks as well
17488         activePartnerTime -= lastTickLength;
17489         partnerUp = 1;
17490         if(activePartner == 'W')
17491             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17492         else
17493             DisplayBlackClock(activePartnerTime, TRUE);
17494         partnerUp = 0;
17495     }
17496
17497     tickStartTM = now;
17498     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17499     StartClockTimer(intendedTickLength);
17500
17501     /* if the time remaining has fallen below the alarm threshold, sound the
17502      * alarm. if the alarm has sounded and (due to a takeback or time control
17503      * with increment) the time remaining has increased to a level above the
17504      * threshold, reset the alarm so it can sound again.
17505      */
17506
17507     if (appData.icsActive && appData.icsAlarm) {
17508
17509         /* make sure we are dealing with the user's clock */
17510         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17511                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17512            )) return;
17513
17514         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17515             alarmSounded = FALSE;
17516         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17517             PlayAlarmSound();
17518             alarmSounded = TRUE;
17519         }
17520     }
17521 }
17522
17523
17524 /* A player has just moved, so stop the previously running
17525    clock and (if in clock mode) start the other one.
17526    We redisplay both clocks in case we're in ICS mode, because
17527    ICS gives us an update to both clocks after every move.
17528    Note that this routine is called *after* forwardMostMove
17529    is updated, so the last fractional tick must be subtracted
17530    from the color that is *not* on move now.
17531 */
17532 void
17533 SwitchClocks (int newMoveNr)
17534 {
17535     long lastTickLength;
17536     TimeMark now;
17537     int flagged = FALSE;
17538
17539     GetTimeMark(&now);
17540
17541     if (StopClockTimer() && appData.clockMode) {
17542         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17543         if (!WhiteOnMove(forwardMostMove)) {
17544             if(blackNPS >= 0) lastTickLength = 0;
17545             blackTimeRemaining -= lastTickLength;
17546            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17547 //         if(pvInfoList[forwardMostMove].time == -1)
17548                  pvInfoList[forwardMostMove].time =               // use GUI time
17549                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17550         } else {
17551            if(whiteNPS >= 0) lastTickLength = 0;
17552            whiteTimeRemaining -= lastTickLength;
17553            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17554 //         if(pvInfoList[forwardMostMove].time == -1)
17555                  pvInfoList[forwardMostMove].time =
17556                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17557         }
17558         flagged = CheckFlags();
17559     }
17560     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17561     CheckTimeControl();
17562
17563     if (flagged || !appData.clockMode) return;
17564
17565     switch (gameMode) {
17566       case MachinePlaysBlack:
17567       case MachinePlaysWhite:
17568       case BeginningOfGame:
17569         if (pausing) return;
17570         break;
17571
17572       case EditGame:
17573       case PlayFromGameFile:
17574       case IcsExamining:
17575         return;
17576
17577       default:
17578         break;
17579     }
17580
17581     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17582         if(WhiteOnMove(forwardMostMove))
17583              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17584         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17585     }
17586
17587     tickStartTM = now;
17588     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17589       whiteTimeRemaining : blackTimeRemaining);
17590     StartClockTimer(intendedTickLength);
17591 }
17592
17593
17594 /* Stop both clocks */
17595 void
17596 StopClocks ()
17597 {
17598     long lastTickLength;
17599     TimeMark now;
17600
17601     if (!StopClockTimer()) return;
17602     if (!appData.clockMode) return;
17603
17604     GetTimeMark(&now);
17605
17606     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17607     if (WhiteOnMove(forwardMostMove)) {
17608         if(whiteNPS >= 0) lastTickLength = 0;
17609         whiteTimeRemaining -= lastTickLength;
17610         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17611     } else {
17612         if(blackNPS >= 0) lastTickLength = 0;
17613         blackTimeRemaining -= lastTickLength;
17614         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17615     }
17616     CheckFlags();
17617 }
17618
17619 /* Start clock of player on move.  Time may have been reset, so
17620    if clock is already running, stop and restart it. */
17621 void
17622 StartClocks ()
17623 {
17624     (void) StopClockTimer(); /* in case it was running already */
17625     DisplayBothClocks();
17626     if (CheckFlags()) return;
17627
17628     if (!appData.clockMode) return;
17629     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17630
17631     GetTimeMark(&tickStartTM);
17632     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17633       whiteTimeRemaining : blackTimeRemaining);
17634
17635    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17636     whiteNPS = blackNPS = -1;
17637     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17638        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17639         whiteNPS = first.nps;
17640     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17641        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17642         blackNPS = first.nps;
17643     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17644         whiteNPS = second.nps;
17645     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17646         blackNPS = second.nps;
17647     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17648
17649     StartClockTimer(intendedTickLength);
17650 }
17651
17652 char *
17653 TimeString (long ms)
17654 {
17655     long second, minute, hour, day;
17656     char *sign = "";
17657     static char buf[32];
17658
17659     if (ms > 0 && ms <= 9900) {
17660       /* convert milliseconds to tenths, rounding up */
17661       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17662
17663       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17664       return buf;
17665     }
17666
17667     /* convert milliseconds to seconds, rounding up */
17668     /* use floating point to avoid strangeness of integer division
17669        with negative dividends on many machines */
17670     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17671
17672     if (second < 0) {
17673         sign = "-";
17674         second = -second;
17675     }
17676
17677     day = second / (60 * 60 * 24);
17678     second = second % (60 * 60 * 24);
17679     hour = second / (60 * 60);
17680     second = second % (60 * 60);
17681     minute = second / 60;
17682     second = second % 60;
17683
17684     if (day > 0)
17685       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17686               sign, day, hour, minute, second);
17687     else if (hour > 0)
17688       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17689     else
17690       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17691
17692     return buf;
17693 }
17694
17695
17696 /*
17697  * This is necessary because some C libraries aren't ANSI C compliant yet.
17698  */
17699 char *
17700 StrStr (char *string, char *match)
17701 {
17702     int i, length;
17703
17704     length = strlen(match);
17705
17706     for (i = strlen(string) - length; i >= 0; i--, string++)
17707       if (!strncmp(match, string, length))
17708         return string;
17709
17710     return NULL;
17711 }
17712
17713 char *
17714 StrCaseStr (char *string, char *match)
17715 {
17716     int i, j, length;
17717
17718     length = strlen(match);
17719
17720     for (i = strlen(string) - length; i >= 0; i--, string++) {
17721         for (j = 0; j < length; j++) {
17722             if (ToLower(match[j]) != ToLower(string[j]))
17723               break;
17724         }
17725         if (j == length) return string;
17726     }
17727
17728     return NULL;
17729 }
17730
17731 #ifndef _amigados
17732 int
17733 StrCaseCmp (char *s1, char *s2)
17734 {
17735     char c1, c2;
17736
17737     for (;;) {
17738         c1 = ToLower(*s1++);
17739         c2 = ToLower(*s2++);
17740         if (c1 > c2) return 1;
17741         if (c1 < c2) return -1;
17742         if (c1 == NULLCHAR) return 0;
17743     }
17744 }
17745
17746
17747 int
17748 ToLower (int c)
17749 {
17750     return isupper(c) ? tolower(c) : c;
17751 }
17752
17753
17754 int
17755 ToUpper (int c)
17756 {
17757     return islower(c) ? toupper(c) : c;
17758 }
17759 #endif /* !_amigados    */
17760
17761 char *
17762 StrSave (char *s)
17763 {
17764   char *ret;
17765
17766   if ((ret = (char *) malloc(strlen(s) + 1)))
17767     {
17768       safeStrCpy(ret, s, strlen(s)+1);
17769     }
17770   return ret;
17771 }
17772
17773 char *
17774 StrSavePtr (char *s, char **savePtr)
17775 {
17776     if (*savePtr) {
17777         free(*savePtr);
17778     }
17779     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17780       safeStrCpy(*savePtr, s, strlen(s)+1);
17781     }
17782     return(*savePtr);
17783 }
17784
17785 char *
17786 PGNDate ()
17787 {
17788     time_t clock;
17789     struct tm *tm;
17790     char buf[MSG_SIZ];
17791
17792     clock = time((time_t *)NULL);
17793     tm = localtime(&clock);
17794     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17795             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17796     return StrSave(buf);
17797 }
17798
17799
17800 char *
17801 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17802 {
17803     int i, j, fromX, fromY, toX, toY;
17804     int whiteToPlay;
17805     char buf[MSG_SIZ];
17806     char *p, *q;
17807     int emptycount;
17808     ChessSquare piece;
17809
17810     whiteToPlay = (gameMode == EditPosition) ?
17811       !blackPlaysFirst : (move % 2 == 0);
17812     p = buf;
17813
17814     /* Piece placement data */
17815     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17816         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17817         emptycount = 0;
17818         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17819             if (boards[move][i][j] == EmptySquare) {
17820                 emptycount++;
17821             } else { ChessSquare piece = boards[move][i][j];
17822                 if (emptycount > 0) {
17823                     if(emptycount<10) /* [HGM] can be >= 10 */
17824                         *p++ = '0' + emptycount;
17825                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17826                     emptycount = 0;
17827                 }
17828                 if(PieceToChar(piece) == '+') {
17829                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17830                     *p++ = '+';
17831                     piece = (ChessSquare)(CHUDEMOTED piece);
17832                 }
17833                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17834                 if(p[-1] == '~') {
17835                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17836                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17837                     *p++ = '~';
17838                 }
17839             }
17840         }
17841         if (emptycount > 0) {
17842             if(emptycount<10) /* [HGM] can be >= 10 */
17843                 *p++ = '0' + emptycount;
17844             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17845             emptycount = 0;
17846         }
17847         *p++ = '/';
17848     }
17849     *(p - 1) = ' ';
17850
17851     /* [HGM] print Crazyhouse or Shogi holdings */
17852     if( gameInfo.holdingsWidth ) {
17853         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17854         q = p;
17855         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17856             piece = boards[move][i][BOARD_WIDTH-1];
17857             if( piece != EmptySquare )
17858               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17859                   *p++ = PieceToChar(piece);
17860         }
17861         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17862             piece = boards[move][BOARD_HEIGHT-i-1][0];
17863             if( piece != EmptySquare )
17864               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17865                   *p++ = PieceToChar(piece);
17866         }
17867
17868         if( q == p ) *p++ = '-';
17869         *p++ = ']';
17870         *p++ = ' ';
17871     }
17872
17873     /* Active color */
17874     *p++ = whiteToPlay ? 'w' : 'b';
17875     *p++ = ' ';
17876
17877   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17878     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17879   } else {
17880   if(nrCastlingRights) {
17881      int handW=0, handB=0;
17882      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17883         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17884         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17885      }
17886      q = p;
17887      if(appData.fischerCastling) {
17888         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17889            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17890                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17891         } else {
17892        /* [HGM] write directly from rights */
17893            if(boards[move][CASTLING][2] != NoRights &&
17894               boards[move][CASTLING][0] != NoRights   )
17895                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17896            if(boards[move][CASTLING][2] != NoRights &&
17897               boards[move][CASTLING][1] != NoRights   )
17898                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17899         }
17900         if(handB) {
17901            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17902                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17903         } else {
17904            if(boards[move][CASTLING][5] != NoRights &&
17905               boards[move][CASTLING][3] != NoRights   )
17906                 *p++ = boards[move][CASTLING][3] + AAA;
17907            if(boards[move][CASTLING][5] != NoRights &&
17908               boards[move][CASTLING][4] != NoRights   )
17909                 *p++ = boards[move][CASTLING][4] + AAA;
17910         }
17911      } else {
17912
17913         /* [HGM] write true castling rights */
17914         if( nrCastlingRights == 6 ) {
17915             int q, k=0;
17916             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17917                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17918             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17919                  boards[move][CASTLING][2] != NoRights  );
17920             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17921                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17922                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17923                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17924             }
17925             if(q) *p++ = 'Q';
17926             k = 0;
17927             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17928                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17929             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17930                  boards[move][CASTLING][5] != NoRights  );
17931             if(handB) {
17932                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17933                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17934                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17935             }
17936             if(q) *p++ = 'q';
17937         }
17938      }
17939      if (q == p) *p++ = '-'; /* No castling rights */
17940      *p++ = ' ';
17941   }
17942
17943   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17944      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17945      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17946     /* En passant target square */
17947     if (move > backwardMostMove) {
17948         fromX = moveList[move - 1][0] - AAA;
17949         fromY = moveList[move - 1][1] - ONE;
17950         toX = moveList[move - 1][2] - AAA;
17951         toY = moveList[move - 1][3] - ONE;
17952         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17953             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17954             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17955             fromX == toX) {
17956             /* 2-square pawn move just happened */
17957             *p++ = toX + AAA;
17958             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17959         } else {
17960             *p++ = '-';
17961         }
17962     } else if(move == backwardMostMove) {
17963         // [HGM] perhaps we should always do it like this, and forget the above?
17964         if((signed char)boards[move][EP_STATUS] >= 0) {
17965             *p++ = boards[move][EP_STATUS] + AAA;
17966             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17967         } else {
17968             *p++ = '-';
17969         }
17970     } else {
17971         *p++ = '-';
17972     }
17973     *p++ = ' ';
17974   }
17975   }
17976
17977     if(moveCounts)
17978     {   int i = 0, j=move;
17979
17980         /* [HGM] find reversible plies */
17981         if (appData.debugMode) { int k;
17982             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17983             for(k=backwardMostMove; k<=forwardMostMove; k++)
17984                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17985
17986         }
17987
17988         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17989         if( j == backwardMostMove ) i += initialRulePlies;
17990         sprintf(p, "%d ", i);
17991         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17992
17993         /* Fullmove number */
17994         sprintf(p, "%d", (move / 2) + 1);
17995     } else *--p = NULLCHAR;
17996
17997     return StrSave(buf);
17998 }
17999
18000 Boolean
18001 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18002 {
18003     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18004     char *p, c;
18005     int emptycount, virgin[BOARD_FILES];
18006     ChessSquare piece;
18007
18008     p = fen;
18009
18010     /* Piece placement data */
18011     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18012         j = 0;
18013         for (;;) {
18014             if (*p == '/' || *p == ' ' || *p == '[' ) {
18015                 if(j > w) w = j;
18016                 emptycount = gameInfo.boardWidth - j;
18017                 while (emptycount--)
18018                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18019                 if (*p == '/') p++;
18020                 else if(autoSize) { // we stumbled unexpectedly into end of board
18021                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18022                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18023                     }
18024                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18025                 }
18026                 break;
18027 #if(BOARD_FILES >= 10)*0
18028             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18029                 p++; emptycount=10;
18030                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18031                 while (emptycount--)
18032                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18033 #endif
18034             } else if (*p == '*') {
18035                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18036             } else if (isdigit(*p)) {
18037                 emptycount = *p++ - '0';
18038                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18039                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18040                 while (emptycount--)
18041                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18042             } else if (*p == '<') {
18043                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18044                 else if (i != 0 || !shuffle) return FALSE;
18045                 p++;
18046             } else if (shuffle && *p == '>') {
18047                 p++; // for now ignore closing shuffle range, and assume rank-end
18048             } else if (*p == '?') {
18049                 if (j >= gameInfo.boardWidth) return FALSE;
18050                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18051                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18052             } else if (*p == '+' || isalpha(*p)) {
18053                 if (j >= gameInfo.boardWidth) return FALSE;
18054                 if(*p=='+') {
18055                     piece = CharToPiece(*++p);
18056                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18057                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18058                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18059                 } else piece = CharToPiece(*p++);
18060
18061                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18062                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18063                     piece = (ChessSquare) (PROMOTED piece);
18064                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18065                     p++;
18066                 }
18067                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18068                 if(piece == WhiteKing) wKingRank = i;
18069                 if(piece == BlackKing) bKingRank = i;
18070             } else {
18071                 return FALSE;
18072             }
18073         }
18074     }
18075     while (*p == '/' || *p == ' ') p++;
18076
18077     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18078
18079     /* [HGM] by default clear Crazyhouse holdings, if present */
18080     if(gameInfo.holdingsWidth) {
18081        for(i=0; i<BOARD_HEIGHT; i++) {
18082            board[i][0]             = EmptySquare; /* black holdings */
18083            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18084            board[i][1]             = (ChessSquare) 0; /* black counts */
18085            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18086        }
18087     }
18088
18089     /* [HGM] look for Crazyhouse holdings here */
18090     while(*p==' ') p++;
18091     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18092         int swap=0, wcnt=0, bcnt=0;
18093         if(*p == '[') p++;
18094         if(*p == '<') swap++, p++;
18095         if(*p == '-' ) p++; /* empty holdings */ else {
18096             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18097             /* if we would allow FEN reading to set board size, we would   */
18098             /* have to add holdings and shift the board read so far here   */
18099             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18100                 p++;
18101                 if((int) piece >= (int) BlackPawn ) {
18102                     i = (int)piece - (int)BlackPawn;
18103                     i = PieceToNumber((ChessSquare)i);
18104                     if( i >= gameInfo.holdingsSize ) return FALSE;
18105                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18106                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18107                     bcnt++;
18108                 } else {
18109                     i = (int)piece - (int)WhitePawn;
18110                     i = PieceToNumber((ChessSquare)i);
18111                     if( i >= gameInfo.holdingsSize ) return FALSE;
18112                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18113                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18114                     wcnt++;
18115                 }
18116             }
18117             if(subst) { // substitute back-rank question marks by holdings pieces
18118                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18119                     int k, m, n = bcnt + 1;
18120                     if(board[0][j] == ClearBoard) {
18121                         if(!wcnt) return FALSE;
18122                         n = rand() % wcnt;
18123                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18124                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18125                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18126                             break;
18127                         }
18128                     }
18129                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18130                         if(!bcnt) return FALSE;
18131                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18132                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18133                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18134                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18135                             break;
18136                         }
18137                     }
18138                 }
18139                 subst = 0;
18140             }
18141         }
18142         if(*p == ']') p++;
18143     }
18144
18145     if(subst) return FALSE; // substitution requested, but no holdings
18146
18147     while(*p == ' ') p++;
18148
18149     /* Active color */
18150     c = *p++;
18151     if(appData.colorNickNames) {
18152       if( c == appData.colorNickNames[0] ) c = 'w'; else
18153       if( c == appData.colorNickNames[1] ) c = 'b';
18154     }
18155     switch (c) {
18156       case 'w':
18157         *blackPlaysFirst = FALSE;
18158         break;
18159       case 'b':
18160         *blackPlaysFirst = TRUE;
18161         break;
18162       default:
18163         return FALSE;
18164     }
18165
18166     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18167     /* return the extra info in global variiables             */
18168
18169     while(*p==' ') p++;
18170
18171     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18172         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18173         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18174     }
18175
18176     /* set defaults in case FEN is incomplete */
18177     board[EP_STATUS] = EP_UNKNOWN;
18178     for(i=0; i<nrCastlingRights; i++ ) {
18179         board[CASTLING][i] =
18180             appData.fischerCastling ? NoRights : initialRights[i];
18181     }   /* assume possible unless obviously impossible */
18182     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18183     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18184     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18185                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18186     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18187     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18188     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18189                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18190     FENrulePlies = 0;
18191
18192     if(nrCastlingRights) {
18193       int fischer = 0;
18194       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18195       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18196           /* castling indicator present, so default becomes no castlings */
18197           for(i=0; i<nrCastlingRights; i++ ) {
18198                  board[CASTLING][i] = NoRights;
18199           }
18200       }
18201       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18202              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18203              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18204              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18205         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18206
18207         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18208             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18209             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18210         }
18211         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18212             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18213         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18214                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18215         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18216                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18217         switch(c) {
18218           case'K':
18219               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18220               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18221               board[CASTLING][2] = whiteKingFile;
18222               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18223               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18224               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18225               break;
18226           case'Q':
18227               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18228               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18229               board[CASTLING][2] = whiteKingFile;
18230               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18231               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18232               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18233               break;
18234           case'k':
18235               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18236               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18237               board[CASTLING][5] = blackKingFile;
18238               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18239               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18240               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18241               break;
18242           case'q':
18243               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18244               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18245               board[CASTLING][5] = blackKingFile;
18246               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18247               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18248               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18249           case '-':
18250               break;
18251           default: /* FRC castlings */
18252               if(c >= 'a') { /* black rights */
18253                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18254                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18255                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18256                   if(i == BOARD_RGHT) break;
18257                   board[CASTLING][5] = i;
18258                   c -= AAA;
18259                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18260                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18261                   if(c > i)
18262                       board[CASTLING][3] = c;
18263                   else
18264                       board[CASTLING][4] = c;
18265               } else { /* white rights */
18266                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18267                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18268                     if(board[0][i] == WhiteKing) break;
18269                   if(i == BOARD_RGHT) break;
18270                   board[CASTLING][2] = i;
18271                   c -= AAA - 'a' + 'A';
18272                   if(board[0][c] >= WhiteKing) break;
18273                   if(c > i)
18274                       board[CASTLING][0] = c;
18275                   else
18276                       board[CASTLING][1] = c;
18277               }
18278         }
18279       }
18280       for(i=0; i<nrCastlingRights; i++)
18281         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18282       if(gameInfo.variant == VariantSChess)
18283         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18284       if(fischer && shuffle) appData.fischerCastling = TRUE;
18285     if (appData.debugMode) {
18286         fprintf(debugFP, "FEN castling rights:");
18287         for(i=0; i<nrCastlingRights; i++)
18288         fprintf(debugFP, " %d", board[CASTLING][i]);
18289         fprintf(debugFP, "\n");
18290     }
18291
18292       while(*p==' ') p++;
18293     }
18294
18295     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18296
18297     /* read e.p. field in games that know e.p. capture */
18298     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18299        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18300        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18301       if(*p=='-') {
18302         p++; board[EP_STATUS] = EP_NONE;
18303       } else {
18304          char c = *p++ - AAA;
18305
18306          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18307          if(*p >= '0' && *p <='9') p++;
18308          board[EP_STATUS] = c;
18309       }
18310     }
18311
18312
18313     if(sscanf(p, "%d", &i) == 1) {
18314         FENrulePlies = i; /* 50-move ply counter */
18315         /* (The move number is still ignored)    */
18316     }
18317
18318     return TRUE;
18319 }
18320
18321 void
18322 EditPositionPasteFEN (char *fen)
18323 {
18324   if (fen != NULL) {
18325     Board initial_position;
18326
18327     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18328       DisplayError(_("Bad FEN position in clipboard"), 0);
18329       return ;
18330     } else {
18331       int savedBlackPlaysFirst = blackPlaysFirst;
18332       EditPositionEvent();
18333       blackPlaysFirst = savedBlackPlaysFirst;
18334       CopyBoard(boards[0], initial_position);
18335       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18336       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18337       DisplayBothClocks();
18338       DrawPosition(FALSE, boards[currentMove]);
18339     }
18340   }
18341 }
18342
18343 static char cseq[12] = "\\   ";
18344
18345 Boolean
18346 set_cont_sequence (char *new_seq)
18347 {
18348     int len;
18349     Boolean ret;
18350
18351     // handle bad attempts to set the sequence
18352         if (!new_seq)
18353                 return 0; // acceptable error - no debug
18354
18355     len = strlen(new_seq);
18356     ret = (len > 0) && (len < sizeof(cseq));
18357     if (ret)
18358       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18359     else if (appData.debugMode)
18360       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18361     return ret;
18362 }
18363
18364 /*
18365     reformat a source message so words don't cross the width boundary.  internal
18366     newlines are not removed.  returns the wrapped size (no null character unless
18367     included in source message).  If dest is NULL, only calculate the size required
18368     for the dest buffer.  lp argument indicats line position upon entry, and it's
18369     passed back upon exit.
18370 */
18371 int
18372 wrap (char *dest, char *src, int count, int width, int *lp)
18373 {
18374     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18375
18376     cseq_len = strlen(cseq);
18377     old_line = line = *lp;
18378     ansi = len = clen = 0;
18379
18380     for (i=0; i < count; i++)
18381     {
18382         if (src[i] == '\033')
18383             ansi = 1;
18384
18385         // if we hit the width, back up
18386         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18387         {
18388             // store i & len in case the word is too long
18389             old_i = i, old_len = len;
18390
18391             // find the end of the last word
18392             while (i && src[i] != ' ' && src[i] != '\n')
18393             {
18394                 i--;
18395                 len--;
18396             }
18397
18398             // word too long?  restore i & len before splitting it
18399             if ((old_i-i+clen) >= width)
18400             {
18401                 i = old_i;
18402                 len = old_len;
18403             }
18404
18405             // extra space?
18406             if (i && src[i-1] == ' ')
18407                 len--;
18408
18409             if (src[i] != ' ' && src[i] != '\n')
18410             {
18411                 i--;
18412                 if (len)
18413                     len--;
18414             }
18415
18416             // now append the newline and continuation sequence
18417             if (dest)
18418                 dest[len] = '\n';
18419             len++;
18420             if (dest)
18421                 strncpy(dest+len, cseq, cseq_len);
18422             len += cseq_len;
18423             line = cseq_len;
18424             clen = cseq_len;
18425             continue;
18426         }
18427
18428         if (dest)
18429             dest[len] = src[i];
18430         len++;
18431         if (!ansi)
18432             line++;
18433         if (src[i] == '\n')
18434             line = 0;
18435         if (src[i] == 'm')
18436             ansi = 0;
18437     }
18438     if (dest && appData.debugMode)
18439     {
18440         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18441             count, width, line, len, *lp);
18442         show_bytes(debugFP, src, count);
18443         fprintf(debugFP, "\ndest: ");
18444         show_bytes(debugFP, dest, len);
18445         fprintf(debugFP, "\n");
18446     }
18447     *lp = dest ? line : old_line;
18448
18449     return len;
18450 }
18451
18452 // [HGM] vari: routines for shelving variations
18453 Boolean modeRestore = FALSE;
18454
18455 void
18456 PushInner (int firstMove, int lastMove)
18457 {
18458         int i, j, nrMoves = lastMove - firstMove;
18459
18460         // push current tail of game on stack
18461         savedResult[storedGames] = gameInfo.result;
18462         savedDetails[storedGames] = gameInfo.resultDetails;
18463         gameInfo.resultDetails = NULL;
18464         savedFirst[storedGames] = firstMove;
18465         savedLast [storedGames] = lastMove;
18466         savedFramePtr[storedGames] = framePtr;
18467         framePtr -= nrMoves; // reserve space for the boards
18468         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18469             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18470             for(j=0; j<MOVE_LEN; j++)
18471                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18472             for(j=0; j<2*MOVE_LEN; j++)
18473                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18474             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18475             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18476             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18477             pvInfoList[firstMove+i-1].depth = 0;
18478             commentList[framePtr+i] = commentList[firstMove+i];
18479             commentList[firstMove+i] = NULL;
18480         }
18481
18482         storedGames++;
18483         forwardMostMove = firstMove; // truncate game so we can start variation
18484 }
18485
18486 void
18487 PushTail (int firstMove, int lastMove)
18488 {
18489         if(appData.icsActive) { // only in local mode
18490                 forwardMostMove = currentMove; // mimic old ICS behavior
18491                 return;
18492         }
18493         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18494
18495         PushInner(firstMove, lastMove);
18496         if(storedGames == 1) GreyRevert(FALSE);
18497         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18498 }
18499
18500 void
18501 PopInner (Boolean annotate)
18502 {
18503         int i, j, nrMoves;
18504         char buf[8000], moveBuf[20];
18505
18506         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18507         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18508         nrMoves = savedLast[storedGames] - currentMove;
18509         if(annotate) {
18510                 int cnt = 10;
18511                 if(!WhiteOnMove(currentMove))
18512                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18513                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18514                 for(i=currentMove; i<forwardMostMove; i++) {
18515                         if(WhiteOnMove(i))
18516                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18517                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18518                         strcat(buf, moveBuf);
18519                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18520                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18521                 }
18522                 strcat(buf, ")");
18523         }
18524         for(i=1; i<=nrMoves; i++) { // copy last variation back
18525             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18526             for(j=0; j<MOVE_LEN; j++)
18527                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18528             for(j=0; j<2*MOVE_LEN; j++)
18529                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18530             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18531             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18532             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18533             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18534             commentList[currentMove+i] = commentList[framePtr+i];
18535             commentList[framePtr+i] = NULL;
18536         }
18537         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18538         framePtr = savedFramePtr[storedGames];
18539         gameInfo.result = savedResult[storedGames];
18540         if(gameInfo.resultDetails != NULL) {
18541             free(gameInfo.resultDetails);
18542       }
18543         gameInfo.resultDetails = savedDetails[storedGames];
18544         forwardMostMove = currentMove + nrMoves;
18545 }
18546
18547 Boolean
18548 PopTail (Boolean annotate)
18549 {
18550         if(appData.icsActive) return FALSE; // only in local mode
18551         if(!storedGames) return FALSE; // sanity
18552         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18553
18554         PopInner(annotate);
18555         if(currentMove < forwardMostMove) ForwardEvent(); else
18556         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18557
18558         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18559         return TRUE;
18560 }
18561
18562 void
18563 CleanupTail ()
18564 {       // remove all shelved variations
18565         int i;
18566         for(i=0; i<storedGames; i++) {
18567             if(savedDetails[i])
18568                 free(savedDetails[i]);
18569             savedDetails[i] = NULL;
18570         }
18571         for(i=framePtr; i<MAX_MOVES; i++) {
18572                 if(commentList[i]) free(commentList[i]);
18573                 commentList[i] = NULL;
18574         }
18575         framePtr = MAX_MOVES-1;
18576         storedGames = 0;
18577 }
18578
18579 void
18580 LoadVariation (int index, char *text)
18581 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18582         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18583         int level = 0, move;
18584
18585         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18586         // first find outermost bracketing variation
18587         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18588             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18589                 if(*p == '{') wait = '}'; else
18590                 if(*p == '[') wait = ']'; else
18591                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18592                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18593             }
18594             if(*p == wait) wait = NULLCHAR; // closing ]} found
18595             p++;
18596         }
18597         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18598         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18599         end[1] = NULLCHAR; // clip off comment beyond variation
18600         ToNrEvent(currentMove-1);
18601         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18602         // kludge: use ParsePV() to append variation to game
18603         move = currentMove;
18604         ParsePV(start, TRUE, TRUE);
18605         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18606         ClearPremoveHighlights();
18607         CommentPopDown();
18608         ToNrEvent(currentMove+1);
18609 }
18610
18611 void
18612 LoadTheme ()
18613 {
18614     char *p, *q, buf[MSG_SIZ];
18615     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18616         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18617         ParseArgsFromString(buf);
18618         ActivateTheme(TRUE); // also redo colors
18619         return;
18620     }
18621     p = nickName;
18622     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18623     {
18624         int len;
18625         q = appData.themeNames;
18626         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18627       if(appData.useBitmaps) {
18628         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18629                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18630                 appData.liteBackTextureMode,
18631                 appData.darkBackTextureMode );
18632       } else {
18633         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18634                 Col2Text(2),   // lightSquareColor
18635                 Col2Text(3) ); // darkSquareColor
18636       }
18637       if(appData.useBorder) {
18638         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18639                 appData.border);
18640       } else {
18641         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18642       }
18643       if(appData.useFont) {
18644         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18645                 appData.renderPiecesWithFont,
18646                 appData.fontToPieceTable,
18647                 Col2Text(9),    // appData.fontBackColorWhite
18648                 Col2Text(10) ); // appData.fontForeColorBlack
18649       } else {
18650         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18651                 appData.pieceDirectory);
18652         if(!appData.pieceDirectory[0])
18653           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18654                 Col2Text(0),   // whitePieceColor
18655                 Col2Text(1) ); // blackPieceColor
18656       }
18657       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18658                 Col2Text(4),   // highlightSquareColor
18659                 Col2Text(5) ); // premoveHighlightColor
18660         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18661         if(insert != q) insert[-1] = NULLCHAR;
18662         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18663         if(q)   free(q);
18664     }
18665     ActivateTheme(FALSE);
18666 }