Fix drops
[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) promoSweep = WhitePawn;
5403         else if(promoSweep == WhiteKing && step > 0) 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             legal[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) {
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 || sweepSelecting) {
7592             /* Second up/down in same square; just abort move */
7593             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7594             second = sweepSelecting = 0;
7595             fromX = fromY = -1;
7596             gatingPiece = EmptySquare;
7597             MarkTargetSquares(1);
7598             ClearHighlights();
7599             gotPremove = 0;
7600             ClearPremoveHighlights();
7601         } else {
7602             /* First upclick in same square; start click-click mode */
7603             SetHighlights(x, y, -1, -1);
7604         }
7605         return;
7606     }
7607
7608     clearFlag = 0;
7609 printf("B\n");
7610     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7611        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7612         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7613         DisplayMessage(_("only marked squares are legal"),"");
7614         DrawPosition(TRUE, NULL);
7615         return; // ignore to-click
7616     }
7617 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7618     /* we now have a different from- and (possibly off-board) to-square */
7619     /* Completed move */
7620     if(!sweepSelecting) {
7621         toX = x;
7622         toY = y;
7623     }
7624
7625     piece = boards[currentMove][fromY][fromX];
7626
7627     saveAnimate = appData.animate;
7628     if (clickType == Press) {
7629         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7630         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7631             // must be Edit Position mode with empty-square selected
7632             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7633             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7634             return;
7635         }
7636         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7637             return;
7638         }
7639         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7640             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7641         } else
7642         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7643         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7644           if(appData.sweepSelect) {
7645             promoSweep = defaultPromoChoice;
7646             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7647             selectFlag = 0; lastX = xPix; lastY = yPix;
7648             Sweep(0); // Pawn that is going to promote: preview promotion piece
7649             sweepSelecting = 1;
7650             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7651             MarkTargetSquares(1);
7652           }
7653           return; // promo popup appears on up-click
7654         }
7655         /* Finish clickclick move */
7656         if (appData.animate || appData.highlightLastMove) {
7657             SetHighlights(fromX, fromY, toX, toY);
7658         } else {
7659             ClearHighlights();
7660         }
7661     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7662         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7663         if (appData.animate || appData.highlightLastMove) {
7664             SetHighlights(fromX, fromY, toX, toY);
7665         } else {
7666             ClearHighlights();
7667         }
7668     } else {
7669 #if 0
7670 // [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
7671         /* Finish drag move */
7672         if (appData.highlightLastMove) {
7673             SetHighlights(fromX, fromY, toX, toY);
7674         } else {
7675             ClearHighlights();
7676         }
7677 #endif
7678         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7679         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7680           dragging *= 2;            // flag button-less dragging if we are dragging
7681           MarkTargetSquares(1);
7682           if(x == killX && y == killY) killX = killY = -1; else {
7683             killX = x; killY = y;     //remeber this square as intermediate
7684             ReportClick("put", x, y); // and inform engine
7685             ReportClick("lift", x, y);
7686             MarkTargetSquares(0);
7687             return;
7688           }
7689         }
7690         DragPieceEnd(xPix, yPix); dragging = 0;
7691         /* Don't animate move and drag both */
7692         appData.animate = FALSE;
7693     }
7694
7695     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7696     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7697         ChessSquare piece = boards[currentMove][fromY][fromX];
7698         if(gameMode == EditPosition && piece != EmptySquare &&
7699            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7700             int n;
7701
7702             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7703                 n = PieceToNumber(piece - (int)BlackPawn);
7704                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7705                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7706                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7707             } else
7708             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7709                 n = PieceToNumber(piece);
7710                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7711                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7712                 boards[currentMove][n][BOARD_WIDTH-2]++;
7713             }
7714             boards[currentMove][fromY][fromX] = EmptySquare;
7715         }
7716         ClearHighlights();
7717         fromX = fromY = -1;
7718         MarkTargetSquares(1);
7719         DrawPosition(TRUE, boards[currentMove]);
7720         return;
7721     }
7722
7723     // off-board moves should not be highlighted
7724     if(x < 0 || y < 0) ClearHighlights();
7725     else ReportClick("put", x, y);
7726
7727     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7728
7729     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7730         SetHighlights(fromX, fromY, toX, toY);
7731         MarkTargetSquares(1);
7732         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7733             // [HGM] super: promotion to captured piece selected from holdings
7734             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7735             promotionChoice = TRUE;
7736             // kludge follows to temporarily execute move on display, without promoting yet
7737             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7738             boards[currentMove][toY][toX] = p;
7739             DrawPosition(FALSE, boards[currentMove]);
7740             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7741             boards[currentMove][toY][toX] = q;
7742             DisplayMessage("Click in holdings to choose piece", "");
7743             return;
7744         }
7745         PromotionPopUp(promoChoice);
7746     } else {
7747         int oldMove = currentMove;
7748         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7749         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7750         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7751         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7752            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7753             DrawPosition(TRUE, boards[currentMove]);
7754         MarkTargetSquares(1);
7755         fromX = fromY = -1;
7756     }
7757     appData.animate = saveAnimate;
7758     if (appData.animate || appData.animateDragging) {
7759         /* Undo animation damage if needed */
7760         DrawPosition(FALSE, NULL);
7761     }
7762 }
7763
7764 int
7765 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7766 {   // front-end-free part taken out of PieceMenuPopup
7767     int whichMenu; int xSqr, ySqr;
7768
7769     if(seekGraphUp) { // [HGM] seekgraph
7770         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7771         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7772         return -2;
7773     }
7774
7775     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7776          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7777         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7778         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7779         if(action == Press)   {
7780             originalFlip = flipView;
7781             flipView = !flipView; // temporarily flip board to see game from partners perspective
7782             DrawPosition(TRUE, partnerBoard);
7783             DisplayMessage(partnerStatus, "");
7784             partnerUp = TRUE;
7785         } else if(action == Release) {
7786             flipView = originalFlip;
7787             DrawPosition(TRUE, boards[currentMove]);
7788             partnerUp = FALSE;
7789         }
7790         return -2;
7791     }
7792
7793     xSqr = EventToSquare(x, BOARD_WIDTH);
7794     ySqr = EventToSquare(y, BOARD_HEIGHT);
7795     if (action == Release) {
7796         if(pieceSweep != EmptySquare) {
7797             EditPositionMenuEvent(pieceSweep, toX, toY);
7798             pieceSweep = EmptySquare;
7799         } else UnLoadPV(); // [HGM] pv
7800     }
7801     if (action != Press) return -2; // return code to be ignored
7802     switch (gameMode) {
7803       case IcsExamining:
7804         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7805       case EditPosition:
7806         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7807         if (xSqr < 0 || ySqr < 0) return -1;
7808         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7809         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7810         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7811         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7812         NextPiece(0);
7813         return 2; // grab
7814       case IcsObserving:
7815         if(!appData.icsEngineAnalyze) return -1;
7816       case IcsPlayingWhite:
7817       case IcsPlayingBlack:
7818         if(!appData.zippyPlay) goto noZip;
7819       case AnalyzeMode:
7820       case AnalyzeFile:
7821       case MachinePlaysWhite:
7822       case MachinePlaysBlack:
7823       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7824         if (!appData.dropMenu) {
7825           LoadPV(x, y);
7826           return 2; // flag front-end to grab mouse events
7827         }
7828         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7829            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7830       case EditGame:
7831       noZip:
7832         if (xSqr < 0 || ySqr < 0) return -1;
7833         if (!appData.dropMenu || appData.testLegality &&
7834             gameInfo.variant != VariantBughouse &&
7835             gameInfo.variant != VariantCrazyhouse) return -1;
7836         whichMenu = 1; // drop menu
7837         break;
7838       default:
7839         return -1;
7840     }
7841
7842     if (((*fromX = xSqr) < 0) ||
7843         ((*fromY = ySqr) < 0)) {
7844         *fromX = *fromY = -1;
7845         return -1;
7846     }
7847     if (flipView)
7848       *fromX = BOARD_WIDTH - 1 - *fromX;
7849     else
7850       *fromY = BOARD_HEIGHT - 1 - *fromY;
7851
7852     return whichMenu;
7853 }
7854
7855 void
7856 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7857 {
7858 //    char * hint = lastHint;
7859     FrontEndProgramStats stats;
7860
7861     stats.which = cps == &first ? 0 : 1;
7862     stats.depth = cpstats->depth;
7863     stats.nodes = cpstats->nodes;
7864     stats.score = cpstats->score;
7865     stats.time = cpstats->time;
7866     stats.pv = cpstats->movelist;
7867     stats.hint = lastHint;
7868     stats.an_move_index = 0;
7869     stats.an_move_count = 0;
7870
7871     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7872         stats.hint = cpstats->move_name;
7873         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7874         stats.an_move_count = cpstats->nr_moves;
7875     }
7876
7877     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
7878
7879     SetProgramStats( &stats );
7880 }
7881
7882 void
7883 ClearEngineOutputPane (int which)
7884 {
7885     static FrontEndProgramStats dummyStats;
7886     dummyStats.which = which;
7887     dummyStats.pv = "#";
7888     SetProgramStats( &dummyStats );
7889 }
7890
7891 #define MAXPLAYERS 500
7892
7893 char *
7894 TourneyStandings (int display)
7895 {
7896     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7897     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7898     char result, *p, *names[MAXPLAYERS];
7899
7900     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7901         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7902     names[0] = p = strdup(appData.participants);
7903     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7904
7905     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7906
7907     while(result = appData.results[nr]) {
7908         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7909         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7910         wScore = bScore = 0;
7911         switch(result) {
7912           case '+': wScore = 2; break;
7913           case '-': bScore = 2; break;
7914           case '=': wScore = bScore = 1; break;
7915           case ' ':
7916           case '*': return strdup("busy"); // tourney not finished
7917         }
7918         score[w] += wScore;
7919         score[b] += bScore;
7920         games[w]++;
7921         games[b]++;
7922         nr++;
7923     }
7924     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7925     for(w=0; w<nPlayers; w++) {
7926         bScore = -1;
7927         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7928         ranking[w] = b; points[w] = bScore; score[b] = -2;
7929     }
7930     p = malloc(nPlayers*34+1);
7931     for(w=0; w<nPlayers && w<display; w++)
7932         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7933     free(names[0]);
7934     return p;
7935 }
7936
7937 void
7938 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7939 {       // count all piece types
7940         int p, f, r;
7941         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7942         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7943         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7944                 p = board[r][f];
7945                 pCnt[p]++;
7946                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7947                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7948                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7949                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7950                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7951                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7952         }
7953 }
7954
7955 int
7956 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7957 {
7958         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7959         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7960
7961         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7962         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7963         if(myPawns == 2 && nMine == 3) // KPP
7964             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7965         if(myPawns == 1 && nMine == 2) // KP
7966             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7967         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7968             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7969         if(myPawns) return FALSE;
7970         if(pCnt[WhiteRook+side])
7971             return pCnt[BlackRook-side] ||
7972                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7973                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7974                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7975         if(pCnt[WhiteCannon+side]) {
7976             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7977             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7978         }
7979         if(pCnt[WhiteKnight+side])
7980             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7981         return FALSE;
7982 }
7983
7984 int
7985 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7986 {
7987         VariantClass v = gameInfo.variant;
7988
7989         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7990         if(v == VariantShatranj) return TRUE; // always winnable through baring
7991         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7992         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7993
7994         if(v == VariantXiangqi) {
7995                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7996
7997                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7998                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7999                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8000                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8001                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8002                 if(stale) // we have at least one last-rank P plus perhaps C
8003                     return majors // KPKX
8004                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8005                 else // KCA*E*
8006                     return pCnt[WhiteFerz+side] // KCAK
8007                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8008                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8009                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8010
8011         } else if(v == VariantKnightmate) {
8012                 if(nMine == 1) return FALSE;
8013                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8014         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8015                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8016
8017                 if(nMine == 1) return FALSE; // bare King
8018                 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
8019                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8020                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8021                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8022                 if(pCnt[WhiteKnight+side])
8023                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8024                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8025                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8026                 if(nBishops)
8027                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8028                 if(pCnt[WhiteAlfil+side])
8029                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8030                 if(pCnt[WhiteWazir+side])
8031                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8032         }
8033
8034         return TRUE;
8035 }
8036
8037 int
8038 CompareWithRights (Board b1, Board b2)
8039 {
8040     int rights = 0;
8041     if(!CompareBoards(b1, b2)) return FALSE;
8042     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8043     /* compare castling rights */
8044     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8045            rights++; /* King lost rights, while rook still had them */
8046     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8047         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8048            rights++; /* but at least one rook lost them */
8049     }
8050     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8051            rights++;
8052     if( b1[CASTLING][5] != NoRights ) {
8053         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8054            rights++;
8055     }
8056     return rights == 0;
8057 }
8058
8059 int
8060 Adjudicate (ChessProgramState *cps)
8061 {       // [HGM] some adjudications useful with buggy engines
8062         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8063         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8064         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8065         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8066         int k, drop, count = 0; static int bare = 1;
8067         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8068         Boolean canAdjudicate = !appData.icsActive;
8069
8070         // most tests only when we understand the game, i.e. legality-checking on
8071             if( appData.testLegality )
8072             {   /* [HGM] Some more adjudications for obstinate engines */
8073                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8074                 static int moveCount = 6;
8075                 ChessMove result;
8076                 char *reason = NULL;
8077
8078                 /* Count what is on board. */
8079                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8080
8081                 /* Some material-based adjudications that have to be made before stalemate test */
8082                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8083                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8084                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8085                      if(canAdjudicate && appData.checkMates) {
8086                          if(engineOpponent)
8087                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8088                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8089                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8090                          return 1;
8091                      }
8092                 }
8093
8094                 /* Bare King in Shatranj (loses) or Losers (wins) */
8095                 if( nrW == 1 || nrB == 1) {
8096                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8097                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8098                      if(canAdjudicate && appData.checkMates) {
8099                          if(engineOpponent)
8100                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8101                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8102                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8103                          return 1;
8104                      }
8105                   } else
8106                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8107                   {    /* bare King */
8108                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8109                         if(canAdjudicate && appData.checkMates) {
8110                             /* but only adjudicate if adjudication enabled */
8111                             if(engineOpponent)
8112                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8113                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8114                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8115                             return 1;
8116                         }
8117                   }
8118                 } else bare = 1;
8119
8120
8121             // don't wait for engine to announce game end if we can judge ourselves
8122             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8123               case MT_CHECK:
8124                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8125                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8126                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8127                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8128                             checkCnt++;
8129                         if(checkCnt >= 2) {
8130                             reason = "Xboard adjudication: 3rd check";
8131                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8132                             break;
8133                         }
8134                     }
8135                 }
8136               case MT_NONE:
8137               default:
8138                 break;
8139               case MT_STEALMATE:
8140               case MT_STALEMATE:
8141               case MT_STAINMATE:
8142                 reason = "Xboard adjudication: Stalemate";
8143                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8144                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8145                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8146                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8147                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8148                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8149                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8150                                                                         EP_CHECKMATE : EP_WINS);
8151                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8152                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8153                 }
8154                 break;
8155               case MT_CHECKMATE:
8156                 reason = "Xboard adjudication: Checkmate";
8157                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8158                 if(gameInfo.variant == VariantShogi) {
8159                     if(forwardMostMove > backwardMostMove
8160                        && moveList[forwardMostMove-1][1] == '@'
8161                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8162                         reason = "XBoard adjudication: pawn-drop mate";
8163                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8164                     }
8165                 }
8166                 break;
8167             }
8168
8169                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8170                     case EP_STALEMATE:
8171                         result = GameIsDrawn; break;
8172                     case EP_CHECKMATE:
8173                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8174                     case EP_WINS:
8175                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8176                     default:
8177                         result = EndOfFile;
8178                 }
8179                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8180                     if(engineOpponent)
8181                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8182                     GameEnds( result, reason, GE_XBOARD );
8183                     return 1;
8184                 }
8185
8186                 /* Next absolutely insufficient mating material. */
8187                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8188                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8189                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8190
8191                      /* always flag draws, for judging claims */
8192                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8193
8194                      if(canAdjudicate && appData.materialDraws) {
8195                          /* but only adjudicate them if adjudication enabled */
8196                          if(engineOpponent) {
8197                            SendToProgram("force\n", engineOpponent); // suppress reply
8198                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8199                          }
8200                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8201                          return 1;
8202                      }
8203                 }
8204
8205                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8206                 if(gameInfo.variant == VariantXiangqi ?
8207                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8208                  : nrW + nrB == 4 &&
8209                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8210                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8211                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8212                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8213                    ) ) {
8214                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8215                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8216                           if(engineOpponent) {
8217                             SendToProgram("force\n", engineOpponent); // suppress reply
8218                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8219                           }
8220                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8221                           return 1;
8222                      }
8223                 } else moveCount = 6;
8224             }
8225
8226         // Repetition draws and 50-move rule can be applied independently of legality testing
8227
8228                 /* Check for rep-draws */
8229                 count = 0;
8230                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8231                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8232                 for(k = forwardMostMove-2;
8233                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8234                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8235                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8236                     k-=2)
8237                 {   int rights=0;
8238                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8239                         /* compare castling rights */
8240                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8241                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8242                                 rights++; /* King lost rights, while rook still had them */
8243                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8244                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8245                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8246                                    rights++; /* but at least one rook lost them */
8247                         }
8248                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8249                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8250                                 rights++;
8251                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8252                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8253                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8254                                    rights++;
8255                         }
8256                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8257                             && appData.drawRepeats > 1) {
8258                              /* adjudicate after user-specified nr of repeats */
8259                              int result = GameIsDrawn;
8260                              char *details = "XBoard adjudication: repetition draw";
8261                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8262                                 // [HGM] xiangqi: check for forbidden perpetuals
8263                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8264                                 for(m=forwardMostMove; m>k; m-=2) {
8265                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8266                                         ourPerpetual = 0; // the current mover did not always check
8267                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8268                                         hisPerpetual = 0; // the opponent did not always check
8269                                 }
8270                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8271                                                                         ourPerpetual, hisPerpetual);
8272                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8273                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8274                                     details = "Xboard adjudication: perpetual checking";
8275                                 } else
8276                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8277                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8278                                 } else
8279                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8280                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8281                                         result = BlackWins;
8282                                         details = "Xboard adjudication: repetition";
8283                                     }
8284                                 } else // it must be XQ
8285                                 // Now check for perpetual chases
8286                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8287                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8288                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8289                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8290                                         static char resdet[MSG_SIZ];
8291                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8292                                         details = resdet;
8293                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8294                                     } else
8295                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8296                                         break; // Abort repetition-checking loop.
8297                                 }
8298                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8299                              }
8300                              if(engineOpponent) {
8301                                SendToProgram("force\n", engineOpponent); // suppress reply
8302                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8303                              }
8304                              GameEnds( result, details, GE_XBOARD );
8305                              return 1;
8306                         }
8307                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8308                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8309                     }
8310                 }
8311
8312                 /* Now we test for 50-move draws. Determine ply count */
8313                 count = forwardMostMove;
8314                 /* look for last irreversble move */
8315                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8316                     count--;
8317                 /* if we hit starting position, add initial plies */
8318                 if( count == backwardMostMove )
8319                     count -= initialRulePlies;
8320                 count = forwardMostMove - count;
8321                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8322                         // adjust reversible move counter for checks in Xiangqi
8323                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8324                         if(i < backwardMostMove) i = backwardMostMove;
8325                         while(i <= forwardMostMove) {
8326                                 lastCheck = inCheck; // check evasion does not count
8327                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8328                                 if(inCheck || lastCheck) count--; // check does not count
8329                                 i++;
8330                         }
8331                 }
8332                 if( count >= 100)
8333                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8334                          /* this is used to judge if draw claims are legal */
8335                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8336                          if(engineOpponent) {
8337                            SendToProgram("force\n", engineOpponent); // suppress reply
8338                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8339                          }
8340                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8341                          return 1;
8342                 }
8343
8344                 /* if draw offer is pending, treat it as a draw claim
8345                  * when draw condition present, to allow engines a way to
8346                  * claim draws before making their move to avoid a race
8347                  * condition occurring after their move
8348                  */
8349                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8350                          char *p = NULL;
8351                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8352                              p = "Draw claim: 50-move rule";
8353                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8354                              p = "Draw claim: 3-fold repetition";
8355                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8356                              p = "Draw claim: insufficient mating material";
8357                          if( p != NULL && canAdjudicate) {
8358                              if(engineOpponent) {
8359                                SendToProgram("force\n", engineOpponent); // suppress reply
8360                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8361                              }
8362                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8363                              return 1;
8364                          }
8365                 }
8366
8367                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8368                     if(engineOpponent) {
8369                       SendToProgram("force\n", engineOpponent); // suppress reply
8370                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8371                     }
8372                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8373                     return 1;
8374                 }
8375         return 0;
8376 }
8377
8378 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8379 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8380 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8381
8382 static int
8383 BitbaseProbe ()
8384 {
8385     int pieces[10], squares[10], cnt=0, r, f, res;
8386     static int loaded;
8387     static PPROBE_EGBB probeBB;
8388     if(!appData.testLegality) return 10;
8389     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8390     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8391     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8392     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8393         ChessSquare piece = boards[forwardMostMove][r][f];
8394         int black = (piece >= BlackPawn);
8395         int type = piece - black*BlackPawn;
8396         if(piece == EmptySquare) continue;
8397         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8398         if(type == WhiteKing) type = WhiteQueen + 1;
8399         type = egbbCode[type];
8400         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8401         pieces[cnt] = type + black*6;
8402         if(++cnt > 5) return 11;
8403     }
8404     pieces[cnt] = squares[cnt] = 0;
8405     // probe EGBB
8406     if(loaded == 2) return 13; // loading failed before
8407     if(loaded == 0) {
8408         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8409         HMODULE lib;
8410         PLOAD_EGBB loadBB;
8411         loaded = 2; // prepare for failure
8412         if(!path) return 13; // no egbb installed
8413         strncpy(buf, path + 8, MSG_SIZ);
8414         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8415         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8416         lib = LoadLibrary(buf);
8417         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8418         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8419         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8420         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8421         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8422         loaded = 1; // success!
8423     }
8424     res = probeBB(forwardMostMove & 1, pieces, squares);
8425     return res > 0 ? 1 : res < 0 ? -1 : 0;
8426 }
8427
8428 char *
8429 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8430 {   // [HGM] book: this routine intercepts moves to simulate book replies
8431     char *bookHit = NULL;
8432
8433     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8434         char buf[MSG_SIZ];
8435         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8436         SendToProgram(buf, cps);
8437     }
8438     //first determine if the incoming move brings opponent into his book
8439     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8440         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8441     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8442     if(bookHit != NULL && !cps->bookSuspend) {
8443         // make sure opponent is not going to reply after receiving move to book position
8444         SendToProgram("force\n", cps);
8445         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8446     }
8447     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8448     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8449     // now arrange restart after book miss
8450     if(bookHit) {
8451         // after a book hit we never send 'go', and the code after the call to this routine
8452         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8453         char buf[MSG_SIZ], *move = bookHit;
8454         if(cps->useSAN) {
8455             int fromX, fromY, toX, toY;
8456             char promoChar;
8457             ChessMove moveType;
8458             move = buf + 30;
8459             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8460                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8461                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8462                                     PosFlags(forwardMostMove),
8463                                     fromY, fromX, toY, toX, promoChar, move);
8464             } else {
8465                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8466                 bookHit = NULL;
8467             }
8468         }
8469         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8470         SendToProgram(buf, cps);
8471         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8472     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8473         SendToProgram("go\n", cps);
8474         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8475     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8476         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8477             SendToProgram("go\n", cps);
8478         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8479     }
8480     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8481 }
8482
8483 int
8484 LoadError (char *errmess, ChessProgramState *cps)
8485 {   // unloads engine and switches back to -ncp mode if it was first
8486     if(cps->initDone) return FALSE;
8487     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8488     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8489     cps->pr = NoProc;
8490     if(cps == &first) {
8491         appData.noChessProgram = TRUE;
8492         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8493         gameMode = BeginningOfGame; ModeHighlight();
8494         SetNCPMode();
8495     }
8496     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8497     DisplayMessage("", ""); // erase waiting message
8498     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8499     return TRUE;
8500 }
8501
8502 char *savedMessage;
8503 ChessProgramState *savedState;
8504 void
8505 DeferredBookMove (void)
8506 {
8507         if(savedState->lastPing != savedState->lastPong)
8508                     ScheduleDelayedEvent(DeferredBookMove, 10);
8509         else
8510         HandleMachineMove(savedMessage, savedState);
8511 }
8512
8513 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8514 static ChessProgramState *stalledEngine;
8515 static char stashedInputMove[MSG_SIZ];
8516
8517 void
8518 HandleMachineMove (char *message, ChessProgramState *cps)
8519 {
8520     static char firstLeg[20];
8521     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8522     char realname[MSG_SIZ];
8523     int fromX, fromY, toX, toY;
8524     ChessMove moveType;
8525     char promoChar, roar;
8526     char *p, *pv=buf1;
8527     int machineWhite, oldError;
8528     char *bookHit;
8529
8530     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8531         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8532         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8533             DisplayError(_("Invalid pairing from pairing engine"), 0);
8534             return;
8535         }
8536         pairingReceived = 1;
8537         NextMatchGame();
8538         return; // Skim the pairing messages here.
8539     }
8540
8541     oldError = cps->userError; cps->userError = 0;
8542
8543 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8544     /*
8545      * Kludge to ignore BEL characters
8546      */
8547     while (*message == '\007') message++;
8548
8549     /*
8550      * [HGM] engine debug message: ignore lines starting with '#' character
8551      */
8552     if(cps->debug && *message == '#') return;
8553
8554     /*
8555      * Look for book output
8556      */
8557     if (cps == &first && bookRequested) {
8558         if (message[0] == '\t' || message[0] == ' ') {
8559             /* Part of the book output is here; append it */
8560             strcat(bookOutput, message);
8561             strcat(bookOutput, "  \n");
8562             return;
8563         } else if (bookOutput[0] != NULLCHAR) {
8564             /* All of book output has arrived; display it */
8565             char *p = bookOutput;
8566             while (*p != NULLCHAR) {
8567                 if (*p == '\t') *p = ' ';
8568                 p++;
8569             }
8570             DisplayInformation(bookOutput);
8571             bookRequested = FALSE;
8572             /* Fall through to parse the current output */
8573         }
8574     }
8575
8576     /*
8577      * Look for machine move.
8578      */
8579     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8580         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8581     {
8582         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8583             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8584             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8585             stalledEngine = cps;
8586             if(appData.ponderNextMove) { // bring opponent out of ponder
8587                 if(gameMode == TwoMachinesPlay) {
8588                     if(cps->other->pause)
8589                         PauseEngine(cps->other);
8590                     else
8591                         SendToProgram("easy\n", cps->other);
8592                 }
8593             }
8594             StopClocks();
8595             return;
8596         }
8597
8598         /* This method is only useful on engines that support ping */
8599         if (cps->lastPing != cps->lastPong) {
8600           if (gameMode == BeginningOfGame) {
8601             /* Extra move from before last new; ignore */
8602             if (appData.debugMode) {
8603                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8604             }
8605           } else {
8606             if (appData.debugMode) {
8607                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8608                         cps->which, gameMode);
8609             }
8610
8611             SendToProgram("undo\n", cps);
8612           }
8613           return;
8614         }
8615
8616         switch (gameMode) {
8617           case BeginningOfGame:
8618             /* Extra move from before last reset; ignore */
8619             if (appData.debugMode) {
8620                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8621             }
8622             return;
8623
8624           case EndOfGame:
8625           case IcsIdle:
8626           default:
8627             /* Extra move after we tried to stop.  The mode test is
8628                not a reliable way of detecting this problem, but it's
8629                the best we can do on engines that don't support ping.
8630             */
8631             if (appData.debugMode) {
8632                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8633                         cps->which, gameMode);
8634             }
8635             SendToProgram("undo\n", cps);
8636             return;
8637
8638           case MachinePlaysWhite:
8639           case IcsPlayingWhite:
8640             machineWhite = TRUE;
8641             break;
8642
8643           case MachinePlaysBlack:
8644           case IcsPlayingBlack:
8645             machineWhite = FALSE;
8646             break;
8647
8648           case TwoMachinesPlay:
8649             machineWhite = (cps->twoMachinesColor[0] == 'w');
8650             break;
8651         }
8652         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8653             if (appData.debugMode) {
8654                 fprintf(debugFP,
8655                         "Ignoring move out of turn by %s, gameMode %d"
8656                         ", forwardMost %d\n",
8657                         cps->which, gameMode, forwardMostMove);
8658             }
8659             return;
8660         }
8661
8662         if(cps->alphaRank) AlphaRank(machineMove, 4);
8663
8664         // [HGM] lion: (some very limited) support for Alien protocol
8665         killX = killY = -1;
8666         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8667             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8668             return;
8669         } else if(firstLeg[0]) { // there was a previous leg;
8670             // only support case where same piece makes two step (and don't even test that!)
8671             char buf[20], *p = machineMove+1, *q = buf+1, f;
8672             safeStrCpy(buf, machineMove, 20);
8673             while(isdigit(*q)) q++; // find start of to-square
8674             safeStrCpy(machineMove, firstLeg, 20);
8675             while(isdigit(*p)) p++;
8676             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8677             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8678             firstLeg[0] = NULLCHAR;
8679         }
8680
8681         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8682                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8683             /* Machine move could not be parsed; ignore it. */
8684           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8685                     machineMove, _(cps->which));
8686             DisplayMoveError(buf1);
8687             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8688                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8689             if (gameMode == TwoMachinesPlay) {
8690               GameEnds(machineWhite ? BlackWins : WhiteWins,
8691                        buf1, GE_XBOARD);
8692             }
8693             return;
8694         }
8695
8696         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8697         /* So we have to redo legality test with true e.p. status here,  */
8698         /* to make sure an illegal e.p. capture does not slip through,   */
8699         /* to cause a forfeit on a justified illegal-move complaint      */
8700         /* of the opponent.                                              */
8701         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8702            ChessMove moveType;
8703            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8704                              fromY, fromX, toY, toX, promoChar);
8705             if(moveType == IllegalMove) {
8706               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8707                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8708                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8709                            buf1, GE_XBOARD);
8710                 return;
8711            } else if(!appData.fischerCastling)
8712            /* [HGM] Kludge to handle engines that send FRC-style castling
8713               when they shouldn't (like TSCP-Gothic) */
8714            switch(moveType) {
8715              case WhiteASideCastleFR:
8716              case BlackASideCastleFR:
8717                toX+=2;
8718                currentMoveString[2]++;
8719                break;
8720              case WhiteHSideCastleFR:
8721              case BlackHSideCastleFR:
8722                toX--;
8723                currentMoveString[2]--;
8724                break;
8725              default: ; // nothing to do, but suppresses warning of pedantic compilers
8726            }
8727         }
8728         hintRequested = FALSE;
8729         lastHint[0] = NULLCHAR;
8730         bookRequested = FALSE;
8731         /* Program may be pondering now */
8732         cps->maybeThinking = TRUE;
8733         if (cps->sendTime == 2) cps->sendTime = 1;
8734         if (cps->offeredDraw) cps->offeredDraw--;
8735
8736         /* [AS] Save move info*/
8737         pvInfoList[ forwardMostMove ].score = programStats.score;
8738         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8739         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8740
8741         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8742
8743         /* Test suites abort the 'game' after one move */
8744         if(*appData.finger) {
8745            static FILE *f;
8746            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8747            if(!f) f = fopen(appData.finger, "w");
8748            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8749            else { DisplayFatalError("Bad output file", errno, 0); return; }
8750            free(fen);
8751            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8752         }
8753
8754         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8755         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8756             int count = 0;
8757
8758             while( count < adjudicateLossPlies ) {
8759                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8760
8761                 if( count & 1 ) {
8762                     score = -score; /* Flip score for winning side */
8763                 }
8764
8765                 if( score > appData.adjudicateLossThreshold ) {
8766                     break;
8767                 }
8768
8769                 count++;
8770             }
8771
8772             if( count >= adjudicateLossPlies ) {
8773                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8774
8775                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8776                     "Xboard adjudication",
8777                     GE_XBOARD );
8778
8779                 return;
8780             }
8781         }
8782
8783         if(Adjudicate(cps)) {
8784             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8785             return; // [HGM] adjudicate: for all automatic game ends
8786         }
8787
8788 #if ZIPPY
8789         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8790             first.initDone) {
8791           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8792                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8793                 SendToICS("draw ");
8794                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8795           }
8796           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8797           ics_user_moved = 1;
8798           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8799                 char buf[3*MSG_SIZ];
8800
8801                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8802                         programStats.score / 100.,
8803                         programStats.depth,
8804                         programStats.time / 100.,
8805                         (unsigned int)programStats.nodes,
8806                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8807                         programStats.movelist);
8808                 SendToICS(buf);
8809           }
8810         }
8811 #endif
8812
8813         /* [AS] Clear stats for next move */
8814         ClearProgramStats();
8815         thinkOutput[0] = NULLCHAR;
8816         hiddenThinkOutputState = 0;
8817
8818         bookHit = NULL;
8819         if (gameMode == TwoMachinesPlay) {
8820             /* [HGM] relaying draw offers moved to after reception of move */
8821             /* and interpreting offer as claim if it brings draw condition */
8822             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8823                 SendToProgram("draw\n", cps->other);
8824             }
8825             if (cps->other->sendTime) {
8826                 SendTimeRemaining(cps->other,
8827                                   cps->other->twoMachinesColor[0] == 'w');
8828             }
8829             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8830             if (firstMove && !bookHit) {
8831                 firstMove = FALSE;
8832                 if (cps->other->useColors) {
8833                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8834                 }
8835                 SendToProgram("go\n", cps->other);
8836             }
8837             cps->other->maybeThinking = TRUE;
8838         }
8839
8840         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8841
8842         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8843
8844         if (!pausing && appData.ringBellAfterMoves) {
8845             if(!roar) RingBell();
8846         }
8847
8848         /*
8849          * Reenable menu items that were disabled while
8850          * machine was thinking
8851          */
8852         if (gameMode != TwoMachinesPlay)
8853             SetUserThinkingEnables();
8854
8855         // [HGM] book: after book hit opponent has received move and is now in force mode
8856         // force the book reply into it, and then fake that it outputted this move by jumping
8857         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8858         if(bookHit) {
8859                 static char bookMove[MSG_SIZ]; // a bit generous?
8860
8861                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8862                 strcat(bookMove, bookHit);
8863                 message = bookMove;
8864                 cps = cps->other;
8865                 programStats.nodes = programStats.depth = programStats.time =
8866                 programStats.score = programStats.got_only_move = 0;
8867                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8868
8869                 if(cps->lastPing != cps->lastPong) {
8870                     savedMessage = message; // args for deferred call
8871                     savedState = cps;
8872                     ScheduleDelayedEvent(DeferredBookMove, 10);
8873                     return;
8874                 }
8875                 goto FakeBookMove;
8876         }
8877
8878         return;
8879     }
8880
8881     /* Set special modes for chess engines.  Later something general
8882      *  could be added here; for now there is just one kludge feature,
8883      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8884      *  when "xboard" is given as an interactive command.
8885      */
8886     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8887         cps->useSigint = FALSE;
8888         cps->useSigterm = FALSE;
8889     }
8890     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8891       ParseFeatures(message+8, cps);
8892       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8893     }
8894
8895     if (!strncmp(message, "setup ", 6) && 
8896         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8897           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8898                                         ) { // [HGM] allow first engine to define opening position
8899       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8900       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8901       *buf = NULLCHAR;
8902       if(sscanf(message, "setup (%s", buf) == 1) {
8903         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8904         ASSIGN(appData.pieceToCharTable, buf);
8905       }
8906       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8907       if(dummy >= 3) {
8908         while(message[s] && message[s++] != ' ');
8909         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8910            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8911             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8912             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8913           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8914           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8915           startedFromSetupPosition = FALSE;
8916         }
8917       }
8918       if(startedFromSetupPosition) return;
8919       ParseFEN(boards[0], &dummy, message+s, FALSE);
8920       DrawPosition(TRUE, boards[0]);
8921       CopyBoard(initialPosition, boards[0]);
8922       startedFromSetupPosition = TRUE;
8923       return;
8924     }
8925     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8926       ChessSquare piece = WhitePawn;
8927       char *p=buf2;
8928       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8929       piece += CharToPiece(*p) - WhitePawn;
8930       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8931       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8932       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8933       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8934       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8935       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8936                                                && gameInfo.variant != VariantGreat
8937                                                && gameInfo.variant != VariantFairy    ) return;
8938       if(piece < EmptySquare) {
8939         pieceDefs = TRUE;
8940         ASSIGN(pieceDesc[piece], buf1);
8941         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8942       }
8943       return;
8944     }
8945     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8946      * want this, I was asked to put it in, and obliged.
8947      */
8948     if (!strncmp(message, "setboard ", 9)) {
8949         Board initial_position;
8950
8951         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8952
8953         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8954             DisplayError(_("Bad FEN received from engine"), 0);
8955             return ;
8956         } else {
8957            Reset(TRUE, FALSE);
8958            CopyBoard(boards[0], initial_position);
8959            initialRulePlies = FENrulePlies;
8960            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8961            else gameMode = MachinePlaysBlack;
8962            DrawPosition(FALSE, boards[currentMove]);
8963         }
8964         return;
8965     }
8966
8967     /*
8968      * Look for communication commands
8969      */
8970     if (!strncmp(message, "telluser ", 9)) {
8971         if(message[9] == '\\' && message[10] == '\\')
8972             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8973         PlayTellSound();
8974         DisplayNote(message + 9);
8975         return;
8976     }
8977     if (!strncmp(message, "tellusererror ", 14)) {
8978         cps->userError = 1;
8979         if(message[14] == '\\' && message[15] == '\\')
8980             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8981         PlayTellSound();
8982         DisplayError(message + 14, 0);
8983         return;
8984     }
8985     if (!strncmp(message, "tellopponent ", 13)) {
8986       if (appData.icsActive) {
8987         if (loggedOn) {
8988           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8989           SendToICS(buf1);
8990         }
8991       } else {
8992         DisplayNote(message + 13);
8993       }
8994       return;
8995     }
8996     if (!strncmp(message, "tellothers ", 11)) {
8997       if (appData.icsActive) {
8998         if (loggedOn) {
8999           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9000           SendToICS(buf1);
9001         }
9002       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9003       return;
9004     }
9005     if (!strncmp(message, "tellall ", 8)) {
9006       if (appData.icsActive) {
9007         if (loggedOn) {
9008           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9009           SendToICS(buf1);
9010         }
9011       } else {
9012         DisplayNote(message + 8);
9013       }
9014       return;
9015     }
9016     if (strncmp(message, "warning", 7) == 0) {
9017         /* Undocumented feature, use tellusererror in new code */
9018         DisplayError(message, 0);
9019         return;
9020     }
9021     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9022         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9023         strcat(realname, " query");
9024         AskQuestion(realname, buf2, buf1, cps->pr);
9025         return;
9026     }
9027     /* Commands from the engine directly to ICS.  We don't allow these to be
9028      *  sent until we are logged on. Crafty kibitzes have been known to
9029      *  interfere with the login process.
9030      */
9031     if (loggedOn) {
9032         if (!strncmp(message, "tellics ", 8)) {
9033             SendToICS(message + 8);
9034             SendToICS("\n");
9035             return;
9036         }
9037         if (!strncmp(message, "tellicsnoalias ", 15)) {
9038             SendToICS(ics_prefix);
9039             SendToICS(message + 15);
9040             SendToICS("\n");
9041             return;
9042         }
9043         /* The following are for backward compatibility only */
9044         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9045             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9046             SendToICS(ics_prefix);
9047             SendToICS(message);
9048             SendToICS("\n");
9049             return;
9050         }
9051     }
9052     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9053         if(initPing == cps->lastPong) {
9054             if(gameInfo.variant == VariantUnknown) {
9055                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9056                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9057                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9058             }
9059             initPing = -1;
9060         }
9061         return;
9062     }
9063     if(!strncmp(message, "highlight ", 10)) {
9064         if(appData.testLegality && appData.markers) return;
9065         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9066         return;
9067     }
9068     if(!strncmp(message, "click ", 6)) {
9069         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9070         if(appData.testLegality || !appData.oneClick) return;
9071         sscanf(message+6, "%c%d%c", &f, &y, &c);
9072         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9073         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9074         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9075         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9076         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9077         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9078             LeftClick(Release, lastLeftX, lastLeftY);
9079         controlKey  = (c == ',');
9080         LeftClick(Press, x, y);
9081         LeftClick(Release, x, y);
9082         first.highlight = f;
9083         return;
9084     }
9085     /*
9086      * If the move is illegal, cancel it and redraw the board.
9087      * Also deal with other error cases.  Matching is rather loose
9088      * here to accommodate engines written before the spec.
9089      */
9090     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9091         strncmp(message, "Error", 5) == 0) {
9092         if (StrStr(message, "name") ||
9093             StrStr(message, "rating") || StrStr(message, "?") ||
9094             StrStr(message, "result") || StrStr(message, "board") ||
9095             StrStr(message, "bk") || StrStr(message, "computer") ||
9096             StrStr(message, "variant") || StrStr(message, "hint") ||
9097             StrStr(message, "random") || StrStr(message, "depth") ||
9098             StrStr(message, "accepted")) {
9099             return;
9100         }
9101         if (StrStr(message, "protover")) {
9102           /* Program is responding to input, so it's apparently done
9103              initializing, and this error message indicates it is
9104              protocol version 1.  So we don't need to wait any longer
9105              for it to initialize and send feature commands. */
9106           FeatureDone(cps, 1);
9107           cps->protocolVersion = 1;
9108           return;
9109         }
9110         cps->maybeThinking = FALSE;
9111
9112         if (StrStr(message, "draw")) {
9113             /* Program doesn't have "draw" command */
9114             cps->sendDrawOffers = 0;
9115             return;
9116         }
9117         if (cps->sendTime != 1 &&
9118             (StrStr(message, "time") || StrStr(message, "otim"))) {
9119           /* Program apparently doesn't have "time" or "otim" command */
9120           cps->sendTime = 0;
9121           return;
9122         }
9123         if (StrStr(message, "analyze")) {
9124             cps->analysisSupport = FALSE;
9125             cps->analyzing = FALSE;
9126 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9127             EditGameEvent(); // [HGM] try to preserve loaded game
9128             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9129             DisplayError(buf2, 0);
9130             return;
9131         }
9132         if (StrStr(message, "(no matching move)st")) {
9133           /* Special kludge for GNU Chess 4 only */
9134           cps->stKludge = TRUE;
9135           SendTimeControl(cps, movesPerSession, timeControl,
9136                           timeIncrement, appData.searchDepth,
9137                           searchTime);
9138           return;
9139         }
9140         if (StrStr(message, "(no matching move)sd")) {
9141           /* Special kludge for GNU Chess 4 only */
9142           cps->sdKludge = TRUE;
9143           SendTimeControl(cps, movesPerSession, timeControl,
9144                           timeIncrement, appData.searchDepth,
9145                           searchTime);
9146           return;
9147         }
9148         if (!StrStr(message, "llegal")) {
9149             return;
9150         }
9151         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9152             gameMode == IcsIdle) return;
9153         if (forwardMostMove <= backwardMostMove) return;
9154         if (pausing) PauseEvent();
9155       if(appData.forceIllegal) {
9156             // [HGM] illegal: machine refused move; force position after move into it
9157           SendToProgram("force\n", cps);
9158           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9159                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9160                 // when black is to move, while there might be nothing on a2 or black
9161                 // might already have the move. So send the board as if white has the move.
9162                 // But first we must change the stm of the engine, as it refused the last move
9163                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9164                 if(WhiteOnMove(forwardMostMove)) {
9165                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9166                     SendBoard(cps, forwardMostMove); // kludgeless board
9167                 } else {
9168                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9169                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9170                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9171                 }
9172           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9173             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9174                  gameMode == TwoMachinesPlay)
9175               SendToProgram("go\n", cps);
9176             return;
9177       } else
9178         if (gameMode == PlayFromGameFile) {
9179             /* Stop reading this game file */
9180             gameMode = EditGame;
9181             ModeHighlight();
9182         }
9183         /* [HGM] illegal-move claim should forfeit game when Xboard */
9184         /* only passes fully legal moves                            */
9185         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9186             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9187                                 "False illegal-move claim", GE_XBOARD );
9188             return; // do not take back move we tested as valid
9189         }
9190         currentMove = forwardMostMove-1;
9191         DisplayMove(currentMove-1); /* before DisplayMoveError */
9192         SwitchClocks(forwardMostMove-1); // [HGM] race
9193         DisplayBothClocks();
9194         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9195                 parseList[currentMove], _(cps->which));
9196         DisplayMoveError(buf1);
9197         DrawPosition(FALSE, boards[currentMove]);
9198
9199         SetUserThinkingEnables();
9200         return;
9201     }
9202     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9203         /* Program has a broken "time" command that
9204            outputs a string not ending in newline.
9205            Don't use it. */
9206         cps->sendTime = 0;
9207     }
9208     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9209         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9210             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9211     }
9212
9213     /*
9214      * If chess program startup fails, exit with an error message.
9215      * Attempts to recover here are futile. [HGM] Well, we try anyway
9216      */
9217     if ((StrStr(message, "unknown host") != NULL)
9218         || (StrStr(message, "No remote directory") != NULL)
9219         || (StrStr(message, "not found") != NULL)
9220         || (StrStr(message, "No such file") != NULL)
9221         || (StrStr(message, "can't alloc") != NULL)
9222         || (StrStr(message, "Permission denied") != NULL)) {
9223
9224         cps->maybeThinking = FALSE;
9225         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9226                 _(cps->which), cps->program, cps->host, message);
9227         RemoveInputSource(cps->isr);
9228         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9229             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9230             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9231         }
9232         return;
9233     }
9234
9235     /*
9236      * Look for hint output
9237      */
9238     if (sscanf(message, "Hint: %s", buf1) == 1) {
9239         if (cps == &first && hintRequested) {
9240             hintRequested = FALSE;
9241             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9242                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9243                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9244                                     PosFlags(forwardMostMove),
9245                                     fromY, fromX, toY, toX, promoChar, buf1);
9246                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9247                 DisplayInformation(buf2);
9248             } else {
9249                 /* Hint move could not be parsed!? */
9250               snprintf(buf2, sizeof(buf2),
9251                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9252                         buf1, _(cps->which));
9253                 DisplayError(buf2, 0);
9254             }
9255         } else {
9256           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9257         }
9258         return;
9259     }
9260
9261     /*
9262      * Ignore other messages if game is not in progress
9263      */
9264     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9265         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9266
9267     /*
9268      * look for win, lose, draw, or draw offer
9269      */
9270     if (strncmp(message, "1-0", 3) == 0) {
9271         char *p, *q, *r = "";
9272         p = strchr(message, '{');
9273         if (p) {
9274             q = strchr(p, '}');
9275             if (q) {
9276                 *q = NULLCHAR;
9277                 r = p + 1;
9278             }
9279         }
9280         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9281         return;
9282     } else if (strncmp(message, "0-1", 3) == 0) {
9283         char *p, *q, *r = "";
9284         p = strchr(message, '{');
9285         if (p) {
9286             q = strchr(p, '}');
9287             if (q) {
9288                 *q = NULLCHAR;
9289                 r = p + 1;
9290             }
9291         }
9292         /* Kludge for Arasan 4.1 bug */
9293         if (strcmp(r, "Black resigns") == 0) {
9294             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9295             return;
9296         }
9297         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9298         return;
9299     } else if (strncmp(message, "1/2", 3) == 0) {
9300         char *p, *q, *r = "";
9301         p = strchr(message, '{');
9302         if (p) {
9303             q = strchr(p, '}');
9304             if (q) {
9305                 *q = NULLCHAR;
9306                 r = p + 1;
9307             }
9308         }
9309
9310         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9311         return;
9312
9313     } else if (strncmp(message, "White resign", 12) == 0) {
9314         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9315         return;
9316     } else if (strncmp(message, "Black resign", 12) == 0) {
9317         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9318         return;
9319     } else if (strncmp(message, "White matches", 13) == 0 ||
9320                strncmp(message, "Black matches", 13) == 0   ) {
9321         /* [HGM] ignore GNUShogi noises */
9322         return;
9323     } else if (strncmp(message, "White", 5) == 0 &&
9324                message[5] != '(' &&
9325                StrStr(message, "Black") == NULL) {
9326         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9327         return;
9328     } else if (strncmp(message, "Black", 5) == 0 &&
9329                message[5] != '(') {
9330         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9331         return;
9332     } else if (strcmp(message, "resign") == 0 ||
9333                strcmp(message, "computer resigns") == 0) {
9334         switch (gameMode) {
9335           case MachinePlaysBlack:
9336           case IcsPlayingBlack:
9337             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9338             break;
9339           case MachinePlaysWhite:
9340           case IcsPlayingWhite:
9341             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9342             break;
9343           case TwoMachinesPlay:
9344             if (cps->twoMachinesColor[0] == 'w')
9345               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9346             else
9347               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9348             break;
9349           default:
9350             /* can't happen */
9351             break;
9352         }
9353         return;
9354     } else if (strncmp(message, "opponent mates", 14) == 0) {
9355         switch (gameMode) {
9356           case MachinePlaysBlack:
9357           case IcsPlayingBlack:
9358             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9359             break;
9360           case MachinePlaysWhite:
9361           case IcsPlayingWhite:
9362             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9363             break;
9364           case TwoMachinesPlay:
9365             if (cps->twoMachinesColor[0] == 'w')
9366               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9367             else
9368               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9369             break;
9370           default:
9371             /* can't happen */
9372             break;
9373         }
9374         return;
9375     } else if (strncmp(message, "computer mates", 14) == 0) {
9376         switch (gameMode) {
9377           case MachinePlaysBlack:
9378           case IcsPlayingBlack:
9379             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9380             break;
9381           case MachinePlaysWhite:
9382           case IcsPlayingWhite:
9383             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9384             break;
9385           case TwoMachinesPlay:
9386             if (cps->twoMachinesColor[0] == 'w')
9387               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9388             else
9389               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9390             break;
9391           default:
9392             /* can't happen */
9393             break;
9394         }
9395         return;
9396     } else if (strncmp(message, "checkmate", 9) == 0) {
9397         if (WhiteOnMove(forwardMostMove)) {
9398             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9399         } else {
9400             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9401         }
9402         return;
9403     } else if (strstr(message, "Draw") != NULL ||
9404                strstr(message, "game is a draw") != NULL) {
9405         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9406         return;
9407     } else if (strstr(message, "offer") != NULL &&
9408                strstr(message, "draw") != NULL) {
9409 #if ZIPPY
9410         if (appData.zippyPlay && first.initDone) {
9411             /* Relay offer to ICS */
9412             SendToICS(ics_prefix);
9413             SendToICS("draw\n");
9414         }
9415 #endif
9416         cps->offeredDraw = 2; /* valid until this engine moves twice */
9417         if (gameMode == TwoMachinesPlay) {
9418             if (cps->other->offeredDraw) {
9419                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9420             /* [HGM] in two-machine mode we delay relaying draw offer      */
9421             /* until after we also have move, to see if it is really claim */
9422             }
9423         } else if (gameMode == MachinePlaysWhite ||
9424                    gameMode == MachinePlaysBlack) {
9425           if (userOfferedDraw) {
9426             DisplayInformation(_("Machine accepts your draw offer"));
9427             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9428           } else {
9429             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9430           }
9431         }
9432     }
9433
9434
9435     /*
9436      * Look for thinking output
9437      */
9438     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9439           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9440                                 ) {
9441         int plylev, mvleft, mvtot, curscore, time;
9442         char mvname[MOVE_LEN];
9443         u64 nodes; // [DM]
9444         char plyext;
9445         int ignore = FALSE;
9446         int prefixHint = FALSE;
9447         mvname[0] = NULLCHAR;
9448
9449         switch (gameMode) {
9450           case MachinePlaysBlack:
9451           case IcsPlayingBlack:
9452             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9453             break;
9454           case MachinePlaysWhite:
9455           case IcsPlayingWhite:
9456             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9457             break;
9458           case AnalyzeMode:
9459           case AnalyzeFile:
9460             break;
9461           case IcsObserving: /* [DM] icsEngineAnalyze */
9462             if (!appData.icsEngineAnalyze) ignore = TRUE;
9463             break;
9464           case TwoMachinesPlay:
9465             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9466                 ignore = TRUE;
9467             }
9468             break;
9469           default:
9470             ignore = TRUE;
9471             break;
9472         }
9473
9474         if (!ignore) {
9475             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9476             buf1[0] = NULLCHAR;
9477             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9478                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9479
9480                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9481                     nodes += u64Const(0x100000000);
9482
9483                 if (plyext != ' ' && plyext != '\t') {
9484                     time *= 100;
9485                 }
9486
9487                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9488                 if( cps->scoreIsAbsolute &&
9489                     ( gameMode == MachinePlaysBlack ||
9490                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9491                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9492                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9493                      !WhiteOnMove(currentMove)
9494                     ) )
9495                 {
9496                     curscore = -curscore;
9497                 }
9498
9499                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9500
9501                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9502                         char buf[MSG_SIZ];
9503                         FILE *f;
9504                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9505                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9506                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9507                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9508                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9509                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9510                                 fclose(f);
9511                         }
9512                         else
9513                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9514                           DisplayError(_("failed writing PV"), 0);
9515                 }
9516
9517                 tempStats.depth = plylev;
9518                 tempStats.nodes = nodes;
9519                 tempStats.time = time;
9520                 tempStats.score = curscore;
9521                 tempStats.got_only_move = 0;
9522
9523                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9524                         int ticklen;
9525
9526                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9527                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9528                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9529                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9530                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9531                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9532                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9533                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9534                 }
9535
9536                 /* Buffer overflow protection */
9537                 if (pv[0] != NULLCHAR) {
9538                     if (strlen(pv) >= sizeof(tempStats.movelist)
9539                         && appData.debugMode) {
9540                         fprintf(debugFP,
9541                                 "PV is too long; using the first %u bytes.\n",
9542                                 (unsigned) sizeof(tempStats.movelist) - 1);
9543                     }
9544
9545                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9546                 } else {
9547                     sprintf(tempStats.movelist, " no PV\n");
9548                 }
9549
9550                 if (tempStats.seen_stat) {
9551                     tempStats.ok_to_send = 1;
9552                 }
9553
9554                 if (strchr(tempStats.movelist, '(') != NULL) {
9555                     tempStats.line_is_book = 1;
9556                     tempStats.nr_moves = 0;
9557                     tempStats.moves_left = 0;
9558                 } else {
9559                     tempStats.line_is_book = 0;
9560                 }
9561
9562                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9563                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9564
9565                 SendProgramStatsToFrontend( cps, &tempStats );
9566
9567                 /*
9568                     [AS] Protect the thinkOutput buffer from overflow... this
9569                     is only useful if buf1 hasn't overflowed first!
9570                 */
9571                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9572                          plylev,
9573                          (gameMode == TwoMachinesPlay ?
9574                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9575                          ((double) curscore) / 100.0,
9576                          prefixHint ? lastHint : "",
9577                          prefixHint ? " " : "" );
9578
9579                 if( buf1[0] != NULLCHAR ) {
9580                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9581
9582                     if( strlen(pv) > max_len ) {
9583                         if( appData.debugMode) {
9584                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9585                         }
9586                         pv[max_len+1] = '\0';
9587                     }
9588
9589                     strcat( thinkOutput, pv);
9590                 }
9591
9592                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9593                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9594                     DisplayMove(currentMove - 1);
9595                 }
9596                 return;
9597
9598             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9599                 /* crafty (9.25+) says "(only move) <move>"
9600                  * if there is only 1 legal move
9601                  */
9602                 sscanf(p, "(only move) %s", buf1);
9603                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9604                 sprintf(programStats.movelist, "%s (only move)", buf1);
9605                 programStats.depth = 1;
9606                 programStats.nr_moves = 1;
9607                 programStats.moves_left = 1;
9608                 programStats.nodes = 1;
9609                 programStats.time = 1;
9610                 programStats.got_only_move = 1;
9611
9612                 /* Not really, but we also use this member to
9613                    mean "line isn't going to change" (Crafty
9614                    isn't searching, so stats won't change) */
9615                 programStats.line_is_book = 1;
9616
9617                 SendProgramStatsToFrontend( cps, &programStats );
9618
9619                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9620                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9621                     DisplayMove(currentMove - 1);
9622                 }
9623                 return;
9624             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9625                               &time, &nodes, &plylev, &mvleft,
9626                               &mvtot, mvname) >= 5) {
9627                 /* The stat01: line is from Crafty (9.29+) in response
9628                    to the "." command */
9629                 programStats.seen_stat = 1;
9630                 cps->maybeThinking = TRUE;
9631
9632                 if (programStats.got_only_move || !appData.periodicUpdates)
9633                   return;
9634
9635                 programStats.depth = plylev;
9636                 programStats.time = time;
9637                 programStats.nodes = nodes;
9638                 programStats.moves_left = mvleft;
9639                 programStats.nr_moves = mvtot;
9640                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9641                 programStats.ok_to_send = 1;
9642                 programStats.movelist[0] = '\0';
9643
9644                 SendProgramStatsToFrontend( cps, &programStats );
9645
9646                 return;
9647
9648             } else if (strncmp(message,"++",2) == 0) {
9649                 /* Crafty 9.29+ outputs this */
9650                 programStats.got_fail = 2;
9651                 return;
9652
9653             } else if (strncmp(message,"--",2) == 0) {
9654                 /* Crafty 9.29+ outputs this */
9655                 programStats.got_fail = 1;
9656                 return;
9657
9658             } else if (thinkOutput[0] != NULLCHAR &&
9659                        strncmp(message, "    ", 4) == 0) {
9660                 unsigned message_len;
9661
9662                 p = message;
9663                 while (*p && *p == ' ') p++;
9664
9665                 message_len = strlen( p );
9666
9667                 /* [AS] Avoid buffer overflow */
9668                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9669                     strcat(thinkOutput, " ");
9670                     strcat(thinkOutput, p);
9671                 }
9672
9673                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9674                     strcat(programStats.movelist, " ");
9675                     strcat(programStats.movelist, p);
9676                 }
9677
9678                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9679                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9680                     DisplayMove(currentMove - 1);
9681                 }
9682                 return;
9683             }
9684         }
9685         else {
9686             buf1[0] = NULLCHAR;
9687
9688             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9689                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9690             {
9691                 ChessProgramStats cpstats;
9692
9693                 if (plyext != ' ' && plyext != '\t') {
9694                     time *= 100;
9695                 }
9696
9697                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9698                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9699                     curscore = -curscore;
9700                 }
9701
9702                 cpstats.depth = plylev;
9703                 cpstats.nodes = nodes;
9704                 cpstats.time = time;
9705                 cpstats.score = curscore;
9706                 cpstats.got_only_move = 0;
9707                 cpstats.movelist[0] = '\0';
9708
9709                 if (buf1[0] != NULLCHAR) {
9710                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9711                 }
9712
9713                 cpstats.ok_to_send = 0;
9714                 cpstats.line_is_book = 0;
9715                 cpstats.nr_moves = 0;
9716                 cpstats.moves_left = 0;
9717
9718                 SendProgramStatsToFrontend( cps, &cpstats );
9719             }
9720         }
9721     }
9722 }
9723
9724
9725 /* Parse a game score from the character string "game", and
9726    record it as the history of the current game.  The game
9727    score is NOT assumed to start from the standard position.
9728    The display is not updated in any way.
9729    */
9730 void
9731 ParseGameHistory (char *game)
9732 {
9733     ChessMove moveType;
9734     int fromX, fromY, toX, toY, boardIndex;
9735     char promoChar;
9736     char *p, *q;
9737     char buf[MSG_SIZ];
9738
9739     if (appData.debugMode)
9740       fprintf(debugFP, "Parsing game history: %s\n", game);
9741
9742     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9743     gameInfo.site = StrSave(appData.icsHost);
9744     gameInfo.date = PGNDate();
9745     gameInfo.round = StrSave("-");
9746
9747     /* Parse out names of players */
9748     while (*game == ' ') game++;
9749     p = buf;
9750     while (*game != ' ') *p++ = *game++;
9751     *p = NULLCHAR;
9752     gameInfo.white = StrSave(buf);
9753     while (*game == ' ') game++;
9754     p = buf;
9755     while (*game != ' ' && *game != '\n') *p++ = *game++;
9756     *p = NULLCHAR;
9757     gameInfo.black = StrSave(buf);
9758
9759     /* Parse moves */
9760     boardIndex = blackPlaysFirst ? 1 : 0;
9761     yynewstr(game);
9762     for (;;) {
9763         yyboardindex = boardIndex;
9764         moveType = (ChessMove) Myylex();
9765         switch (moveType) {
9766           case IllegalMove:             /* maybe suicide chess, etc. */
9767   if (appData.debugMode) {
9768     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9769     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9770     setbuf(debugFP, NULL);
9771   }
9772           case WhitePromotion:
9773           case BlackPromotion:
9774           case WhiteNonPromotion:
9775           case BlackNonPromotion:
9776           case NormalMove:
9777           case FirstLeg:
9778           case WhiteCapturesEnPassant:
9779           case BlackCapturesEnPassant:
9780           case WhiteKingSideCastle:
9781           case WhiteQueenSideCastle:
9782           case BlackKingSideCastle:
9783           case BlackQueenSideCastle:
9784           case WhiteKingSideCastleWild:
9785           case WhiteQueenSideCastleWild:
9786           case BlackKingSideCastleWild:
9787           case BlackQueenSideCastleWild:
9788           /* PUSH Fabien */
9789           case WhiteHSideCastleFR:
9790           case WhiteASideCastleFR:
9791           case BlackHSideCastleFR:
9792           case BlackASideCastleFR:
9793           /* POP Fabien */
9794             fromX = currentMoveString[0] - AAA;
9795             fromY = currentMoveString[1] - ONE;
9796             toX = currentMoveString[2] - AAA;
9797             toY = currentMoveString[3] - ONE;
9798             promoChar = currentMoveString[4];
9799             break;
9800           case WhiteDrop:
9801           case BlackDrop:
9802             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9803             fromX = moveType == WhiteDrop ?
9804               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9805             (int) CharToPiece(ToLower(currentMoveString[0]));
9806             fromY = DROP_RANK;
9807             toX = currentMoveString[2] - AAA;
9808             toY = currentMoveString[3] - ONE;
9809             promoChar = NULLCHAR;
9810             break;
9811           case AmbiguousMove:
9812             /* bug? */
9813             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9814   if (appData.debugMode) {
9815     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9816     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9817     setbuf(debugFP, NULL);
9818   }
9819             DisplayError(buf, 0);
9820             return;
9821           case ImpossibleMove:
9822             /* bug? */
9823             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9824   if (appData.debugMode) {
9825     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9826     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9827     setbuf(debugFP, NULL);
9828   }
9829             DisplayError(buf, 0);
9830             return;
9831           case EndOfFile:
9832             if (boardIndex < backwardMostMove) {
9833                 /* Oops, gap.  How did that happen? */
9834                 DisplayError(_("Gap in move list"), 0);
9835                 return;
9836             }
9837             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9838             if (boardIndex > forwardMostMove) {
9839                 forwardMostMove = boardIndex;
9840             }
9841             return;
9842           case ElapsedTime:
9843             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9844                 strcat(parseList[boardIndex-1], " ");
9845                 strcat(parseList[boardIndex-1], yy_text);
9846             }
9847             continue;
9848           case Comment:
9849           case PGNTag:
9850           case NAG:
9851           default:
9852             /* ignore */
9853             continue;
9854           case WhiteWins:
9855           case BlackWins:
9856           case GameIsDrawn:
9857           case GameUnfinished:
9858             if (gameMode == IcsExamining) {
9859                 if (boardIndex < backwardMostMove) {
9860                     /* Oops, gap.  How did that happen? */
9861                     return;
9862                 }
9863                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9864                 return;
9865             }
9866             gameInfo.result = moveType;
9867             p = strchr(yy_text, '{');
9868             if (p == NULL) p = strchr(yy_text, '(');
9869             if (p == NULL) {
9870                 p = yy_text;
9871                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9872             } else {
9873                 q = strchr(p, *p == '{' ? '}' : ')');
9874                 if (q != NULL) *q = NULLCHAR;
9875                 p++;
9876             }
9877             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9878             gameInfo.resultDetails = StrSave(p);
9879             continue;
9880         }
9881         if (boardIndex >= forwardMostMove &&
9882             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9883             backwardMostMove = blackPlaysFirst ? 1 : 0;
9884             return;
9885         }
9886         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9887                                  fromY, fromX, toY, toX, promoChar,
9888                                  parseList[boardIndex]);
9889         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9890         /* currentMoveString is set as a side-effect of yylex */
9891         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9892         strcat(moveList[boardIndex], "\n");
9893         boardIndex++;
9894         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9895         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9896           case MT_NONE:
9897           case MT_STALEMATE:
9898           default:
9899             break;
9900           case MT_CHECK:
9901             if(!IS_SHOGI(gameInfo.variant))
9902                 strcat(parseList[boardIndex - 1], "+");
9903             break;
9904           case MT_CHECKMATE:
9905           case MT_STAINMATE:
9906             strcat(parseList[boardIndex - 1], "#");
9907             break;
9908         }
9909     }
9910 }
9911
9912
9913 /* Apply a move to the given board  */
9914 void
9915 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9916 {
9917   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9918   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9919
9920     /* [HGM] compute & store e.p. status and castling rights for new position */
9921     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9922
9923       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9924       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9925       board[EP_STATUS] = EP_NONE;
9926       board[EP_FILE] = board[EP_RANK] = 100;
9927
9928   if (fromY == DROP_RANK) {
9929         /* must be first */
9930         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9931             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9932             return;
9933         }
9934         piece = board[toY][toX] = (ChessSquare) fromX;
9935   } else {
9936 //      ChessSquare victim;
9937       int i;
9938
9939       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9940 //           victim = board[killY][killX],
9941            killed = board[killY][killX],
9942            board[killY][killX] = EmptySquare,
9943            board[EP_STATUS] = EP_CAPTURE;
9944
9945       if( board[toY][toX] != EmptySquare ) {
9946            board[EP_STATUS] = EP_CAPTURE;
9947            if( (fromX != toX || fromY != toY) && // not igui!
9948                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9949                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9950                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9951            }
9952       }
9953
9954       pawn = board[fromY][fromX];
9955       if( pawn == WhiteLance || pawn == BlackLance ) {
9956            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9957                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9958                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9959            }
9960       }
9961       if( pawn == WhitePawn ) {
9962            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9963                board[EP_STATUS] = EP_PAWN_MOVE;
9964            if( toY-fromY>=2) {
9965                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9966                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9967                         gameInfo.variant != VariantBerolina || toX < fromX)
9968                       board[EP_STATUS] = toX | berolina;
9969                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9970                         gameInfo.variant != VariantBerolina || toX > fromX)
9971                       board[EP_STATUS] = toX;
9972            }
9973       } else
9974       if( pawn == BlackPawn ) {
9975            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9976                board[EP_STATUS] = EP_PAWN_MOVE;
9977            if( toY-fromY<= -2) {
9978                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9979                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9980                         gameInfo.variant != VariantBerolina || toX < fromX)
9981                       board[EP_STATUS] = toX | berolina;
9982                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9983                         gameInfo.variant != VariantBerolina || toX > fromX)
9984                       board[EP_STATUS] = toX;
9985            }
9986        }
9987
9988        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9989        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9990        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9991        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9992
9993        for(i=0; i<nrCastlingRights; i++) {
9994            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9995               board[CASTLING][i] == toX   && castlingRank[i] == toY
9996              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9997        }
9998
9999        if(gameInfo.variant == VariantSChess) { // update virginity
10000            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10001            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10002            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10003            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10004        }
10005
10006      if (fromX == toX && fromY == toY) return;
10007
10008      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10009      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10010      if(gameInfo.variant == VariantKnightmate)
10011          king += (int) WhiteUnicorn - (int) WhiteKing;
10012
10013     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10014        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
10015         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10016         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10017         board[EP_STATUS] = EP_NONE; // capture was fake!
10018     } else
10019     /* Code added by Tord: */
10020     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10021     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10022         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10023       board[EP_STATUS] = EP_NONE; // capture was fake!
10024       board[fromY][fromX] = EmptySquare;
10025       board[toY][toX] = EmptySquare;
10026       if((toX > fromX) != (piece == WhiteRook)) {
10027         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10028       } else {
10029         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10030       }
10031     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10032                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10033       board[EP_STATUS] = EP_NONE;
10034       board[fromY][fromX] = EmptySquare;
10035       board[toY][toX] = EmptySquare;
10036       if((toX > fromX) != (piece == BlackRook)) {
10037         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10038       } else {
10039         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10040       }
10041     /* End of code added by Tord */
10042
10043     } else if (board[fromY][fromX] == king
10044         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10045         && toY == fromY && toX > fromX+1) {
10046         board[fromY][fromX] = EmptySquare;
10047         board[toY][toX] = king;
10048         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10049         board[toY][toX-1] = board[fromY][rookX];
10050         board[fromY][rookX] = EmptySquare;
10051     } else if (board[fromY][fromX] == king
10052         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10053                && toY == fromY && toX < fromX-1) {
10054         board[fromY][fromX] = EmptySquare;
10055         board[toY][toX] = king;
10056         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10057         board[toY][toX+1] = board[fromY][rookX];
10058         board[fromY][rookX] = EmptySquare;
10059     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10060                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10061                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10062                ) {
10063         /* white pawn promotion */
10064         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10065         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10066             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10067         board[fromY][fromX] = EmptySquare;
10068     } else if ((fromY >= BOARD_HEIGHT>>1)
10069                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10070                && (toX != fromX)
10071                && gameInfo.variant != VariantXiangqi
10072                && gameInfo.variant != VariantBerolina
10073                && (pawn == WhitePawn)
10074                && (board[toY][toX] == EmptySquare)) {
10075         board[fromY][fromX] = EmptySquare;
10076         board[toY][toX] = piece;
10077         if(toY == epRank - 128 + 1)
10078             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10079         else
10080             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10081     } else if ((fromY == BOARD_HEIGHT-4)
10082                && (toX == fromX)
10083                && gameInfo.variant == VariantBerolina
10084                && (board[fromY][fromX] == WhitePawn)
10085                && (board[toY][toX] == EmptySquare)) {
10086         board[fromY][fromX] = EmptySquare;
10087         board[toY][toX] = WhitePawn;
10088         if(oldEP & EP_BEROLIN_A) {
10089                 captured = board[fromY][fromX-1];
10090                 board[fromY][fromX-1] = EmptySquare;
10091         }else{  captured = board[fromY][fromX+1];
10092                 board[fromY][fromX+1] = EmptySquare;
10093         }
10094     } else if (board[fromY][fromX] == king
10095         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10096                && toY == fromY && toX > fromX+1) {
10097         board[fromY][fromX] = EmptySquare;
10098         board[toY][toX] = king;
10099         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10100         board[toY][toX-1] = board[fromY][rookX];
10101         board[fromY][rookX] = EmptySquare;
10102     } else if (board[fromY][fromX] == king
10103         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10104                && toY == fromY && toX < fromX-1) {
10105         board[fromY][fromX] = EmptySquare;
10106         board[toY][toX] = king;
10107         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10108         board[toY][toX+1] = board[fromY][rookX];
10109         board[fromY][rookX] = EmptySquare;
10110     } else if (fromY == 7 && fromX == 3
10111                && board[fromY][fromX] == BlackKing
10112                && toY == 7 && toX == 5) {
10113         board[fromY][fromX] = EmptySquare;
10114         board[toY][toX] = BlackKing;
10115         board[fromY][7] = EmptySquare;
10116         board[toY][4] = BlackRook;
10117     } else if (fromY == 7 && fromX == 3
10118                && board[fromY][fromX] == BlackKing
10119                && toY == 7 && toX == 1) {
10120         board[fromY][fromX] = EmptySquare;
10121         board[toY][toX] = BlackKing;
10122         board[fromY][0] = EmptySquare;
10123         board[toY][2] = BlackRook;
10124     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10125                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10126                && toY < promoRank && promoChar
10127                ) {
10128         /* black pawn promotion */
10129         board[toY][toX] = CharToPiece(ToLower(promoChar));
10130         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10131             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10132         board[fromY][fromX] = EmptySquare;
10133     } else if ((fromY < BOARD_HEIGHT>>1)
10134                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10135                && (toX != fromX)
10136                && gameInfo.variant != VariantXiangqi
10137                && gameInfo.variant != VariantBerolina
10138                && (pawn == BlackPawn)
10139                && (board[toY][toX] == EmptySquare)) {
10140         board[fromY][fromX] = EmptySquare;
10141         board[toY][toX] = piece;
10142         if(toY == epRank - 128 - 1)
10143             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10144         else
10145             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10146     } else if ((fromY == 3)
10147                && (toX == fromX)
10148                && gameInfo.variant == VariantBerolina
10149                && (board[fromY][fromX] == BlackPawn)
10150                && (board[toY][toX] == EmptySquare)) {
10151         board[fromY][fromX] = EmptySquare;
10152         board[toY][toX] = BlackPawn;
10153         if(oldEP & EP_BEROLIN_A) {
10154                 captured = board[fromY][fromX-1];
10155                 board[fromY][fromX-1] = EmptySquare;
10156         }else{  captured = board[fromY][fromX+1];
10157                 board[fromY][fromX+1] = EmptySquare;
10158         }
10159     } else {
10160         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10161         board[fromY][fromX] = EmptySquare;
10162         board[toY][toX] = piece;
10163     }
10164   }
10165
10166     if (gameInfo.holdingsWidth != 0) {
10167
10168       /* !!A lot more code needs to be written to support holdings  */
10169       /* [HGM] OK, so I have written it. Holdings are stored in the */
10170       /* penultimate board files, so they are automaticlly stored   */
10171       /* in the game history.                                       */
10172       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10173                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10174         /* Delete from holdings, by decreasing count */
10175         /* and erasing image if necessary            */
10176         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10177         if(p < (int) BlackPawn) { /* white drop */
10178              p -= (int)WhitePawn;
10179                  p = PieceToNumber((ChessSquare)p);
10180              if(p >= gameInfo.holdingsSize) p = 0;
10181              if(--board[p][BOARD_WIDTH-2] <= 0)
10182                   board[p][BOARD_WIDTH-1] = EmptySquare;
10183              if((int)board[p][BOARD_WIDTH-2] < 0)
10184                         board[p][BOARD_WIDTH-2] = 0;
10185         } else {                  /* black drop */
10186              p -= (int)BlackPawn;
10187                  p = PieceToNumber((ChessSquare)p);
10188              if(p >= gameInfo.holdingsSize) p = 0;
10189              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10190                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10191              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10192                         board[BOARD_HEIGHT-1-p][1] = 0;
10193         }
10194       }
10195       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10196           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10197         /* [HGM] holdings: Add to holdings, if holdings exist */
10198         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10199                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10200                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10201         }
10202         p = (int) captured;
10203         if (p >= (int) BlackPawn) {
10204           p -= (int)BlackPawn;
10205           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10206                   /* Restore shogi-promoted piece to its original  first */
10207                   captured = (ChessSquare) (DEMOTED captured);
10208                   p = DEMOTED p;
10209           }
10210           p = PieceToNumber((ChessSquare)p);
10211           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10212           board[p][BOARD_WIDTH-2]++;
10213           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10214         } else {
10215           p -= (int)WhitePawn;
10216           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10217                   captured = (ChessSquare) (DEMOTED captured);
10218                   p = DEMOTED p;
10219           }
10220           p = PieceToNumber((ChessSquare)p);
10221           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10222           board[BOARD_HEIGHT-1-p][1]++;
10223           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10224         }
10225       }
10226     } else if (gameInfo.variant == VariantAtomic) {
10227       if (captured != EmptySquare) {
10228         int y, x;
10229         for (y = toY-1; y <= toY+1; y++) {
10230           for (x = toX-1; x <= toX+1; x++) {
10231             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10232                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10233               board[y][x] = EmptySquare;
10234             }
10235           }
10236         }
10237         board[toY][toX] = EmptySquare;
10238       }
10239     }
10240
10241     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10242         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10243     } else
10244     if(promoChar == '+') {
10245         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10246         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10247         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10248           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10249     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10250         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10251         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10252            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10253         board[toY][toX] = newPiece;
10254     }
10255     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10256                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10257         // [HGM] superchess: take promotion piece out of holdings
10258         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10259         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10260             if(!--board[k][BOARD_WIDTH-2])
10261                 board[k][BOARD_WIDTH-1] = EmptySquare;
10262         } else {
10263             if(!--board[BOARD_HEIGHT-1-k][1])
10264                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10265         }
10266     }
10267 }
10268
10269 /* Updates forwardMostMove */
10270 void
10271 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10272 {
10273     int x = toX, y = toY;
10274     char *s = parseList[forwardMostMove];
10275     ChessSquare p = boards[forwardMostMove][toY][toX];
10276 //    forwardMostMove++; // [HGM] bare: moved downstream
10277
10278     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10279     (void) CoordsToAlgebraic(boards[forwardMostMove],
10280                              PosFlags(forwardMostMove),
10281                              fromY, fromX, y, x, promoChar,
10282                              s);
10283     if(killX >= 0 && killY >= 0)
10284         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10285
10286     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10287         int timeLeft; static int lastLoadFlag=0; int king, piece;
10288         piece = boards[forwardMostMove][fromY][fromX];
10289         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10290         if(gameInfo.variant == VariantKnightmate)
10291             king += (int) WhiteUnicorn - (int) WhiteKing;
10292         if(forwardMostMove == 0) {
10293             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10294                 fprintf(serverMoves, "%s;", UserName());
10295             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10296                 fprintf(serverMoves, "%s;", second.tidy);
10297             fprintf(serverMoves, "%s;", first.tidy);
10298             if(gameMode == MachinePlaysWhite)
10299                 fprintf(serverMoves, "%s;", UserName());
10300             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10301                 fprintf(serverMoves, "%s;", second.tidy);
10302         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10303         lastLoadFlag = loadFlag;
10304         // print base move
10305         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10306         // print castling suffix
10307         if( toY == fromY && piece == king ) {
10308             if(toX-fromX > 1)
10309                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10310             if(fromX-toX >1)
10311                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10312         }
10313         // e.p. suffix
10314         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10315              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10316              boards[forwardMostMove][toY][toX] == EmptySquare
10317              && fromX != toX && fromY != toY)
10318                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10319         // promotion suffix
10320         if(promoChar != NULLCHAR) {
10321             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10322                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10323                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10324             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10325         }
10326         if(!loadFlag) {
10327                 char buf[MOVE_LEN*2], *p; int len;
10328             fprintf(serverMoves, "/%d/%d",
10329                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10330             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10331             else                      timeLeft = blackTimeRemaining/1000;
10332             fprintf(serverMoves, "/%d", timeLeft);
10333                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10334                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10335                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10336                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10337             fprintf(serverMoves, "/%s", buf);
10338         }
10339         fflush(serverMoves);
10340     }
10341
10342     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10343         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10344       return;
10345     }
10346     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10347     if (commentList[forwardMostMove+1] != NULL) {
10348         free(commentList[forwardMostMove+1]);
10349         commentList[forwardMostMove+1] = NULL;
10350     }
10351     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10352     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10353     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10354     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10355     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10356     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10357     adjustedClock = FALSE;
10358     gameInfo.result = GameUnfinished;
10359     if (gameInfo.resultDetails != NULL) {
10360         free(gameInfo.resultDetails);
10361         gameInfo.resultDetails = NULL;
10362     }
10363     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10364                               moveList[forwardMostMove - 1]);
10365     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10366       case MT_NONE:
10367       case MT_STALEMATE:
10368       default:
10369         break;
10370       case MT_CHECK:
10371         if(!IS_SHOGI(gameInfo.variant))
10372             strcat(parseList[forwardMostMove - 1], "+");
10373         break;
10374       case MT_CHECKMATE:
10375       case MT_STAINMATE:
10376         strcat(parseList[forwardMostMove - 1], "#");
10377         break;
10378     }
10379 }
10380
10381 /* Updates currentMove if not pausing */
10382 void
10383 ShowMove (int fromX, int fromY, int toX, int toY)
10384 {
10385     int instant = (gameMode == PlayFromGameFile) ?
10386         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10387     if(appData.noGUI) return;
10388     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10389         if (!instant) {
10390             if (forwardMostMove == currentMove + 1) {
10391                 AnimateMove(boards[forwardMostMove - 1],
10392                             fromX, fromY, toX, toY);
10393             }
10394         }
10395         currentMove = forwardMostMove;
10396     }
10397
10398     killX = killY = -1; // [HGM] lion: used up
10399
10400     if (instant) return;
10401
10402     DisplayMove(currentMove - 1);
10403     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10404             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10405                 SetHighlights(fromX, fromY, toX, toY);
10406             }
10407     }
10408     DrawPosition(FALSE, boards[currentMove]);
10409     DisplayBothClocks();
10410     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10411 }
10412
10413 void
10414 SendEgtPath (ChessProgramState *cps)
10415 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10416         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10417
10418         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10419
10420         while(*p) {
10421             char c, *q = name+1, *r, *s;
10422
10423             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10424             while(*p && *p != ',') *q++ = *p++;
10425             *q++ = ':'; *q = 0;
10426             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10427                 strcmp(name, ",nalimov:") == 0 ) {
10428                 // take nalimov path from the menu-changeable option first, if it is defined
10429               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10430                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10431             } else
10432             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10433                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10434                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10435                 s = r = StrStr(s, ":") + 1; // beginning of path info
10436                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10437                 c = *r; *r = 0;             // temporarily null-terminate path info
10438                     *--q = 0;               // strip of trailig ':' from name
10439                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10440                 *r = c;
10441                 SendToProgram(buf,cps);     // send egtbpath command for this format
10442             }
10443             if(*p == ',') p++; // read away comma to position for next format name
10444         }
10445 }
10446
10447 static int
10448 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10449 {
10450       int width = 8, height = 8, holdings = 0;             // most common sizes
10451       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10452       // correct the deviations default for each variant
10453       if( v == VariantXiangqi ) width = 9,  height = 10;
10454       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10455       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10456       if( v == VariantCapablanca || v == VariantCapaRandom ||
10457           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10458                                 width = 10;
10459       if( v == VariantCourier ) width = 12;
10460       if( v == VariantSuper )                            holdings = 8;
10461       if( v == VariantGreat )   width = 10,              holdings = 8;
10462       if( v == VariantSChess )                           holdings = 7;
10463       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10464       if( v == VariantChuChess) width = 10, height = 10;
10465       if( v == VariantChu )     width = 12, height = 12;
10466       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10467              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10468              holdingsSize >= 0 && holdingsSize != holdings;
10469 }
10470
10471 char variantError[MSG_SIZ];
10472
10473 char *
10474 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10475 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10476       char *p, *variant = VariantName(v);
10477       static char b[MSG_SIZ];
10478       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10479            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10480                                                holdingsSize, variant); // cook up sized variant name
10481            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10482            if(StrStr(list, b) == NULL) {
10483                // specific sized variant not known, check if general sizing allowed
10484                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10485                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10486                             boardWidth, boardHeight, holdingsSize, engine);
10487                    return NULL;
10488                }
10489                /* [HGM] here we really should compare with the maximum supported board size */
10490            }
10491       } else snprintf(b, MSG_SIZ,"%s", variant);
10492       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10493       p = StrStr(list, b);
10494       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10495       if(p == NULL) {
10496           // occurs not at all in list, or only as sub-string
10497           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10498           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10499               int l = strlen(variantError);
10500               char *q;
10501               while(p != list && p[-1] != ',') p--;
10502               q = strchr(p, ',');
10503               if(q) *q = NULLCHAR;
10504               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10505               if(q) *q= ',';
10506           }
10507           return NULL;
10508       }
10509       return b;
10510 }
10511
10512 void
10513 InitChessProgram (ChessProgramState *cps, int setup)
10514 /* setup needed to setup FRC opening position */
10515 {
10516     char buf[MSG_SIZ], *b;
10517     if (appData.noChessProgram) return;
10518     hintRequested = FALSE;
10519     bookRequested = FALSE;
10520
10521     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10522     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10523     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10524     if(cps->memSize) { /* [HGM] memory */
10525       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10526         SendToProgram(buf, cps);
10527     }
10528     SendEgtPath(cps); /* [HGM] EGT */
10529     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10530       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10531         SendToProgram(buf, cps);
10532     }
10533
10534     setboardSpoiledMachineBlack = FALSE;
10535     SendToProgram(cps->initString, cps);
10536     if (gameInfo.variant != VariantNormal &&
10537         gameInfo.variant != VariantLoadable
10538         /* [HGM] also send variant if board size non-standard */
10539         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10540
10541       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10542                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10543       if (b == NULL) {
10544         VariantClass v;
10545         char c, *q = cps->variants, *p = strchr(q, ',');
10546         if(p) *p = NULLCHAR;
10547         v = StringToVariant(q);
10548         DisplayError(variantError, 0);
10549         if(v != VariantUnknown && cps == &first) {
10550             int w, h, s;
10551             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10552                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10553             ASSIGN(appData.variant, q);
10554             Reset(TRUE, FALSE);
10555         }
10556         if(p) *p = ',';
10557         return;
10558       }
10559
10560       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10561       SendToProgram(buf, cps);
10562     }
10563     currentlyInitializedVariant = gameInfo.variant;
10564
10565     /* [HGM] send opening position in FRC to first engine */
10566     if(setup) {
10567           SendToProgram("force\n", cps);
10568           SendBoard(cps, 0);
10569           /* engine is now in force mode! Set flag to wake it up after first move. */
10570           setboardSpoiledMachineBlack = 1;
10571     }
10572
10573     if (cps->sendICS) {
10574       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10575       SendToProgram(buf, cps);
10576     }
10577     cps->maybeThinking = FALSE;
10578     cps->offeredDraw = 0;
10579     if (!appData.icsActive) {
10580         SendTimeControl(cps, movesPerSession, timeControl,
10581                         timeIncrement, appData.searchDepth,
10582                         searchTime);
10583     }
10584     if (appData.showThinking
10585         // [HGM] thinking: four options require thinking output to be sent
10586         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10587                                 ) {
10588         SendToProgram("post\n", cps);
10589     }
10590     SendToProgram("hard\n", cps);
10591     if (!appData.ponderNextMove) {
10592         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10593            it without being sure what state we are in first.  "hard"
10594            is not a toggle, so that one is OK.
10595          */
10596         SendToProgram("easy\n", cps);
10597     }
10598     if (cps->usePing) {
10599       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10600       SendToProgram(buf, cps);
10601     }
10602     cps->initDone = TRUE;
10603     ClearEngineOutputPane(cps == &second);
10604 }
10605
10606
10607 void
10608 ResendOptions (ChessProgramState *cps)
10609 { // send the stored value of the options
10610   int i;
10611   char buf[MSG_SIZ];
10612   Option *opt = cps->option;
10613   for(i=0; i<cps->nrOptions; i++, opt++) {
10614       switch(opt->type) {
10615         case Spin:
10616         case Slider:
10617         case CheckBox:
10618             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10619           break;
10620         case ComboBox:
10621           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10622           break;
10623         default:
10624             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10625           break;
10626         case Button:
10627         case SaveButton:
10628           continue;
10629       }
10630       SendToProgram(buf, cps);
10631   }
10632 }
10633
10634 void
10635 StartChessProgram (ChessProgramState *cps)
10636 {
10637     char buf[MSG_SIZ];
10638     int err;
10639
10640     if (appData.noChessProgram) return;
10641     cps->initDone = FALSE;
10642
10643     if (strcmp(cps->host, "localhost") == 0) {
10644         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10645     } else if (*appData.remoteShell == NULLCHAR) {
10646         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10647     } else {
10648         if (*appData.remoteUser == NULLCHAR) {
10649           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10650                     cps->program);
10651         } else {
10652           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10653                     cps->host, appData.remoteUser, cps->program);
10654         }
10655         err = StartChildProcess(buf, "", &cps->pr);
10656     }
10657
10658     if (err != 0) {
10659       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10660         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10661         if(cps != &first) return;
10662         appData.noChessProgram = TRUE;
10663         ThawUI();
10664         SetNCPMode();
10665 //      DisplayFatalError(buf, err, 1);
10666 //      cps->pr = NoProc;
10667 //      cps->isr = NULL;
10668         return;
10669     }
10670
10671     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10672     if (cps->protocolVersion > 1) {
10673       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10674       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10675         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10676         cps->comboCnt = 0;  //                and values of combo boxes
10677       }
10678       SendToProgram(buf, cps);
10679       if(cps->reload) ResendOptions(cps);
10680     } else {
10681       SendToProgram("xboard\n", cps);
10682     }
10683 }
10684
10685 void
10686 TwoMachinesEventIfReady P((void))
10687 {
10688   static int curMess = 0;
10689   if (first.lastPing != first.lastPong) {
10690     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10691     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10692     return;
10693   }
10694   if (second.lastPing != second.lastPong) {
10695     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10696     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10697     return;
10698   }
10699   DisplayMessage("", ""); curMess = 0;
10700   TwoMachinesEvent();
10701 }
10702
10703 char *
10704 MakeName (char *template)
10705 {
10706     time_t clock;
10707     struct tm *tm;
10708     static char buf[MSG_SIZ];
10709     char *p = buf;
10710     int i;
10711
10712     clock = time((time_t *)NULL);
10713     tm = localtime(&clock);
10714
10715     while(*p++ = *template++) if(p[-1] == '%') {
10716         switch(*template++) {
10717           case 0:   *p = 0; return buf;
10718           case 'Y': i = tm->tm_year+1900; break;
10719           case 'y': i = tm->tm_year-100; break;
10720           case 'M': i = tm->tm_mon+1; break;
10721           case 'd': i = tm->tm_mday; break;
10722           case 'h': i = tm->tm_hour; break;
10723           case 'm': i = tm->tm_min; break;
10724           case 's': i = tm->tm_sec; break;
10725           default:  i = 0;
10726         }
10727         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10728     }
10729     return buf;
10730 }
10731
10732 int
10733 CountPlayers (char *p)
10734 {
10735     int n = 0;
10736     while(p = strchr(p, '\n')) p++, n++; // count participants
10737     return n;
10738 }
10739
10740 FILE *
10741 WriteTourneyFile (char *results, FILE *f)
10742 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10743     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10744     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10745         // create a file with tournament description
10746         fprintf(f, "-participants {%s}\n", appData.participants);
10747         fprintf(f, "-seedBase %d\n", appData.seedBase);
10748         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10749         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10750         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10751         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10752         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10753         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10754         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10755         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10756         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10757         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10758         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10759         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10760         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10761         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10762         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10763         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10764         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10765         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10766         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10767         fprintf(f, "-smpCores %d\n", appData.smpCores);
10768         if(searchTime > 0)
10769                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10770         else {
10771                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10772                 fprintf(f, "-tc %s\n", appData.timeControl);
10773                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10774         }
10775         fprintf(f, "-results \"%s\"\n", results);
10776     }
10777     return f;
10778 }
10779
10780 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10781
10782 void
10783 Substitute (char *participants, int expunge)
10784 {
10785     int i, changed, changes=0, nPlayers=0;
10786     char *p, *q, *r, buf[MSG_SIZ];
10787     if(participants == NULL) return;
10788     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10789     r = p = participants; q = appData.participants;
10790     while(*p && *p == *q) {
10791         if(*p == '\n') r = p+1, nPlayers++;
10792         p++; q++;
10793     }
10794     if(*p) { // difference
10795         while(*p && *p++ != '\n');
10796         while(*q && *q++ != '\n');
10797       changed = nPlayers;
10798         changes = 1 + (strcmp(p, q) != 0);
10799     }
10800     if(changes == 1) { // a single engine mnemonic was changed
10801         q = r; while(*q) nPlayers += (*q++ == '\n');
10802         p = buf; while(*r && (*p = *r++) != '\n') p++;
10803         *p = NULLCHAR;
10804         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10805         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10806         if(mnemonic[i]) { // The substitute is valid
10807             FILE *f;
10808             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10809                 flock(fileno(f), LOCK_EX);
10810                 ParseArgsFromFile(f);
10811                 fseek(f, 0, SEEK_SET);
10812                 FREE(appData.participants); appData.participants = participants;
10813                 if(expunge) { // erase results of replaced engine
10814                     int len = strlen(appData.results), w, b, dummy;
10815                     for(i=0; i<len; i++) {
10816                         Pairing(i, nPlayers, &w, &b, &dummy);
10817                         if((w == changed || b == changed) && appData.results[i] == '*') {
10818                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10819                             fclose(f);
10820                             return;
10821                         }
10822                     }
10823                     for(i=0; i<len; i++) {
10824                         Pairing(i, nPlayers, &w, &b, &dummy);
10825                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10826                     }
10827                 }
10828                 WriteTourneyFile(appData.results, f);
10829                 fclose(f); // release lock
10830                 return;
10831             }
10832         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10833     }
10834     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10835     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10836     free(participants);
10837     return;
10838 }
10839
10840 int
10841 CheckPlayers (char *participants)
10842 {
10843         int i;
10844         char buf[MSG_SIZ], *p;
10845         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10846         while(p = strchr(participants, '\n')) {
10847             *p = NULLCHAR;
10848             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10849             if(!mnemonic[i]) {
10850                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10851                 *p = '\n';
10852                 DisplayError(buf, 0);
10853                 return 1;
10854             }
10855             *p = '\n';
10856             participants = p + 1;
10857         }
10858         return 0;
10859 }
10860
10861 int
10862 CreateTourney (char *name)
10863 {
10864         FILE *f;
10865         if(matchMode && strcmp(name, appData.tourneyFile)) {
10866              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10867         }
10868         if(name[0] == NULLCHAR) {
10869             if(appData.participants[0])
10870                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10871             return 0;
10872         }
10873         f = fopen(name, "r");
10874         if(f) { // file exists
10875             ASSIGN(appData.tourneyFile, name);
10876             ParseArgsFromFile(f); // parse it
10877         } else {
10878             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10879             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10880                 DisplayError(_("Not enough participants"), 0);
10881                 return 0;
10882             }
10883             if(CheckPlayers(appData.participants)) return 0;
10884             ASSIGN(appData.tourneyFile, name);
10885             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10886             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10887         }
10888         fclose(f);
10889         appData.noChessProgram = FALSE;
10890         appData.clockMode = TRUE;
10891         SetGNUMode();
10892         return 1;
10893 }
10894
10895 int
10896 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10897 {
10898     char buf[MSG_SIZ], *p, *q;
10899     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10900     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10901     skip = !all && group[0]; // if group requested, we start in skip mode
10902     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10903         p = names; q = buf; header = 0;
10904         while(*p && *p != '\n') *q++ = *p++;
10905         *q = 0;
10906         if(*p == '\n') p++;
10907         if(buf[0] == '#') {
10908             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10909             depth++; // we must be entering a new group
10910             if(all) continue; // suppress printing group headers when complete list requested
10911             header = 1;
10912             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10913         }
10914         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10915         if(engineList[i]) free(engineList[i]);
10916         engineList[i] = strdup(buf);
10917         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10918         if(engineMnemonic[i]) free(engineMnemonic[i]);
10919         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10920             strcat(buf, " (");
10921             sscanf(q + 8, "%s", buf + strlen(buf));
10922             strcat(buf, ")");
10923         }
10924         engineMnemonic[i] = strdup(buf);
10925         i++;
10926     }
10927     engineList[i] = engineMnemonic[i] = NULL;
10928     return i;
10929 }
10930
10931 // following implemented as macro to avoid type limitations
10932 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10933
10934 void
10935 SwapEngines (int n)
10936 {   // swap settings for first engine and other engine (so far only some selected options)
10937     int h;
10938     char *p;
10939     if(n == 0) return;
10940     SWAP(directory, p)
10941     SWAP(chessProgram, p)
10942     SWAP(isUCI, h)
10943     SWAP(hasOwnBookUCI, h)
10944     SWAP(protocolVersion, h)
10945     SWAP(reuse, h)
10946     SWAP(scoreIsAbsolute, h)
10947     SWAP(timeOdds, h)
10948     SWAP(logo, p)
10949     SWAP(pgnName, p)
10950     SWAP(pvSAN, h)
10951     SWAP(engOptions, p)
10952     SWAP(engInitString, p)
10953     SWAP(computerString, p)
10954     SWAP(features, p)
10955     SWAP(fenOverride, p)
10956     SWAP(NPS, h)
10957     SWAP(accumulateTC, h)
10958     SWAP(drawDepth, h)
10959     SWAP(host, p)
10960     SWAP(pseudo, h)
10961 }
10962
10963 int
10964 GetEngineLine (char *s, int n)
10965 {
10966     int i;
10967     char buf[MSG_SIZ];
10968     extern char *icsNames;
10969     if(!s || !*s) return 0;
10970     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10971     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10972     if(!mnemonic[i]) return 0;
10973     if(n == 11) return 1; // just testing if there was a match
10974     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10975     if(n == 1) SwapEngines(n);
10976     ParseArgsFromString(buf);
10977     if(n == 1) SwapEngines(n);
10978     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10979         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10980         ParseArgsFromString(buf);
10981     }
10982     return 1;
10983 }
10984
10985 int
10986 SetPlayer (int player, char *p)
10987 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10988     int i;
10989     char buf[MSG_SIZ], *engineName;
10990     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10991     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10992     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10993     if(mnemonic[i]) {
10994         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10995         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10996         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10997         ParseArgsFromString(buf);
10998     } else { // no engine with this nickname is installed!
10999         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11000         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11001         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11002         ModeHighlight();
11003         DisplayError(buf, 0);
11004         return 0;
11005     }
11006     free(engineName);
11007     return i;
11008 }
11009
11010 char *recentEngines;
11011
11012 void
11013 RecentEngineEvent (int nr)
11014 {
11015     int n;
11016 //    SwapEngines(1); // bump first to second
11017 //    ReplaceEngine(&second, 1); // and load it there
11018     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11019     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11020     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11021         ReplaceEngine(&first, 0);
11022         FloatToFront(&appData.recentEngineList, command[n]);
11023     }
11024 }
11025
11026 int
11027 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11028 {   // determine players from game number
11029     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11030
11031     if(appData.tourneyType == 0) {
11032         roundsPerCycle = (nPlayers - 1) | 1;
11033         pairingsPerRound = nPlayers / 2;
11034     } else if(appData.tourneyType > 0) {
11035         roundsPerCycle = nPlayers - appData.tourneyType;
11036         pairingsPerRound = appData.tourneyType;
11037     }
11038     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11039     gamesPerCycle = gamesPerRound * roundsPerCycle;
11040     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11041     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11042     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11043     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11044     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11045     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11046
11047     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11048     if(appData.roundSync) *syncInterval = gamesPerRound;
11049
11050     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11051
11052     if(appData.tourneyType == 0) {
11053         if(curPairing == (nPlayers-1)/2 ) {
11054             *whitePlayer = curRound;
11055             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11056         } else {
11057             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11058             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11059             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11060             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11061         }
11062     } else if(appData.tourneyType > 1) {
11063         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11064         *whitePlayer = curRound + appData.tourneyType;
11065     } else if(appData.tourneyType > 0) {
11066         *whitePlayer = curPairing;
11067         *blackPlayer = curRound + appData.tourneyType;
11068     }
11069
11070     // take care of white/black alternation per round.
11071     // For cycles and games this is already taken care of by default, derived from matchGame!
11072     return curRound & 1;
11073 }
11074
11075 int
11076 NextTourneyGame (int nr, int *swapColors)
11077 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11078     char *p, *q;
11079     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11080     FILE *tf;
11081     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11082     tf = fopen(appData.tourneyFile, "r");
11083     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11084     ParseArgsFromFile(tf); fclose(tf);
11085     InitTimeControls(); // TC might be altered from tourney file
11086
11087     nPlayers = CountPlayers(appData.participants); // count participants
11088     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11089     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11090
11091     if(syncInterval) {
11092         p = q = appData.results;
11093         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11094         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11095             DisplayMessage(_("Waiting for other game(s)"),"");
11096             waitingForGame = TRUE;
11097             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11098             return 0;
11099         }
11100         waitingForGame = FALSE;
11101     }
11102
11103     if(appData.tourneyType < 0) {
11104         if(nr>=0 && !pairingReceived) {
11105             char buf[1<<16];
11106             if(pairing.pr == NoProc) {
11107                 if(!appData.pairingEngine[0]) {
11108                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11109                     return 0;
11110                 }
11111                 StartChessProgram(&pairing); // starts the pairing engine
11112             }
11113             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11114             SendToProgram(buf, &pairing);
11115             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11116             SendToProgram(buf, &pairing);
11117             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11118         }
11119         pairingReceived = 0;                              // ... so we continue here
11120         *swapColors = 0;
11121         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11122         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11123         matchGame = 1; roundNr = nr / syncInterval + 1;
11124     }
11125
11126     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11127
11128     // redefine engines, engine dir, etc.
11129     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11130     if(first.pr == NoProc) {
11131       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11132       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11133     }
11134     if(second.pr == NoProc) {
11135       SwapEngines(1);
11136       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11137       SwapEngines(1);         // and make that valid for second engine by swapping
11138       InitEngine(&second, 1);
11139     }
11140     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11141     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11142     return OK;
11143 }
11144
11145 void
11146 NextMatchGame ()
11147 {   // performs game initialization that does not invoke engines, and then tries to start the game
11148     int res, firstWhite, swapColors = 0;
11149     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11150     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
11151         char buf[MSG_SIZ];
11152         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11153         if(strcmp(buf, currentDebugFile)) { // name has changed
11154             FILE *f = fopen(buf, "w");
11155             if(f) { // if opening the new file failed, just keep using the old one
11156                 ASSIGN(currentDebugFile, buf);
11157                 fclose(debugFP);
11158                 debugFP = f;
11159             }
11160             if(appData.serverFileName) {
11161                 if(serverFP) fclose(serverFP);
11162                 serverFP = fopen(appData.serverFileName, "w");
11163                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11164                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11165             }
11166         }
11167     }
11168     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11169     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11170     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11171     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11172     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11173     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11174     Reset(FALSE, first.pr != NoProc);
11175     res = LoadGameOrPosition(matchGame); // setup game
11176     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11177     if(!res) return; // abort when bad game/pos file
11178     TwoMachinesEvent();
11179 }
11180
11181 void
11182 UserAdjudicationEvent (int result)
11183 {
11184     ChessMove gameResult = GameIsDrawn;
11185
11186     if( result > 0 ) {
11187         gameResult = WhiteWins;
11188     }
11189     else if( result < 0 ) {
11190         gameResult = BlackWins;
11191     }
11192
11193     if( gameMode == TwoMachinesPlay ) {
11194         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11195     }
11196 }
11197
11198
11199 // [HGM] save: calculate checksum of game to make games easily identifiable
11200 int
11201 StringCheckSum (char *s)
11202 {
11203         int i = 0;
11204         if(s==NULL) return 0;
11205         while(*s) i = i*259 + *s++;
11206         return i;
11207 }
11208
11209 int
11210 GameCheckSum ()
11211 {
11212         int i, sum=0;
11213         for(i=backwardMostMove; i<forwardMostMove; i++) {
11214                 sum += pvInfoList[i].depth;
11215                 sum += StringCheckSum(parseList[i]);
11216                 sum += StringCheckSum(commentList[i]);
11217                 sum *= 261;
11218         }
11219         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11220         return sum + StringCheckSum(commentList[i]);
11221 } // end of save patch
11222
11223 void
11224 GameEnds (ChessMove result, char *resultDetails, int whosays)
11225 {
11226     GameMode nextGameMode;
11227     int isIcsGame;
11228     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11229
11230     if(endingGame) return; /* [HGM] crash: forbid recursion */
11231     endingGame = 1;
11232     if(twoBoards) { // [HGM] dual: switch back to one board
11233         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11234         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11235     }
11236     if (appData.debugMode) {
11237       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11238               result, resultDetails ? resultDetails : "(null)", whosays);
11239     }
11240
11241     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11242
11243     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11244
11245     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11246         /* If we are playing on ICS, the server decides when the
11247            game is over, but the engine can offer to draw, claim
11248            a draw, or resign.
11249          */
11250 #if ZIPPY
11251         if (appData.zippyPlay && first.initDone) {
11252             if (result == GameIsDrawn) {
11253                 /* In case draw still needs to be claimed */
11254                 SendToICS(ics_prefix);
11255                 SendToICS("draw\n");
11256             } else if (StrCaseStr(resultDetails, "resign")) {
11257                 SendToICS(ics_prefix);
11258                 SendToICS("resign\n");
11259             }
11260         }
11261 #endif
11262         endingGame = 0; /* [HGM] crash */
11263         return;
11264     }
11265
11266     /* If we're loading the game from a file, stop */
11267     if (whosays == GE_FILE) {
11268       (void) StopLoadGameTimer();
11269       gameFileFP = NULL;
11270     }
11271
11272     /* Cancel draw offers */
11273     first.offeredDraw = second.offeredDraw = 0;
11274
11275     /* If this is an ICS game, only ICS can really say it's done;
11276        if not, anyone can. */
11277     isIcsGame = (gameMode == IcsPlayingWhite ||
11278                  gameMode == IcsPlayingBlack ||
11279                  gameMode == IcsObserving    ||
11280                  gameMode == IcsExamining);
11281
11282     if (!isIcsGame || whosays == GE_ICS) {
11283         /* OK -- not an ICS game, or ICS said it was done */
11284         StopClocks();
11285         if (!isIcsGame && !appData.noChessProgram)
11286           SetUserThinkingEnables();
11287
11288         /* [HGM] if a machine claims the game end we verify this claim */
11289         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11290             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11291                 char claimer;
11292                 ChessMove trueResult = (ChessMove) -1;
11293
11294                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11295                                             first.twoMachinesColor[0] :
11296                                             second.twoMachinesColor[0] ;
11297
11298                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11299                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11300                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11301                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11302                 } else
11303                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11304                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11305                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11306                 } else
11307                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11308                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11309                 }
11310
11311                 // now verify win claims, but not in drop games, as we don't understand those yet
11312                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11313                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11314                     (result == WhiteWins && claimer == 'w' ||
11315                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11316                       if (appData.debugMode) {
11317                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11318                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11319                       }
11320                       if(result != trueResult) {
11321                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11322                               result = claimer == 'w' ? BlackWins : WhiteWins;
11323                               resultDetails = buf;
11324                       }
11325                 } else
11326                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11327                     && (forwardMostMove <= backwardMostMove ||
11328                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11329                         (claimer=='b')==(forwardMostMove&1))
11330                                                                                   ) {
11331                       /* [HGM] verify: draws that were not flagged are false claims */
11332                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11333                       result = claimer == 'w' ? BlackWins : WhiteWins;
11334                       resultDetails = buf;
11335                 }
11336                 /* (Claiming a loss is accepted no questions asked!) */
11337             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11338                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11339                 result = GameUnfinished;
11340                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11341             }
11342             /* [HGM] bare: don't allow bare King to win */
11343             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11344                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11345                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11346                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11347                && result != GameIsDrawn)
11348             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11349                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11350                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11351                         if(p >= 0 && p <= (int)WhiteKing) k++;
11352                 }
11353                 if (appData.debugMode) {
11354                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11355                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11356                 }
11357                 if(k <= 1) {
11358                         result = GameIsDrawn;
11359                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11360                         resultDetails = buf;
11361                 }
11362             }
11363         }
11364
11365
11366         if(serverMoves != NULL && !loadFlag) { char c = '=';
11367             if(result==WhiteWins) c = '+';
11368             if(result==BlackWins) c = '-';
11369             if(resultDetails != NULL)
11370                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11371         }
11372         if (resultDetails != NULL) {
11373             gameInfo.result = result;
11374             gameInfo.resultDetails = StrSave(resultDetails);
11375
11376             /* display last move only if game was not loaded from file */
11377             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11378                 DisplayMove(currentMove - 1);
11379
11380             if (forwardMostMove != 0) {
11381                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11382                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11383                                                                 ) {
11384                     if (*appData.saveGameFile != NULLCHAR) {
11385                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11386                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11387                         else
11388                         SaveGameToFile(appData.saveGameFile, TRUE);
11389                     } else if (appData.autoSaveGames) {
11390                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11391                     }
11392                     if (*appData.savePositionFile != NULLCHAR) {
11393                         SavePositionToFile(appData.savePositionFile);
11394                     }
11395                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11396                 }
11397             }
11398
11399             /* Tell program how game ended in case it is learning */
11400             /* [HGM] Moved this to after saving the PGN, just in case */
11401             /* engine died and we got here through time loss. In that */
11402             /* case we will get a fatal error writing the pipe, which */
11403             /* would otherwise lose us the PGN.                       */
11404             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11405             /* output during GameEnds should never be fatal anymore   */
11406             if (gameMode == MachinePlaysWhite ||
11407                 gameMode == MachinePlaysBlack ||
11408                 gameMode == TwoMachinesPlay ||
11409                 gameMode == IcsPlayingWhite ||
11410                 gameMode == IcsPlayingBlack ||
11411                 gameMode == BeginningOfGame) {
11412                 char buf[MSG_SIZ];
11413                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11414                         resultDetails);
11415                 if (first.pr != NoProc) {
11416                     SendToProgram(buf, &first);
11417                 }
11418                 if (second.pr != NoProc &&
11419                     gameMode == TwoMachinesPlay) {
11420                     SendToProgram(buf, &second);
11421                 }
11422             }
11423         }
11424
11425         if (appData.icsActive) {
11426             if (appData.quietPlay &&
11427                 (gameMode == IcsPlayingWhite ||
11428                  gameMode == IcsPlayingBlack)) {
11429                 SendToICS(ics_prefix);
11430                 SendToICS("set shout 1\n");
11431             }
11432             nextGameMode = IcsIdle;
11433             ics_user_moved = FALSE;
11434             /* clean up premove.  It's ugly when the game has ended and the
11435              * premove highlights are still on the board.
11436              */
11437             if (gotPremove) {
11438               gotPremove = FALSE;
11439               ClearPremoveHighlights();
11440               DrawPosition(FALSE, boards[currentMove]);
11441             }
11442             if (whosays == GE_ICS) {
11443                 switch (result) {
11444                 case WhiteWins:
11445                     if (gameMode == IcsPlayingWhite)
11446                         PlayIcsWinSound();
11447                     else if(gameMode == IcsPlayingBlack)
11448                         PlayIcsLossSound();
11449                     break;
11450                 case BlackWins:
11451                     if (gameMode == IcsPlayingBlack)
11452                         PlayIcsWinSound();
11453                     else if(gameMode == IcsPlayingWhite)
11454                         PlayIcsLossSound();
11455                     break;
11456                 case GameIsDrawn:
11457                     PlayIcsDrawSound();
11458                     break;
11459                 default:
11460                     PlayIcsUnfinishedSound();
11461                 }
11462             }
11463             if(appData.quitNext) { ExitEvent(0); return; }
11464         } else if (gameMode == EditGame ||
11465                    gameMode == PlayFromGameFile ||
11466                    gameMode == AnalyzeMode ||
11467                    gameMode == AnalyzeFile) {
11468             nextGameMode = gameMode;
11469         } else {
11470             nextGameMode = EndOfGame;
11471         }
11472         pausing = FALSE;
11473         ModeHighlight();
11474     } else {
11475         nextGameMode = gameMode;
11476     }
11477
11478     if (appData.noChessProgram) {
11479         gameMode = nextGameMode;
11480         ModeHighlight();
11481         endingGame = 0; /* [HGM] crash */
11482         return;
11483     }
11484
11485     if (first.reuse) {
11486         /* Put first chess program into idle state */
11487         if (first.pr != NoProc &&
11488             (gameMode == MachinePlaysWhite ||
11489              gameMode == MachinePlaysBlack ||
11490              gameMode == TwoMachinesPlay ||
11491              gameMode == IcsPlayingWhite ||
11492              gameMode == IcsPlayingBlack ||
11493              gameMode == BeginningOfGame)) {
11494             SendToProgram("force\n", &first);
11495             if (first.usePing) {
11496               char buf[MSG_SIZ];
11497               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11498               SendToProgram(buf, &first);
11499             }
11500         }
11501     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11502         /* Kill off first chess program */
11503         if (first.isr != NULL)
11504           RemoveInputSource(first.isr);
11505         first.isr = NULL;
11506
11507         if (first.pr != NoProc) {
11508             ExitAnalyzeMode();
11509             DoSleep( appData.delayBeforeQuit );
11510             SendToProgram("quit\n", &first);
11511             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11512             first.reload = TRUE;
11513         }
11514         first.pr = NoProc;
11515     }
11516     if (second.reuse) {
11517         /* Put second chess program into idle state */
11518         if (second.pr != NoProc &&
11519             gameMode == TwoMachinesPlay) {
11520             SendToProgram("force\n", &second);
11521             if (second.usePing) {
11522               char buf[MSG_SIZ];
11523               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11524               SendToProgram(buf, &second);
11525             }
11526         }
11527     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11528         /* Kill off second chess program */
11529         if (second.isr != NULL)
11530           RemoveInputSource(second.isr);
11531         second.isr = NULL;
11532
11533         if (second.pr != NoProc) {
11534             DoSleep( appData.delayBeforeQuit );
11535             SendToProgram("quit\n", &second);
11536             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11537             second.reload = TRUE;
11538         }
11539         second.pr = NoProc;
11540     }
11541
11542     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11543         char resChar = '=';
11544         switch (result) {
11545         case WhiteWins:
11546           resChar = '+';
11547           if (first.twoMachinesColor[0] == 'w') {
11548             first.matchWins++;
11549           } else {
11550             second.matchWins++;
11551           }
11552           break;
11553         case BlackWins:
11554           resChar = '-';
11555           if (first.twoMachinesColor[0] == 'b') {
11556             first.matchWins++;
11557           } else {
11558             second.matchWins++;
11559           }
11560           break;
11561         case GameUnfinished:
11562           resChar = ' ';
11563         default:
11564           break;
11565         }
11566
11567         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11568         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11569             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11570             ReserveGame(nextGame, resChar); // sets nextGame
11571             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11572             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11573         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11574
11575         if (nextGame <= appData.matchGames && !abortMatch) {
11576             gameMode = nextGameMode;
11577             matchGame = nextGame; // this will be overruled in tourney mode!
11578             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11579             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11580             endingGame = 0; /* [HGM] crash */
11581             return;
11582         } else {
11583             gameMode = nextGameMode;
11584             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11585                      first.tidy, second.tidy,
11586                      first.matchWins, second.matchWins,
11587                      appData.matchGames - (first.matchWins + second.matchWins));
11588             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11589             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11590             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11591             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11592                 first.twoMachinesColor = "black\n";
11593                 second.twoMachinesColor = "white\n";
11594             } else {
11595                 first.twoMachinesColor = "white\n";
11596                 second.twoMachinesColor = "black\n";
11597             }
11598         }
11599     }
11600     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11601         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11602       ExitAnalyzeMode();
11603     gameMode = nextGameMode;
11604     ModeHighlight();
11605     endingGame = 0;  /* [HGM] crash */
11606     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11607         if(matchMode == TRUE) { // match through command line: exit with or without popup
11608             if(ranking) {
11609                 ToNrEvent(forwardMostMove);
11610                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11611                 else ExitEvent(0);
11612             } else DisplayFatalError(buf, 0, 0);
11613         } else { // match through menu; just stop, with or without popup
11614             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11615             ModeHighlight();
11616             if(ranking){
11617                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11618             } else DisplayNote(buf);
11619       }
11620       if(ranking) free(ranking);
11621     }
11622 }
11623
11624 /* Assumes program was just initialized (initString sent).
11625    Leaves program in force mode. */
11626 void
11627 FeedMovesToProgram (ChessProgramState *cps, int upto)
11628 {
11629     int i;
11630
11631     if (appData.debugMode)
11632       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11633               startedFromSetupPosition ? "position and " : "",
11634               backwardMostMove, upto, cps->which);
11635     if(currentlyInitializedVariant != gameInfo.variant) {
11636       char buf[MSG_SIZ];
11637         // [HGM] variantswitch: make engine aware of new variant
11638         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11639                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11640                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11641         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11642         SendToProgram(buf, cps);
11643         currentlyInitializedVariant = gameInfo.variant;
11644     }
11645     SendToProgram("force\n", cps);
11646     if (startedFromSetupPosition) {
11647         SendBoard(cps, backwardMostMove);
11648     if (appData.debugMode) {
11649         fprintf(debugFP, "feedMoves\n");
11650     }
11651     }
11652     for (i = backwardMostMove; i < upto; i++) {
11653         SendMoveToProgram(i, cps);
11654     }
11655 }
11656
11657
11658 int
11659 ResurrectChessProgram ()
11660 {
11661      /* The chess program may have exited.
11662         If so, restart it and feed it all the moves made so far. */
11663     static int doInit = 0;
11664
11665     if (appData.noChessProgram) return 1;
11666
11667     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11668         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11669         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11670         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11671     } else {
11672         if (first.pr != NoProc) return 1;
11673         StartChessProgram(&first);
11674     }
11675     InitChessProgram(&first, FALSE);
11676     FeedMovesToProgram(&first, currentMove);
11677
11678     if (!first.sendTime) {
11679         /* can't tell gnuchess what its clock should read,
11680            so we bow to its notion. */
11681         ResetClocks();
11682         timeRemaining[0][currentMove] = whiteTimeRemaining;
11683         timeRemaining[1][currentMove] = blackTimeRemaining;
11684     }
11685
11686     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11687                 appData.icsEngineAnalyze) && first.analysisSupport) {
11688       SendToProgram("analyze\n", &first);
11689       first.analyzing = TRUE;
11690     }
11691     return 1;
11692 }
11693
11694 /*
11695  * Button procedures
11696  */
11697 void
11698 Reset (int redraw, int init)
11699 {
11700     int i;
11701
11702     if (appData.debugMode) {
11703         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11704                 redraw, init, gameMode);
11705     }
11706     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11707     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11708     CleanupTail(); // [HGM] vari: delete any stored variations
11709     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11710     pausing = pauseExamInvalid = FALSE;
11711     startedFromSetupPosition = blackPlaysFirst = FALSE;
11712     firstMove = TRUE;
11713     whiteFlag = blackFlag = FALSE;
11714     userOfferedDraw = FALSE;
11715     hintRequested = bookRequested = FALSE;
11716     first.maybeThinking = FALSE;
11717     second.maybeThinking = FALSE;
11718     first.bookSuspend = FALSE; // [HGM] book
11719     second.bookSuspend = FALSE;
11720     thinkOutput[0] = NULLCHAR;
11721     lastHint[0] = NULLCHAR;
11722     ClearGameInfo(&gameInfo);
11723     gameInfo.variant = StringToVariant(appData.variant);
11724     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11725     ics_user_moved = ics_clock_paused = FALSE;
11726     ics_getting_history = H_FALSE;
11727     ics_gamenum = -1;
11728     white_holding[0] = black_holding[0] = NULLCHAR;
11729     ClearProgramStats();
11730     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11731
11732     ResetFrontEnd();
11733     ClearHighlights();
11734     flipView = appData.flipView;
11735     ClearPremoveHighlights();
11736     gotPremove = FALSE;
11737     alarmSounded = FALSE;
11738     killX = killY = -1; // [HGM] lion
11739
11740     GameEnds(EndOfFile, NULL, GE_PLAYER);
11741     if(appData.serverMovesName != NULL) {
11742         /* [HGM] prepare to make moves file for broadcasting */
11743         clock_t t = clock();
11744         if(serverMoves != NULL) fclose(serverMoves);
11745         serverMoves = fopen(appData.serverMovesName, "r");
11746         if(serverMoves != NULL) {
11747             fclose(serverMoves);
11748             /* delay 15 sec before overwriting, so all clients can see end */
11749             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11750         }
11751         serverMoves = fopen(appData.serverMovesName, "w");
11752     }
11753
11754     ExitAnalyzeMode();
11755     gameMode = BeginningOfGame;
11756     ModeHighlight();
11757     if(appData.icsActive) gameInfo.variant = VariantNormal;
11758     currentMove = forwardMostMove = backwardMostMove = 0;
11759     MarkTargetSquares(1);
11760     InitPosition(redraw);
11761     for (i = 0; i < MAX_MOVES; i++) {
11762         if (commentList[i] != NULL) {
11763             free(commentList[i]);
11764             commentList[i] = NULL;
11765         }
11766     }
11767     ResetClocks();
11768     timeRemaining[0][0] = whiteTimeRemaining;
11769     timeRemaining[1][0] = blackTimeRemaining;
11770
11771     if (first.pr == NoProc) {
11772         StartChessProgram(&first);
11773     }
11774     if (init) {
11775             InitChessProgram(&first, startedFromSetupPosition);
11776     }
11777     DisplayTitle("");
11778     DisplayMessage("", "");
11779     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11780     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11781     ClearMap();        // [HGM] exclude: invalidate map
11782 }
11783
11784 void
11785 AutoPlayGameLoop ()
11786 {
11787     for (;;) {
11788         if (!AutoPlayOneMove())
11789           return;
11790         if (matchMode || appData.timeDelay == 0)
11791           continue;
11792         if (appData.timeDelay < 0)
11793           return;
11794         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11795         break;
11796     }
11797 }
11798
11799 void
11800 AnalyzeNextGame()
11801 {
11802     ReloadGame(1); // next game
11803 }
11804
11805 int
11806 AutoPlayOneMove ()
11807 {
11808     int fromX, fromY, toX, toY;
11809
11810     if (appData.debugMode) {
11811       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11812     }
11813
11814     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11815       return FALSE;
11816
11817     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11818       pvInfoList[currentMove].depth = programStats.depth;
11819       pvInfoList[currentMove].score = programStats.score;
11820       pvInfoList[currentMove].time  = 0;
11821       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11822       else { // append analysis of final position as comment
11823         char buf[MSG_SIZ];
11824         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11825         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11826       }
11827       programStats.depth = 0;
11828     }
11829
11830     if (currentMove >= forwardMostMove) {
11831       if(gameMode == AnalyzeFile) {
11832           if(appData.loadGameIndex == -1) {
11833             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11834           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11835           } else {
11836           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11837         }
11838       }
11839 //      gameMode = EndOfGame;
11840 //      ModeHighlight();
11841
11842       /* [AS] Clear current move marker at the end of a game */
11843       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11844
11845       return FALSE;
11846     }
11847
11848     toX = moveList[currentMove][2] - AAA;
11849     toY = moveList[currentMove][3] - ONE;
11850
11851     if (moveList[currentMove][1] == '@') {
11852         if (appData.highlightLastMove) {
11853             SetHighlights(-1, -1, toX, toY);
11854         }
11855     } else {
11856         int viaX = moveList[currentMove][5] - AAA;
11857         int viaY = moveList[currentMove][6] - ONE;
11858         fromX = moveList[currentMove][0] - AAA;
11859         fromY = moveList[currentMove][1] - ONE;
11860
11861         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11862
11863         if(moveList[currentMove][4] == ';') { // multi-leg
11864             ChessSquare piece = boards[currentMove][viaY][viaX];
11865             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11866             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11867             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11868             boards[currentMove][viaY][viaX] = piece;
11869         } else
11870         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11871
11872         if (appData.highlightLastMove) {
11873             SetHighlights(fromX, fromY, toX, toY);
11874         }
11875     }
11876     DisplayMove(currentMove);
11877     SendMoveToProgram(currentMove++, &first);
11878     DisplayBothClocks();
11879     DrawPosition(FALSE, boards[currentMove]);
11880     // [HGM] PV info: always display, routine tests if empty
11881     DisplayComment(currentMove - 1, commentList[currentMove]);
11882     return TRUE;
11883 }
11884
11885
11886 int
11887 LoadGameOneMove (ChessMove readAhead)
11888 {
11889     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11890     char promoChar = NULLCHAR;
11891     ChessMove moveType;
11892     char move[MSG_SIZ];
11893     char *p, *q;
11894
11895     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11896         gameMode != AnalyzeMode && gameMode != Training) {
11897         gameFileFP = NULL;
11898         return FALSE;
11899     }
11900
11901     yyboardindex = forwardMostMove;
11902     if (readAhead != EndOfFile) {
11903       moveType = readAhead;
11904     } else {
11905       if (gameFileFP == NULL)
11906           return FALSE;
11907       moveType = (ChessMove) Myylex();
11908     }
11909
11910     done = FALSE;
11911     switch (moveType) {
11912       case Comment:
11913         if (appData.debugMode)
11914           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11915         p = yy_text;
11916
11917         /* append the comment but don't display it */
11918         AppendComment(currentMove, p, FALSE);
11919         return TRUE;
11920
11921       case WhiteCapturesEnPassant:
11922       case BlackCapturesEnPassant:
11923       case WhitePromotion:
11924       case BlackPromotion:
11925       case WhiteNonPromotion:
11926       case BlackNonPromotion:
11927       case NormalMove:
11928       case FirstLeg:
11929       case WhiteKingSideCastle:
11930       case WhiteQueenSideCastle:
11931       case BlackKingSideCastle:
11932       case BlackQueenSideCastle:
11933       case WhiteKingSideCastleWild:
11934       case WhiteQueenSideCastleWild:
11935       case BlackKingSideCastleWild:
11936       case BlackQueenSideCastleWild:
11937       /* PUSH Fabien */
11938       case WhiteHSideCastleFR:
11939       case WhiteASideCastleFR:
11940       case BlackHSideCastleFR:
11941       case BlackASideCastleFR:
11942       /* POP Fabien */
11943         if (appData.debugMode)
11944           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11945         fromX = currentMoveString[0] - AAA;
11946         fromY = currentMoveString[1] - ONE;
11947         toX = currentMoveString[2] - AAA;
11948         toY = currentMoveString[3] - ONE;
11949         promoChar = currentMoveString[4];
11950         if(promoChar == ';') promoChar = NULLCHAR;
11951         break;
11952
11953       case WhiteDrop:
11954       case BlackDrop:
11955         if (appData.debugMode)
11956           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11957         fromX = moveType == WhiteDrop ?
11958           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11959         (int) CharToPiece(ToLower(currentMoveString[0]));
11960         fromY = DROP_RANK;
11961         toX = currentMoveString[2] - AAA;
11962         toY = currentMoveString[3] - ONE;
11963         break;
11964
11965       case WhiteWins:
11966       case BlackWins:
11967       case GameIsDrawn:
11968       case GameUnfinished:
11969         if (appData.debugMode)
11970           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11971         p = strchr(yy_text, '{');
11972         if (p == NULL) p = strchr(yy_text, '(');
11973         if (p == NULL) {
11974             p = yy_text;
11975             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11976         } else {
11977             q = strchr(p, *p == '{' ? '}' : ')');
11978             if (q != NULL) *q = NULLCHAR;
11979             p++;
11980         }
11981         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11982         GameEnds(moveType, p, GE_FILE);
11983         done = TRUE;
11984         if (cmailMsgLoaded) {
11985             ClearHighlights();
11986             flipView = WhiteOnMove(currentMove);
11987             if (moveType == GameUnfinished) flipView = !flipView;
11988             if (appData.debugMode)
11989               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11990         }
11991         break;
11992
11993       case EndOfFile:
11994         if (appData.debugMode)
11995           fprintf(debugFP, "Parser hit end of file\n");
11996         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11997           case MT_NONE:
11998           case MT_CHECK:
11999             break;
12000           case MT_CHECKMATE:
12001           case MT_STAINMATE:
12002             if (WhiteOnMove(currentMove)) {
12003                 GameEnds(BlackWins, "Black mates", GE_FILE);
12004             } else {
12005                 GameEnds(WhiteWins, "White mates", GE_FILE);
12006             }
12007             break;
12008           case MT_STALEMATE:
12009             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12010             break;
12011         }
12012         done = TRUE;
12013         break;
12014
12015       case MoveNumberOne:
12016         if (lastLoadGameStart == GNUChessGame) {
12017             /* GNUChessGames have numbers, but they aren't move numbers */
12018             if (appData.debugMode)
12019               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12020                       yy_text, (int) moveType);
12021             return LoadGameOneMove(EndOfFile); /* tail recursion */
12022         }
12023         /* else fall thru */
12024
12025       case XBoardGame:
12026       case GNUChessGame:
12027       case PGNTag:
12028         /* Reached start of next game in file */
12029         if (appData.debugMode)
12030           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12031         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12032           case MT_NONE:
12033           case MT_CHECK:
12034             break;
12035           case MT_CHECKMATE:
12036           case MT_STAINMATE:
12037             if (WhiteOnMove(currentMove)) {
12038                 GameEnds(BlackWins, "Black mates", GE_FILE);
12039             } else {
12040                 GameEnds(WhiteWins, "White mates", GE_FILE);
12041             }
12042             break;
12043           case MT_STALEMATE:
12044             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12045             break;
12046         }
12047         done = TRUE;
12048         break;
12049
12050       case PositionDiagram:     /* should not happen; ignore */
12051       case ElapsedTime:         /* ignore */
12052       case NAG:                 /* ignore */
12053         if (appData.debugMode)
12054           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12055                   yy_text, (int) moveType);
12056         return LoadGameOneMove(EndOfFile); /* tail recursion */
12057
12058       case IllegalMove:
12059         if (appData.testLegality) {
12060             if (appData.debugMode)
12061               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12062             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12063                     (forwardMostMove / 2) + 1,
12064                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12065             DisplayError(move, 0);
12066             done = TRUE;
12067         } else {
12068             if (appData.debugMode)
12069               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12070                       yy_text, currentMoveString);
12071             fromX = currentMoveString[0] - AAA;
12072             fromY = currentMoveString[1] - ONE;
12073             toX = currentMoveString[2] - AAA;
12074             toY = currentMoveString[3] - ONE;
12075             promoChar = currentMoveString[4];
12076         }
12077         break;
12078
12079       case AmbiguousMove:
12080         if (appData.debugMode)
12081           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12082         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12083                 (forwardMostMove / 2) + 1,
12084                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12085         DisplayError(move, 0);
12086         done = TRUE;
12087         break;
12088
12089       default:
12090       case ImpossibleMove:
12091         if (appData.debugMode)
12092           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12093         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12094                 (forwardMostMove / 2) + 1,
12095                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12096         DisplayError(move, 0);
12097         done = TRUE;
12098         break;
12099     }
12100
12101     if (done) {
12102         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12103             DrawPosition(FALSE, boards[currentMove]);
12104             DisplayBothClocks();
12105             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12106               DisplayComment(currentMove - 1, commentList[currentMove]);
12107         }
12108         (void) StopLoadGameTimer();
12109         gameFileFP = NULL;
12110         cmailOldMove = forwardMostMove;
12111         return FALSE;
12112     } else {
12113         /* currentMoveString is set as a side-effect of yylex */
12114
12115         thinkOutput[0] = NULLCHAR;
12116         MakeMove(fromX, fromY, toX, toY, promoChar);
12117         killX = killY = -1; // [HGM] lion: used up
12118         currentMove = forwardMostMove;
12119         return TRUE;
12120     }
12121 }
12122
12123 /* Load the nth game from the given file */
12124 int
12125 LoadGameFromFile (char *filename, int n, char *title, int useList)
12126 {
12127     FILE *f;
12128     char buf[MSG_SIZ];
12129
12130     if (strcmp(filename, "-") == 0) {
12131         f = stdin;
12132         title = "stdin";
12133     } else {
12134         f = fopen(filename, "rb");
12135         if (f == NULL) {
12136           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12137             DisplayError(buf, errno);
12138             return FALSE;
12139         }
12140     }
12141     if (fseek(f, 0, 0) == -1) {
12142         /* f is not seekable; probably a pipe */
12143         useList = FALSE;
12144     }
12145     if (useList && n == 0) {
12146         int error = GameListBuild(f);
12147         if (error) {
12148             DisplayError(_("Cannot build game list"), error);
12149         } else if (!ListEmpty(&gameList) &&
12150                    ((ListGame *) gameList.tailPred)->number > 1) {
12151             GameListPopUp(f, title);
12152             return TRUE;
12153         }
12154         GameListDestroy();
12155         n = 1;
12156     }
12157     if (n == 0) n = 1;
12158     return LoadGame(f, n, title, FALSE);
12159 }
12160
12161
12162 void
12163 MakeRegisteredMove ()
12164 {
12165     int fromX, fromY, toX, toY;
12166     char promoChar;
12167     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12168         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12169           case CMAIL_MOVE:
12170           case CMAIL_DRAW:
12171             if (appData.debugMode)
12172               fprintf(debugFP, "Restoring %s for game %d\n",
12173                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12174
12175             thinkOutput[0] = NULLCHAR;
12176             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12177             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12178             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12179             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12180             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12181             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12182             MakeMove(fromX, fromY, toX, toY, promoChar);
12183             ShowMove(fromX, fromY, toX, toY);
12184
12185             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12186               case MT_NONE:
12187               case MT_CHECK:
12188                 break;
12189
12190               case MT_CHECKMATE:
12191               case MT_STAINMATE:
12192                 if (WhiteOnMove(currentMove)) {
12193                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12194                 } else {
12195                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12196                 }
12197                 break;
12198
12199               case MT_STALEMATE:
12200                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12201                 break;
12202             }
12203
12204             break;
12205
12206           case CMAIL_RESIGN:
12207             if (WhiteOnMove(currentMove)) {
12208                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12209             } else {
12210                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12211             }
12212             break;
12213
12214           case CMAIL_ACCEPT:
12215             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12216             break;
12217
12218           default:
12219             break;
12220         }
12221     }
12222
12223     return;
12224 }
12225
12226 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12227 int
12228 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12229 {
12230     int retVal;
12231
12232     if (gameNumber > nCmailGames) {
12233         DisplayError(_("No more games in this message"), 0);
12234         return FALSE;
12235     }
12236     if (f == lastLoadGameFP) {
12237         int offset = gameNumber - lastLoadGameNumber;
12238         if (offset == 0) {
12239             cmailMsg[0] = NULLCHAR;
12240             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12241                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12242                 nCmailMovesRegistered--;
12243             }
12244             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12245             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12246                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12247             }
12248         } else {
12249             if (! RegisterMove()) return FALSE;
12250         }
12251     }
12252
12253     retVal = LoadGame(f, gameNumber, title, useList);
12254
12255     /* Make move registered during previous look at this game, if any */
12256     MakeRegisteredMove();
12257
12258     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12259         commentList[currentMove]
12260           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12261         DisplayComment(currentMove - 1, commentList[currentMove]);
12262     }
12263
12264     return retVal;
12265 }
12266
12267 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12268 int
12269 ReloadGame (int offset)
12270 {
12271     int gameNumber = lastLoadGameNumber + offset;
12272     if (lastLoadGameFP == NULL) {
12273         DisplayError(_("No game has been loaded yet"), 0);
12274         return FALSE;
12275     }
12276     if (gameNumber <= 0) {
12277         DisplayError(_("Can't back up any further"), 0);
12278         return FALSE;
12279     }
12280     if (cmailMsgLoaded) {
12281         return CmailLoadGame(lastLoadGameFP, gameNumber,
12282                              lastLoadGameTitle, lastLoadGameUseList);
12283     } else {
12284         return LoadGame(lastLoadGameFP, gameNumber,
12285                         lastLoadGameTitle, lastLoadGameUseList);
12286     }
12287 }
12288
12289 int keys[EmptySquare+1];
12290
12291 int
12292 PositionMatches (Board b1, Board b2)
12293 {
12294     int r, f, sum=0;
12295     switch(appData.searchMode) {
12296         case 1: return CompareWithRights(b1, b2);
12297         case 2:
12298             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12299                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12300             }
12301             return TRUE;
12302         case 3:
12303             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12304               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12305                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12306             }
12307             return sum==0;
12308         case 4:
12309             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12310                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12311             }
12312             return sum==0;
12313     }
12314     return TRUE;
12315 }
12316
12317 #define Q_PROMO  4
12318 #define Q_EP     3
12319 #define Q_BCASTL 2
12320 #define Q_WCASTL 1
12321
12322 int pieceList[256], quickBoard[256];
12323 ChessSquare pieceType[256] = { EmptySquare };
12324 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12325 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12326 int soughtTotal, turn;
12327 Boolean epOK, flipSearch;
12328
12329 typedef struct {
12330     unsigned char piece, to;
12331 } Move;
12332
12333 #define DSIZE (250000)
12334
12335 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12336 Move *moveDatabase = initialSpace;
12337 unsigned int movePtr, dataSize = DSIZE;
12338
12339 int
12340 MakePieceList (Board board, int *counts)
12341 {
12342     int r, f, n=Q_PROMO, total=0;
12343     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12344     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12345         int sq = f + (r<<4);
12346         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12347             quickBoard[sq] = ++n;
12348             pieceList[n] = sq;
12349             pieceType[n] = board[r][f];
12350             counts[board[r][f]]++;
12351             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12352             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12353             total++;
12354         }
12355     }
12356     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12357     return total;
12358 }
12359
12360 void
12361 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12362 {
12363     int sq = fromX + (fromY<<4);
12364     int piece = quickBoard[sq], rook;
12365     quickBoard[sq] = 0;
12366     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12367     if(piece == pieceList[1] && fromY == toY) {
12368       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12369         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12370         moveDatabase[movePtr++].piece = Q_WCASTL;
12371         quickBoard[sq] = piece;
12372         piece = quickBoard[from]; quickBoard[from] = 0;
12373         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12374       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12375         quickBoard[sq] = 0; // remove Rook
12376         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12377         moveDatabase[movePtr++].piece = Q_WCASTL;
12378         quickBoard[sq] = pieceList[1]; // put King
12379         piece = rook;
12380         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12381       }
12382     } else
12383     if(piece == pieceList[2] && fromY == toY) {
12384       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12385         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12386         moveDatabase[movePtr++].piece = Q_BCASTL;
12387         quickBoard[sq] = piece;
12388         piece = quickBoard[from]; quickBoard[from] = 0;
12389         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12390       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12391         quickBoard[sq] = 0; // remove Rook
12392         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12393         moveDatabase[movePtr++].piece = Q_BCASTL;
12394         quickBoard[sq] = pieceList[2]; // put King
12395         piece = rook;
12396         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12397       }
12398     } else
12399     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12400         quickBoard[(fromY<<4)+toX] = 0;
12401         moveDatabase[movePtr].piece = Q_EP;
12402         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12403         moveDatabase[movePtr].to = sq;
12404     } else
12405     if(promoPiece != pieceType[piece]) {
12406         moveDatabase[movePtr++].piece = Q_PROMO;
12407         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12408     }
12409     moveDatabase[movePtr].piece = piece;
12410     quickBoard[sq] = piece;
12411     movePtr++;
12412 }
12413
12414 int
12415 PackGame (Board board)
12416 {
12417     Move *newSpace = NULL;
12418     moveDatabase[movePtr].piece = 0; // terminate previous game
12419     if(movePtr > dataSize) {
12420         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12421         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12422         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12423         if(newSpace) {
12424             int i;
12425             Move *p = moveDatabase, *q = newSpace;
12426             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12427             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12428             moveDatabase = newSpace;
12429         } else { // calloc failed, we must be out of memory. Too bad...
12430             dataSize = 0; // prevent calloc events for all subsequent games
12431             return 0;     // and signal this one isn't cached
12432         }
12433     }
12434     movePtr++;
12435     MakePieceList(board, counts);
12436     return movePtr;
12437 }
12438
12439 int
12440 QuickCompare (Board board, int *minCounts, int *maxCounts)
12441 {   // compare according to search mode
12442     int r, f;
12443     switch(appData.searchMode)
12444     {
12445       case 1: // exact position match
12446         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12447         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12448             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12449         }
12450         break;
12451       case 2: // can have extra material on empty squares
12452         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12453             if(board[r][f] == EmptySquare) continue;
12454             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12455         }
12456         break;
12457       case 3: // material with exact Pawn structure
12458         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12459             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12460             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12461         } // fall through to material comparison
12462       case 4: // exact material
12463         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12464         break;
12465       case 6: // material range with given imbalance
12466         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12467         // fall through to range comparison
12468       case 5: // material range
12469         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12470     }
12471     return TRUE;
12472 }
12473
12474 int
12475 QuickScan (Board board, Move *move)
12476 {   // reconstruct game,and compare all positions in it
12477     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12478     do {
12479         int piece = move->piece;
12480         int to = move->to, from = pieceList[piece];
12481         if(found < 0) { // if already found just scan to game end for final piece count
12482           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12483            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12484            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12485                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12486             ) {
12487             static int lastCounts[EmptySquare+1];
12488             int i;
12489             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12490             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12491           } else stretch = 0;
12492           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12493           if(found >= 0 && !appData.minPieces) return found;
12494         }
12495         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12496           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12497           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12498             piece = (++move)->piece;
12499             from = pieceList[piece];
12500             counts[pieceType[piece]]--;
12501             pieceType[piece] = (ChessSquare) move->to;
12502             counts[move->to]++;
12503           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12504             counts[pieceType[quickBoard[to]]]--;
12505             quickBoard[to] = 0; total--;
12506             move++;
12507             continue;
12508           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12509             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12510             from  = pieceList[piece]; // so this must be King
12511             quickBoard[from] = 0;
12512             pieceList[piece] = to;
12513             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12514             quickBoard[from] = 0; // rook
12515             quickBoard[to] = piece;
12516             to = move->to; piece = move->piece;
12517             goto aftercastle;
12518           }
12519         }
12520         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12521         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12522         quickBoard[from] = 0;
12523       aftercastle:
12524         quickBoard[to] = piece;
12525         pieceList[piece] = to;
12526         cnt++; turn ^= 3;
12527         move++;
12528     } while(1);
12529 }
12530
12531 void
12532 InitSearch ()
12533 {
12534     int r, f;
12535     flipSearch = FALSE;
12536     CopyBoard(soughtBoard, boards[currentMove]);
12537     soughtTotal = MakePieceList(soughtBoard, maxSought);
12538     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12539     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12540     CopyBoard(reverseBoard, boards[currentMove]);
12541     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12542         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12543         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12544         reverseBoard[r][f] = piece;
12545     }
12546     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12547     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12548     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12549                  || (boards[currentMove][CASTLING][2] == NoRights ||
12550                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12551                  && (boards[currentMove][CASTLING][5] == NoRights ||
12552                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12553       ) {
12554         flipSearch = TRUE;
12555         CopyBoard(flipBoard, soughtBoard);
12556         CopyBoard(rotateBoard, reverseBoard);
12557         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12558             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12559             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12560         }
12561     }
12562     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12563     if(appData.searchMode >= 5) {
12564         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12565         MakePieceList(soughtBoard, minSought);
12566         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12567     }
12568     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12569         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12570 }
12571
12572 GameInfo dummyInfo;
12573 static int creatingBook;
12574
12575 int
12576 GameContainsPosition (FILE *f, ListGame *lg)
12577 {
12578     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12579     int fromX, fromY, toX, toY;
12580     char promoChar;
12581     static int initDone=FALSE;
12582
12583     // weed out games based on numerical tag comparison
12584     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12585     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12586     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12587     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12588     if(!initDone) {
12589         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12590         initDone = TRUE;
12591     }
12592     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12593     else CopyBoard(boards[scratch], initialPosition); // default start position
12594     if(lg->moves) {
12595         turn = btm + 1;
12596         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12597         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12598     }
12599     if(btm) plyNr++;
12600     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12601     fseek(f, lg->offset, 0);
12602     yynewfile(f);
12603     while(1) {
12604         yyboardindex = scratch;
12605         quickFlag = plyNr+1;
12606         next = Myylex();
12607         quickFlag = 0;
12608         switch(next) {
12609             case PGNTag:
12610                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12611             default:
12612                 continue;
12613
12614             case XBoardGame:
12615             case GNUChessGame:
12616                 if(plyNr) return -1; // after we have seen moves, this is for new game
12617               continue;
12618
12619             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12620             case ImpossibleMove:
12621             case WhiteWins: // game ends here with these four
12622             case BlackWins:
12623             case GameIsDrawn:
12624             case GameUnfinished:
12625                 return -1;
12626
12627             case IllegalMove:
12628                 if(appData.testLegality) return -1;
12629             case WhiteCapturesEnPassant:
12630             case BlackCapturesEnPassant:
12631             case WhitePromotion:
12632             case BlackPromotion:
12633             case WhiteNonPromotion:
12634             case BlackNonPromotion:
12635             case NormalMove:
12636             case FirstLeg:
12637             case WhiteKingSideCastle:
12638             case WhiteQueenSideCastle:
12639             case BlackKingSideCastle:
12640             case BlackQueenSideCastle:
12641             case WhiteKingSideCastleWild:
12642             case WhiteQueenSideCastleWild:
12643             case BlackKingSideCastleWild:
12644             case BlackQueenSideCastleWild:
12645             case WhiteHSideCastleFR:
12646             case WhiteASideCastleFR:
12647             case BlackHSideCastleFR:
12648             case BlackASideCastleFR:
12649                 fromX = currentMoveString[0] - AAA;
12650                 fromY = currentMoveString[1] - ONE;
12651                 toX = currentMoveString[2] - AAA;
12652                 toY = currentMoveString[3] - ONE;
12653                 promoChar = currentMoveString[4];
12654                 break;
12655             case WhiteDrop:
12656             case BlackDrop:
12657                 fromX = next == WhiteDrop ?
12658                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12659                   (int) CharToPiece(ToLower(currentMoveString[0]));
12660                 fromY = DROP_RANK;
12661                 toX = currentMoveString[2] - AAA;
12662                 toY = currentMoveString[3] - ONE;
12663                 promoChar = 0;
12664                 break;
12665         }
12666         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12667         plyNr++;
12668         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12669         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12670         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12671         if(appData.findMirror) {
12672             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12673             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12674         }
12675     }
12676 }
12677
12678 /* Load the nth game from open file f */
12679 int
12680 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12681 {
12682     ChessMove cm;
12683     char buf[MSG_SIZ];
12684     int gn = gameNumber;
12685     ListGame *lg = NULL;
12686     int numPGNTags = 0;
12687     int err, pos = -1;
12688     GameMode oldGameMode;
12689     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12690
12691     if (appData.debugMode)
12692         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12693
12694     if (gameMode == Training )
12695         SetTrainingModeOff();
12696
12697     oldGameMode = gameMode;
12698     if (gameMode != BeginningOfGame) {
12699       Reset(FALSE, TRUE);
12700     }
12701     killX = killY = -1; // [HGM] lion: in case we did not Reset
12702
12703     gameFileFP = f;
12704     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12705         fclose(lastLoadGameFP);
12706     }
12707
12708     if (useList) {
12709         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12710
12711         if (lg) {
12712             fseek(f, lg->offset, 0);
12713             GameListHighlight(gameNumber);
12714             pos = lg->position;
12715             gn = 1;
12716         }
12717         else {
12718             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12719               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12720             else
12721             DisplayError(_("Game number out of range"), 0);
12722             return FALSE;
12723         }
12724     } else {
12725         GameListDestroy();
12726         if (fseek(f, 0, 0) == -1) {
12727             if (f == lastLoadGameFP ?
12728                 gameNumber == lastLoadGameNumber + 1 :
12729                 gameNumber == 1) {
12730                 gn = 1;
12731             } else {
12732                 DisplayError(_("Can't seek on game file"), 0);
12733                 return FALSE;
12734             }
12735         }
12736     }
12737     lastLoadGameFP = f;
12738     lastLoadGameNumber = gameNumber;
12739     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12740     lastLoadGameUseList = useList;
12741
12742     yynewfile(f);
12743
12744     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12745       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12746                 lg->gameInfo.black);
12747             DisplayTitle(buf);
12748     } else if (*title != NULLCHAR) {
12749         if (gameNumber > 1) {
12750           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12751             DisplayTitle(buf);
12752         } else {
12753             DisplayTitle(title);
12754         }
12755     }
12756
12757     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12758         gameMode = PlayFromGameFile;
12759         ModeHighlight();
12760     }
12761
12762     currentMove = forwardMostMove = backwardMostMove = 0;
12763     CopyBoard(boards[0], initialPosition);
12764     StopClocks();
12765
12766     /*
12767      * Skip the first gn-1 games in the file.
12768      * Also skip over anything that precedes an identifiable
12769      * start of game marker, to avoid being confused by
12770      * garbage at the start of the file.  Currently
12771      * recognized start of game markers are the move number "1",
12772      * the pattern "gnuchess .* game", the pattern
12773      * "^[#;%] [^ ]* game file", and a PGN tag block.
12774      * A game that starts with one of the latter two patterns
12775      * will also have a move number 1, possibly
12776      * following a position diagram.
12777      * 5-4-02: Let's try being more lenient and allowing a game to
12778      * start with an unnumbered move.  Does that break anything?
12779      */
12780     cm = lastLoadGameStart = EndOfFile;
12781     while (gn > 0) {
12782         yyboardindex = forwardMostMove;
12783         cm = (ChessMove) Myylex();
12784         switch (cm) {
12785           case EndOfFile:
12786             if (cmailMsgLoaded) {
12787                 nCmailGames = CMAIL_MAX_GAMES - gn;
12788             } else {
12789                 Reset(TRUE, TRUE);
12790                 DisplayError(_("Game not found in file"), 0);
12791             }
12792             return FALSE;
12793
12794           case GNUChessGame:
12795           case XBoardGame:
12796             gn--;
12797             lastLoadGameStart = cm;
12798             break;
12799
12800           case MoveNumberOne:
12801             switch (lastLoadGameStart) {
12802               case GNUChessGame:
12803               case XBoardGame:
12804               case PGNTag:
12805                 break;
12806               case MoveNumberOne:
12807               case EndOfFile:
12808                 gn--;           /* count this game */
12809                 lastLoadGameStart = cm;
12810                 break;
12811               default:
12812                 /* impossible */
12813                 break;
12814             }
12815             break;
12816
12817           case PGNTag:
12818             switch (lastLoadGameStart) {
12819               case GNUChessGame:
12820               case PGNTag:
12821               case MoveNumberOne:
12822               case EndOfFile:
12823                 gn--;           /* count this game */
12824                 lastLoadGameStart = cm;
12825                 break;
12826               case XBoardGame:
12827                 lastLoadGameStart = cm; /* game counted already */
12828                 break;
12829               default:
12830                 /* impossible */
12831                 break;
12832             }
12833             if (gn > 0) {
12834                 do {
12835                     yyboardindex = forwardMostMove;
12836                     cm = (ChessMove) Myylex();
12837                 } while (cm == PGNTag || cm == Comment);
12838             }
12839             break;
12840
12841           case WhiteWins:
12842           case BlackWins:
12843           case GameIsDrawn:
12844             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12845                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12846                     != CMAIL_OLD_RESULT) {
12847                     nCmailResults ++ ;
12848                     cmailResult[  CMAIL_MAX_GAMES
12849                                 - gn - 1] = CMAIL_OLD_RESULT;
12850                 }
12851             }
12852             break;
12853
12854           case NormalMove:
12855           case FirstLeg:
12856             /* Only a NormalMove can be at the start of a game
12857              * without a position diagram. */
12858             if (lastLoadGameStart == EndOfFile ) {
12859               gn--;
12860               lastLoadGameStart = MoveNumberOne;
12861             }
12862             break;
12863
12864           default:
12865             break;
12866         }
12867     }
12868
12869     if (appData.debugMode)
12870       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12871
12872     if (cm == XBoardGame) {
12873         /* Skip any header junk before position diagram and/or move 1 */
12874         for (;;) {
12875             yyboardindex = forwardMostMove;
12876             cm = (ChessMove) Myylex();
12877
12878             if (cm == EndOfFile ||
12879                 cm == GNUChessGame || cm == XBoardGame) {
12880                 /* Empty game; pretend end-of-file and handle later */
12881                 cm = EndOfFile;
12882                 break;
12883             }
12884
12885             if (cm == MoveNumberOne || cm == PositionDiagram ||
12886                 cm == PGNTag || cm == Comment)
12887               break;
12888         }
12889     } else if (cm == GNUChessGame) {
12890         if (gameInfo.event != NULL) {
12891             free(gameInfo.event);
12892         }
12893         gameInfo.event = StrSave(yy_text);
12894     }
12895
12896     startedFromSetupPosition = FALSE;
12897     while (cm == PGNTag) {
12898         if (appData.debugMode)
12899           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12900         err = ParsePGNTag(yy_text, &gameInfo);
12901         if (!err) numPGNTags++;
12902
12903         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12904         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12905             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12906             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12907             InitPosition(TRUE);
12908             oldVariant = gameInfo.variant;
12909             if (appData.debugMode)
12910               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12911         }
12912
12913
12914         if (gameInfo.fen != NULL) {
12915           Board initial_position;
12916           startedFromSetupPosition = TRUE;
12917           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12918             Reset(TRUE, TRUE);
12919             DisplayError(_("Bad FEN position in file"), 0);
12920             return FALSE;
12921           }
12922           CopyBoard(boards[0], initial_position);
12923           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12924             CopyBoard(initialPosition, initial_position);
12925           if (blackPlaysFirst) {
12926             currentMove = forwardMostMove = backwardMostMove = 1;
12927             CopyBoard(boards[1], initial_position);
12928             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12929             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12930             timeRemaining[0][1] = whiteTimeRemaining;
12931             timeRemaining[1][1] = blackTimeRemaining;
12932             if (commentList[0] != NULL) {
12933               commentList[1] = commentList[0];
12934               commentList[0] = NULL;
12935             }
12936           } else {
12937             currentMove = forwardMostMove = backwardMostMove = 0;
12938           }
12939           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12940           {   int i;
12941               initialRulePlies = FENrulePlies;
12942               for( i=0; i< nrCastlingRights; i++ )
12943                   initialRights[i] = initial_position[CASTLING][i];
12944           }
12945           yyboardindex = forwardMostMove;
12946           free(gameInfo.fen);
12947           gameInfo.fen = NULL;
12948         }
12949
12950         yyboardindex = forwardMostMove;
12951         cm = (ChessMove) Myylex();
12952
12953         /* Handle comments interspersed among the tags */
12954         while (cm == Comment) {
12955             char *p;
12956             if (appData.debugMode)
12957               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12958             p = yy_text;
12959             AppendComment(currentMove, p, FALSE);
12960             yyboardindex = forwardMostMove;
12961             cm = (ChessMove) Myylex();
12962         }
12963     }
12964
12965     /* don't rely on existence of Event tag since if game was
12966      * pasted from clipboard the Event tag may not exist
12967      */
12968     if (numPGNTags > 0){
12969         char *tags;
12970         if (gameInfo.variant == VariantNormal) {
12971           VariantClass v = StringToVariant(gameInfo.event);
12972           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12973           if(v < VariantShogi) gameInfo.variant = v;
12974         }
12975         if (!matchMode) {
12976           if( appData.autoDisplayTags ) {
12977             tags = PGNTags(&gameInfo);
12978             TagsPopUp(tags, CmailMsg());
12979             free(tags);
12980           }
12981         }
12982     } else {
12983         /* Make something up, but don't display it now */
12984         SetGameInfo();
12985         TagsPopDown();
12986     }
12987
12988     if (cm == PositionDiagram) {
12989         int i, j;
12990         char *p;
12991         Board initial_position;
12992
12993         if (appData.debugMode)
12994           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12995
12996         if (!startedFromSetupPosition) {
12997             p = yy_text;
12998             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12999               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13000                 switch (*p) {
13001                   case '{':
13002                   case '[':
13003                   case '-':
13004                   case ' ':
13005                   case '\t':
13006                   case '\n':
13007                   case '\r':
13008                     break;
13009                   default:
13010                     initial_position[i][j++] = CharToPiece(*p);
13011                     break;
13012                 }
13013             while (*p == ' ' || *p == '\t' ||
13014                    *p == '\n' || *p == '\r') p++;
13015
13016             if (strncmp(p, "black", strlen("black"))==0)
13017               blackPlaysFirst = TRUE;
13018             else
13019               blackPlaysFirst = FALSE;
13020             startedFromSetupPosition = TRUE;
13021
13022             CopyBoard(boards[0], initial_position);
13023             if (blackPlaysFirst) {
13024                 currentMove = forwardMostMove = backwardMostMove = 1;
13025                 CopyBoard(boards[1], initial_position);
13026                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13027                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13028                 timeRemaining[0][1] = whiteTimeRemaining;
13029                 timeRemaining[1][1] = blackTimeRemaining;
13030                 if (commentList[0] != NULL) {
13031                     commentList[1] = commentList[0];
13032                     commentList[0] = NULL;
13033                 }
13034             } else {
13035                 currentMove = forwardMostMove = backwardMostMove = 0;
13036             }
13037         }
13038         yyboardindex = forwardMostMove;
13039         cm = (ChessMove) Myylex();
13040     }
13041
13042   if(!creatingBook) {
13043     if (first.pr == NoProc) {
13044         StartChessProgram(&first);
13045     }
13046     InitChessProgram(&first, FALSE);
13047     SendToProgram("force\n", &first);
13048     if (startedFromSetupPosition) {
13049         SendBoard(&first, forwardMostMove);
13050     if (appData.debugMode) {
13051         fprintf(debugFP, "Load Game\n");
13052     }
13053         DisplayBothClocks();
13054     }
13055   }
13056
13057     /* [HGM] server: flag to write setup moves in broadcast file as one */
13058     loadFlag = appData.suppressLoadMoves;
13059
13060     while (cm == Comment) {
13061         char *p;
13062         if (appData.debugMode)
13063           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13064         p = yy_text;
13065         AppendComment(currentMove, p, FALSE);
13066         yyboardindex = forwardMostMove;
13067         cm = (ChessMove) Myylex();
13068     }
13069
13070     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13071         cm == WhiteWins || cm == BlackWins ||
13072         cm == GameIsDrawn || cm == GameUnfinished) {
13073         DisplayMessage("", _("No moves in game"));
13074         if (cmailMsgLoaded) {
13075             if (appData.debugMode)
13076               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13077             ClearHighlights();
13078             flipView = FALSE;
13079         }
13080         DrawPosition(FALSE, boards[currentMove]);
13081         DisplayBothClocks();
13082         gameMode = EditGame;
13083         ModeHighlight();
13084         gameFileFP = NULL;
13085         cmailOldMove = 0;
13086         return TRUE;
13087     }
13088
13089     // [HGM] PV info: routine tests if comment empty
13090     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13091         DisplayComment(currentMove - 1, commentList[currentMove]);
13092     }
13093     if (!matchMode && appData.timeDelay != 0)
13094       DrawPosition(FALSE, boards[currentMove]);
13095
13096     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13097       programStats.ok_to_send = 1;
13098     }
13099
13100     /* if the first token after the PGN tags is a move
13101      * and not move number 1, retrieve it from the parser
13102      */
13103     if (cm != MoveNumberOne)
13104         LoadGameOneMove(cm);
13105
13106     /* load the remaining moves from the file */
13107     while (LoadGameOneMove(EndOfFile)) {
13108       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13109       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13110     }
13111
13112     /* rewind to the start of the game */
13113     currentMove = backwardMostMove;
13114
13115     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13116
13117     if (oldGameMode == AnalyzeFile) {
13118       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13119       AnalyzeFileEvent();
13120     } else
13121     if (oldGameMode == AnalyzeMode) {
13122       AnalyzeFileEvent();
13123     }
13124
13125     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13126         long int w, b; // [HGM] adjourn: restore saved clock times
13127         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13128         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13129             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13130             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13131         }
13132     }
13133
13134     if(creatingBook) return TRUE;
13135     if (!matchMode && pos > 0) {
13136         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13137     } else
13138     if (matchMode || appData.timeDelay == 0) {
13139       ToEndEvent();
13140     } else if (appData.timeDelay > 0) {
13141       AutoPlayGameLoop();
13142     }
13143
13144     if (appData.debugMode)
13145         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13146
13147     loadFlag = 0; /* [HGM] true game starts */
13148     return TRUE;
13149 }
13150
13151 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13152 int
13153 ReloadPosition (int offset)
13154 {
13155     int positionNumber = lastLoadPositionNumber + offset;
13156     if (lastLoadPositionFP == NULL) {
13157         DisplayError(_("No position has been loaded yet"), 0);
13158         return FALSE;
13159     }
13160     if (positionNumber <= 0) {
13161         DisplayError(_("Can't back up any further"), 0);
13162         return FALSE;
13163     }
13164     return LoadPosition(lastLoadPositionFP, positionNumber,
13165                         lastLoadPositionTitle);
13166 }
13167
13168 /* Load the nth position from the given file */
13169 int
13170 LoadPositionFromFile (char *filename, int n, char *title)
13171 {
13172     FILE *f;
13173     char buf[MSG_SIZ];
13174
13175     if (strcmp(filename, "-") == 0) {
13176         return LoadPosition(stdin, n, "stdin");
13177     } else {
13178         f = fopen(filename, "rb");
13179         if (f == NULL) {
13180             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13181             DisplayError(buf, errno);
13182             return FALSE;
13183         } else {
13184             return LoadPosition(f, n, title);
13185         }
13186     }
13187 }
13188
13189 /* Load the nth position from the given open file, and close it */
13190 int
13191 LoadPosition (FILE *f, int positionNumber, char *title)
13192 {
13193     char *p, line[MSG_SIZ];
13194     Board initial_position;
13195     int i, j, fenMode, pn;
13196
13197     if (gameMode == Training )
13198         SetTrainingModeOff();
13199
13200     if (gameMode != BeginningOfGame) {
13201         Reset(FALSE, TRUE);
13202     }
13203     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13204         fclose(lastLoadPositionFP);
13205     }
13206     if (positionNumber == 0) positionNumber = 1;
13207     lastLoadPositionFP = f;
13208     lastLoadPositionNumber = positionNumber;
13209     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13210     if (first.pr == NoProc && !appData.noChessProgram) {
13211       StartChessProgram(&first);
13212       InitChessProgram(&first, FALSE);
13213     }
13214     pn = positionNumber;
13215     if (positionNumber < 0) {
13216         /* Negative position number means to seek to that byte offset */
13217         if (fseek(f, -positionNumber, 0) == -1) {
13218             DisplayError(_("Can't seek on position file"), 0);
13219             return FALSE;
13220         };
13221         pn = 1;
13222     } else {
13223         if (fseek(f, 0, 0) == -1) {
13224             if (f == lastLoadPositionFP ?
13225                 positionNumber == lastLoadPositionNumber + 1 :
13226                 positionNumber == 1) {
13227                 pn = 1;
13228             } else {
13229                 DisplayError(_("Can't seek on position file"), 0);
13230                 return FALSE;
13231             }
13232         }
13233     }
13234     /* See if this file is FEN or old-style xboard */
13235     if (fgets(line, MSG_SIZ, f) == NULL) {
13236         DisplayError(_("Position not found in file"), 0);
13237         return FALSE;
13238     }
13239     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13240     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13241
13242     if (pn >= 2) {
13243         if (fenMode || line[0] == '#') pn--;
13244         while (pn > 0) {
13245             /* skip positions before number pn */
13246             if (fgets(line, MSG_SIZ, f) == NULL) {
13247                 Reset(TRUE, TRUE);
13248                 DisplayError(_("Position not found in file"), 0);
13249                 return FALSE;
13250             }
13251             if (fenMode || line[0] == '#') pn--;
13252         }
13253     }
13254
13255     if (fenMode) {
13256         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13257             DisplayError(_("Bad FEN position in file"), 0);
13258             return FALSE;
13259         }
13260     } else {
13261         (void) fgets(line, MSG_SIZ, f);
13262         (void) fgets(line, MSG_SIZ, f);
13263
13264         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13265             (void) fgets(line, MSG_SIZ, f);
13266             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13267                 if (*p == ' ')
13268                   continue;
13269                 initial_position[i][j++] = CharToPiece(*p);
13270             }
13271         }
13272
13273         blackPlaysFirst = FALSE;
13274         if (!feof(f)) {
13275             (void) fgets(line, MSG_SIZ, f);
13276             if (strncmp(line, "black", strlen("black"))==0)
13277               blackPlaysFirst = TRUE;
13278         }
13279     }
13280     startedFromSetupPosition = TRUE;
13281
13282     CopyBoard(boards[0], initial_position);
13283     if (blackPlaysFirst) {
13284         currentMove = forwardMostMove = backwardMostMove = 1;
13285         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13286         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13287         CopyBoard(boards[1], initial_position);
13288         DisplayMessage("", _("Black to play"));
13289     } else {
13290         currentMove = forwardMostMove = backwardMostMove = 0;
13291         DisplayMessage("", _("White to play"));
13292     }
13293     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13294     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13295         SendToProgram("force\n", &first);
13296         SendBoard(&first, forwardMostMove);
13297     }
13298     if (appData.debugMode) {
13299 int i, j;
13300   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13301   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13302         fprintf(debugFP, "Load Position\n");
13303     }
13304
13305     if (positionNumber > 1) {
13306       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13307         DisplayTitle(line);
13308     } else {
13309         DisplayTitle(title);
13310     }
13311     gameMode = EditGame;
13312     ModeHighlight();
13313     ResetClocks();
13314     timeRemaining[0][1] = whiteTimeRemaining;
13315     timeRemaining[1][1] = blackTimeRemaining;
13316     DrawPosition(FALSE, boards[currentMove]);
13317
13318     return TRUE;
13319 }
13320
13321
13322 void
13323 CopyPlayerNameIntoFileName (char **dest, char *src)
13324 {
13325     while (*src != NULLCHAR && *src != ',') {
13326         if (*src == ' ') {
13327             *(*dest)++ = '_';
13328             src++;
13329         } else {
13330             *(*dest)++ = *src++;
13331         }
13332     }
13333 }
13334
13335 char *
13336 DefaultFileName (char *ext)
13337 {
13338     static char def[MSG_SIZ];
13339     char *p;
13340
13341     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13342         p = def;
13343         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13344         *p++ = '-';
13345         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13346         *p++ = '.';
13347         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13348     } else {
13349         def[0] = NULLCHAR;
13350     }
13351     return def;
13352 }
13353
13354 /* Save the current game to the given file */
13355 int
13356 SaveGameToFile (char *filename, int append)
13357 {
13358     FILE *f;
13359     char buf[MSG_SIZ];
13360     int result, i, t,tot=0;
13361
13362     if (strcmp(filename, "-") == 0) {
13363         return SaveGame(stdout, 0, NULL);
13364     } else {
13365         for(i=0; i<10; i++) { // upto 10 tries
13366              f = fopen(filename, append ? "a" : "w");
13367              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13368              if(f || errno != 13) break;
13369              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13370              tot += t;
13371         }
13372         if (f == NULL) {
13373             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13374             DisplayError(buf, errno);
13375             return FALSE;
13376         } else {
13377             safeStrCpy(buf, lastMsg, MSG_SIZ);
13378             DisplayMessage(_("Waiting for access to save file"), "");
13379             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13380             DisplayMessage(_("Saving game"), "");
13381             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13382             result = SaveGame(f, 0, NULL);
13383             DisplayMessage(buf, "");
13384             return result;
13385         }
13386     }
13387 }
13388
13389 char *
13390 SavePart (char *str)
13391 {
13392     static char buf[MSG_SIZ];
13393     char *p;
13394
13395     p = strchr(str, ' ');
13396     if (p == NULL) return str;
13397     strncpy(buf, str, p - str);
13398     buf[p - str] = NULLCHAR;
13399     return buf;
13400 }
13401
13402 #define PGN_MAX_LINE 75
13403
13404 #define PGN_SIDE_WHITE  0
13405 #define PGN_SIDE_BLACK  1
13406
13407 static int
13408 FindFirstMoveOutOfBook (int side)
13409 {
13410     int result = -1;
13411
13412     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13413         int index = backwardMostMove;
13414         int has_book_hit = 0;
13415
13416         if( (index % 2) != side ) {
13417             index++;
13418         }
13419
13420         while( index < forwardMostMove ) {
13421             /* Check to see if engine is in book */
13422             int depth = pvInfoList[index].depth;
13423             int score = pvInfoList[index].score;
13424             int in_book = 0;
13425
13426             if( depth <= 2 ) {
13427                 in_book = 1;
13428             }
13429             else if( score == 0 && depth == 63 ) {
13430                 in_book = 1; /* Zappa */
13431             }
13432             else if( score == 2 && depth == 99 ) {
13433                 in_book = 1; /* Abrok */
13434             }
13435
13436             has_book_hit += in_book;
13437
13438             if( ! in_book ) {
13439                 result = index;
13440
13441                 break;
13442             }
13443
13444             index += 2;
13445         }
13446     }
13447
13448     return result;
13449 }
13450
13451 void
13452 GetOutOfBookInfo (char * buf)
13453 {
13454     int oob[2];
13455     int i;
13456     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13457
13458     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13459     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13460
13461     *buf = '\0';
13462
13463     if( oob[0] >= 0 || oob[1] >= 0 ) {
13464         for( i=0; i<2; i++ ) {
13465             int idx = oob[i];
13466
13467             if( idx >= 0 ) {
13468                 if( i > 0 && oob[0] >= 0 ) {
13469                     strcat( buf, "   " );
13470                 }
13471
13472                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13473                 sprintf( buf+strlen(buf), "%s%.2f",
13474                     pvInfoList[idx].score >= 0 ? "+" : "",
13475                     pvInfoList[idx].score / 100.0 );
13476             }
13477         }
13478     }
13479 }
13480
13481 /* Save game in PGN style */
13482 static void
13483 SaveGamePGN2 (FILE *f)
13484 {
13485     int i, offset, linelen, newblock;
13486 //    char *movetext;
13487     char numtext[32];
13488     int movelen, numlen, blank;
13489     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13490
13491     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13492
13493     PrintPGNTags(f, &gameInfo);
13494
13495     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13496
13497     if (backwardMostMove > 0 || startedFromSetupPosition) {
13498         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13499         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13500         fprintf(f, "\n{--------------\n");
13501         PrintPosition(f, backwardMostMove);
13502         fprintf(f, "--------------}\n");
13503         free(fen);
13504     }
13505     else {
13506         /* [AS] Out of book annotation */
13507         if( appData.saveOutOfBookInfo ) {
13508             char buf[64];
13509
13510             GetOutOfBookInfo( buf );
13511
13512             if( buf[0] != '\0' ) {
13513                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13514             }
13515         }
13516
13517         fprintf(f, "\n");
13518     }
13519
13520     i = backwardMostMove;
13521     linelen = 0;
13522     newblock = TRUE;
13523
13524     while (i < forwardMostMove) {
13525         /* Print comments preceding this move */
13526         if (commentList[i] != NULL) {
13527             if (linelen > 0) fprintf(f, "\n");
13528             fprintf(f, "%s", commentList[i]);
13529             linelen = 0;
13530             newblock = TRUE;
13531         }
13532
13533         /* Format move number */
13534         if ((i % 2) == 0)
13535           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13536         else
13537           if (newblock)
13538             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13539           else
13540             numtext[0] = NULLCHAR;
13541
13542         numlen = strlen(numtext);
13543         newblock = FALSE;
13544
13545         /* Print move number */
13546         blank = linelen > 0 && numlen > 0;
13547         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13548             fprintf(f, "\n");
13549             linelen = 0;
13550             blank = 0;
13551         }
13552         if (blank) {
13553             fprintf(f, " ");
13554             linelen++;
13555         }
13556         fprintf(f, "%s", numtext);
13557         linelen += numlen;
13558
13559         /* Get move */
13560         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13561         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13562
13563         /* Print move */
13564         blank = linelen > 0 && movelen > 0;
13565         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13566             fprintf(f, "\n");
13567             linelen = 0;
13568             blank = 0;
13569         }
13570         if (blank) {
13571             fprintf(f, " ");
13572             linelen++;
13573         }
13574         fprintf(f, "%s", move_buffer);
13575         linelen += movelen;
13576
13577         /* [AS] Add PV info if present */
13578         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13579             /* [HGM] add time */
13580             char buf[MSG_SIZ]; int seconds;
13581
13582             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13583
13584             if( seconds <= 0)
13585               buf[0] = 0;
13586             else
13587               if( seconds < 30 )
13588                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13589               else
13590                 {
13591                   seconds = (seconds + 4)/10; // round to full seconds
13592                   if( seconds < 60 )
13593                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13594                   else
13595                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13596                 }
13597
13598             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13599                       pvInfoList[i].score >= 0 ? "+" : "",
13600                       pvInfoList[i].score / 100.0,
13601                       pvInfoList[i].depth,
13602                       buf );
13603
13604             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13605
13606             /* Print score/depth */
13607             blank = linelen > 0 && movelen > 0;
13608             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13609                 fprintf(f, "\n");
13610                 linelen = 0;
13611                 blank = 0;
13612             }
13613             if (blank) {
13614                 fprintf(f, " ");
13615                 linelen++;
13616             }
13617             fprintf(f, "%s", move_buffer);
13618             linelen += movelen;
13619         }
13620
13621         i++;
13622     }
13623
13624     /* Start a new line */
13625     if (linelen > 0) fprintf(f, "\n");
13626
13627     /* Print comments after last move */
13628     if (commentList[i] != NULL) {
13629         fprintf(f, "%s\n", commentList[i]);
13630     }
13631
13632     /* Print result */
13633     if (gameInfo.resultDetails != NULL &&
13634         gameInfo.resultDetails[0] != NULLCHAR) {
13635         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13636         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13637            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13638             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13639         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13640     } else {
13641         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13642     }
13643 }
13644
13645 /* Save game in PGN style and close the file */
13646 int
13647 SaveGamePGN (FILE *f)
13648 {
13649     SaveGamePGN2(f);
13650     fclose(f);
13651     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13652     return TRUE;
13653 }
13654
13655 /* Save game in old style and close the file */
13656 int
13657 SaveGameOldStyle (FILE *f)
13658 {
13659     int i, offset;
13660     time_t tm;
13661
13662     tm = time((time_t *) NULL);
13663
13664     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13665     PrintOpponents(f);
13666
13667     if (backwardMostMove > 0 || startedFromSetupPosition) {
13668         fprintf(f, "\n[--------------\n");
13669         PrintPosition(f, backwardMostMove);
13670         fprintf(f, "--------------]\n");
13671     } else {
13672         fprintf(f, "\n");
13673     }
13674
13675     i = backwardMostMove;
13676     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13677
13678     while (i < forwardMostMove) {
13679         if (commentList[i] != NULL) {
13680             fprintf(f, "[%s]\n", commentList[i]);
13681         }
13682
13683         if ((i % 2) == 1) {
13684             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13685             i++;
13686         } else {
13687             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13688             i++;
13689             if (commentList[i] != NULL) {
13690                 fprintf(f, "\n");
13691                 continue;
13692             }
13693             if (i >= forwardMostMove) {
13694                 fprintf(f, "\n");
13695                 break;
13696             }
13697             fprintf(f, "%s\n", parseList[i]);
13698             i++;
13699         }
13700     }
13701
13702     if (commentList[i] != NULL) {
13703         fprintf(f, "[%s]\n", commentList[i]);
13704     }
13705
13706     /* This isn't really the old style, but it's close enough */
13707     if (gameInfo.resultDetails != NULL &&
13708         gameInfo.resultDetails[0] != NULLCHAR) {
13709         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13710                 gameInfo.resultDetails);
13711     } else {
13712         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13713     }
13714
13715     fclose(f);
13716     return TRUE;
13717 }
13718
13719 /* Save the current game to open file f and close the file */
13720 int
13721 SaveGame (FILE *f, int dummy, char *dummy2)
13722 {
13723     if (gameMode == EditPosition) EditPositionDone(TRUE);
13724     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13725     if (appData.oldSaveStyle)
13726       return SaveGameOldStyle(f);
13727     else
13728       return SaveGamePGN(f);
13729 }
13730
13731 /* Save the current position to the given file */
13732 int
13733 SavePositionToFile (char *filename)
13734 {
13735     FILE *f;
13736     char buf[MSG_SIZ];
13737
13738     if (strcmp(filename, "-") == 0) {
13739         return SavePosition(stdout, 0, NULL);
13740     } else {
13741         f = fopen(filename, "a");
13742         if (f == NULL) {
13743             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13744             DisplayError(buf, errno);
13745             return FALSE;
13746         } else {
13747             safeStrCpy(buf, lastMsg, MSG_SIZ);
13748             DisplayMessage(_("Waiting for access to save file"), "");
13749             flock(fileno(f), LOCK_EX); // [HGM] lock
13750             DisplayMessage(_("Saving position"), "");
13751             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13752             SavePosition(f, 0, NULL);
13753             DisplayMessage(buf, "");
13754             return TRUE;
13755         }
13756     }
13757 }
13758
13759 /* Save the current position to the given open file and close the file */
13760 int
13761 SavePosition (FILE *f, int dummy, char *dummy2)
13762 {
13763     time_t tm;
13764     char *fen;
13765
13766     if (gameMode == EditPosition) EditPositionDone(TRUE);
13767     if (appData.oldSaveStyle) {
13768         tm = time((time_t *) NULL);
13769
13770         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13771         PrintOpponents(f);
13772         fprintf(f, "[--------------\n");
13773         PrintPosition(f, currentMove);
13774         fprintf(f, "--------------]\n");
13775     } else {
13776         fen = PositionToFEN(currentMove, NULL, 1);
13777         fprintf(f, "%s\n", fen);
13778         free(fen);
13779     }
13780     fclose(f);
13781     return TRUE;
13782 }
13783
13784 void
13785 ReloadCmailMsgEvent (int unregister)
13786 {
13787 #if !WIN32
13788     static char *inFilename = NULL;
13789     static char *outFilename;
13790     int i;
13791     struct stat inbuf, outbuf;
13792     int status;
13793
13794     /* Any registered moves are unregistered if unregister is set, */
13795     /* i.e. invoked by the signal handler */
13796     if (unregister) {
13797         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13798             cmailMoveRegistered[i] = FALSE;
13799             if (cmailCommentList[i] != NULL) {
13800                 free(cmailCommentList[i]);
13801                 cmailCommentList[i] = NULL;
13802             }
13803         }
13804         nCmailMovesRegistered = 0;
13805     }
13806
13807     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13808         cmailResult[i] = CMAIL_NOT_RESULT;
13809     }
13810     nCmailResults = 0;
13811
13812     if (inFilename == NULL) {
13813         /* Because the filenames are static they only get malloced once  */
13814         /* and they never get freed                                      */
13815         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13816         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13817
13818         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13819         sprintf(outFilename, "%s.out", appData.cmailGameName);
13820     }
13821
13822     status = stat(outFilename, &outbuf);
13823     if (status < 0) {
13824         cmailMailedMove = FALSE;
13825     } else {
13826         status = stat(inFilename, &inbuf);
13827         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13828     }
13829
13830     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13831        counts the games, notes how each one terminated, etc.
13832
13833        It would be nice to remove this kludge and instead gather all
13834        the information while building the game list.  (And to keep it
13835        in the game list nodes instead of having a bunch of fixed-size
13836        parallel arrays.)  Note this will require getting each game's
13837        termination from the PGN tags, as the game list builder does
13838        not process the game moves.  --mann
13839        */
13840     cmailMsgLoaded = TRUE;
13841     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13842
13843     /* Load first game in the file or popup game menu */
13844     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13845
13846 #endif /* !WIN32 */
13847     return;
13848 }
13849
13850 int
13851 RegisterMove ()
13852 {
13853     FILE *f;
13854     char string[MSG_SIZ];
13855
13856     if (   cmailMailedMove
13857         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13858         return TRUE;            /* Allow free viewing  */
13859     }
13860
13861     /* Unregister move to ensure that we don't leave RegisterMove        */
13862     /* with the move registered when the conditions for registering no   */
13863     /* longer hold                                                       */
13864     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13865         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13866         nCmailMovesRegistered --;
13867
13868         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13869           {
13870               free(cmailCommentList[lastLoadGameNumber - 1]);
13871               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13872           }
13873     }
13874
13875     if (cmailOldMove == -1) {
13876         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13877         return FALSE;
13878     }
13879
13880     if (currentMove > cmailOldMove + 1) {
13881         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13882         return FALSE;
13883     }
13884
13885     if (currentMove < cmailOldMove) {
13886         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13887         return FALSE;
13888     }
13889
13890     if (forwardMostMove > currentMove) {
13891         /* Silently truncate extra moves */
13892         TruncateGame();
13893     }
13894
13895     if (   (currentMove == cmailOldMove + 1)
13896         || (   (currentMove == cmailOldMove)
13897             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13898                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13899         if (gameInfo.result != GameUnfinished) {
13900             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13901         }
13902
13903         if (commentList[currentMove] != NULL) {
13904             cmailCommentList[lastLoadGameNumber - 1]
13905               = StrSave(commentList[currentMove]);
13906         }
13907         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13908
13909         if (appData.debugMode)
13910           fprintf(debugFP, "Saving %s for game %d\n",
13911                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13912
13913         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13914
13915         f = fopen(string, "w");
13916         if (appData.oldSaveStyle) {
13917             SaveGameOldStyle(f); /* also closes the file */
13918
13919             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13920             f = fopen(string, "w");
13921             SavePosition(f, 0, NULL); /* also closes the file */
13922         } else {
13923             fprintf(f, "{--------------\n");
13924             PrintPosition(f, currentMove);
13925             fprintf(f, "--------------}\n\n");
13926
13927             SaveGame(f, 0, NULL); /* also closes the file*/
13928         }
13929
13930         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13931         nCmailMovesRegistered ++;
13932     } else if (nCmailGames == 1) {
13933         DisplayError(_("You have not made a move yet"), 0);
13934         return FALSE;
13935     }
13936
13937     return TRUE;
13938 }
13939
13940 void
13941 MailMoveEvent ()
13942 {
13943 #if !WIN32
13944     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13945     FILE *commandOutput;
13946     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13947     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13948     int nBuffers;
13949     int i;
13950     int archived;
13951     char *arcDir;
13952
13953     if (! cmailMsgLoaded) {
13954         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13955         return;
13956     }
13957
13958     if (nCmailGames == nCmailResults) {
13959         DisplayError(_("No unfinished games"), 0);
13960         return;
13961     }
13962
13963 #if CMAIL_PROHIBIT_REMAIL
13964     if (cmailMailedMove) {
13965       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);
13966         DisplayError(msg, 0);
13967         return;
13968     }
13969 #endif
13970
13971     if (! (cmailMailedMove || RegisterMove())) return;
13972
13973     if (   cmailMailedMove
13974         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13975       snprintf(string, MSG_SIZ, partCommandString,
13976                appData.debugMode ? " -v" : "", appData.cmailGameName);
13977         commandOutput = popen(string, "r");
13978
13979         if (commandOutput == NULL) {
13980             DisplayError(_("Failed to invoke cmail"), 0);
13981         } else {
13982             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13983                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13984             }
13985             if (nBuffers > 1) {
13986                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13987                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13988                 nBytes = MSG_SIZ - 1;
13989             } else {
13990                 (void) memcpy(msg, buffer, nBytes);
13991             }
13992             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13993
13994             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13995                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13996
13997                 archived = TRUE;
13998                 for (i = 0; i < nCmailGames; i ++) {
13999                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14000                         archived = FALSE;
14001                     }
14002                 }
14003                 if (   archived
14004                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14005                         != NULL)) {
14006                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14007                            arcDir,
14008                            appData.cmailGameName,
14009                            gameInfo.date);
14010                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14011                     cmailMsgLoaded = FALSE;
14012                 }
14013             }
14014
14015             DisplayInformation(msg);
14016             pclose(commandOutput);
14017         }
14018     } else {
14019         if ((*cmailMsg) != '\0') {
14020             DisplayInformation(cmailMsg);
14021         }
14022     }
14023
14024     return;
14025 #endif /* !WIN32 */
14026 }
14027
14028 char *
14029 CmailMsg ()
14030 {
14031 #if WIN32
14032     return NULL;
14033 #else
14034     int  prependComma = 0;
14035     char number[5];
14036     char string[MSG_SIZ];       /* Space for game-list */
14037     int  i;
14038
14039     if (!cmailMsgLoaded) return "";
14040
14041     if (cmailMailedMove) {
14042       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14043     } else {
14044         /* Create a list of games left */
14045       snprintf(string, MSG_SIZ, "[");
14046         for (i = 0; i < nCmailGames; i ++) {
14047             if (! (   cmailMoveRegistered[i]
14048                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14049                 if (prependComma) {
14050                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14051                 } else {
14052                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14053                     prependComma = 1;
14054                 }
14055
14056                 strcat(string, number);
14057             }
14058         }
14059         strcat(string, "]");
14060
14061         if (nCmailMovesRegistered + nCmailResults == 0) {
14062             switch (nCmailGames) {
14063               case 1:
14064                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14065                 break;
14066
14067               case 2:
14068                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14069                 break;
14070
14071               default:
14072                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14073                          nCmailGames);
14074                 break;
14075             }
14076         } else {
14077             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14078               case 1:
14079                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14080                          string);
14081                 break;
14082
14083               case 0:
14084                 if (nCmailResults == nCmailGames) {
14085                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14086                 } else {
14087                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14088                 }
14089                 break;
14090
14091               default:
14092                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14093                          string);
14094             }
14095         }
14096     }
14097     return cmailMsg;
14098 #endif /* WIN32 */
14099 }
14100
14101 void
14102 ResetGameEvent ()
14103 {
14104     if (gameMode == Training)
14105       SetTrainingModeOff();
14106
14107     Reset(TRUE, TRUE);
14108     cmailMsgLoaded = FALSE;
14109     if (appData.icsActive) {
14110       SendToICS(ics_prefix);
14111       SendToICS("refresh\n");
14112     }
14113 }
14114
14115 void
14116 ExitEvent (int status)
14117 {
14118     exiting++;
14119     if (exiting > 2) {
14120       /* Give up on clean exit */
14121       exit(status);
14122     }
14123     if (exiting > 1) {
14124       /* Keep trying for clean exit */
14125       return;
14126     }
14127
14128     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14129     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14130
14131     if (telnetISR != NULL) {
14132       RemoveInputSource(telnetISR);
14133     }
14134     if (icsPR != NoProc) {
14135       DestroyChildProcess(icsPR, TRUE);
14136     }
14137
14138     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14139     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14140
14141     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14142     /* make sure this other one finishes before killing it!                  */
14143     if(endingGame) { int count = 0;
14144         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14145         while(endingGame && count++ < 10) DoSleep(1);
14146         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14147     }
14148
14149     /* Kill off chess programs */
14150     if (first.pr != NoProc) {
14151         ExitAnalyzeMode();
14152
14153         DoSleep( appData.delayBeforeQuit );
14154         SendToProgram("quit\n", &first);
14155         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14156     }
14157     if (second.pr != NoProc) {
14158         DoSleep( appData.delayBeforeQuit );
14159         SendToProgram("quit\n", &second);
14160         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14161     }
14162     if (first.isr != NULL) {
14163         RemoveInputSource(first.isr);
14164     }
14165     if (second.isr != NULL) {
14166         RemoveInputSource(second.isr);
14167     }
14168
14169     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14170     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14171
14172     ShutDownFrontEnd();
14173     exit(status);
14174 }
14175
14176 void
14177 PauseEngine (ChessProgramState *cps)
14178 {
14179     SendToProgram("pause\n", cps);
14180     cps->pause = 2;
14181 }
14182
14183 void
14184 UnPauseEngine (ChessProgramState *cps)
14185 {
14186     SendToProgram("resume\n", cps);
14187     cps->pause = 1;
14188 }
14189
14190 void
14191 PauseEvent ()
14192 {
14193     if (appData.debugMode)
14194         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14195     if (pausing) {
14196         pausing = FALSE;
14197         ModeHighlight();
14198         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14199             StartClocks();
14200             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14201                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14202                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14203             }
14204             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14205             HandleMachineMove(stashedInputMove, stalledEngine);
14206             stalledEngine = NULL;
14207             return;
14208         }
14209         if (gameMode == MachinePlaysWhite ||
14210             gameMode == TwoMachinesPlay   ||
14211             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14212             if(first.pause)  UnPauseEngine(&first);
14213             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14214             if(second.pause) UnPauseEngine(&second);
14215             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14216             StartClocks();
14217         } else {
14218             DisplayBothClocks();
14219         }
14220         if (gameMode == PlayFromGameFile) {
14221             if (appData.timeDelay >= 0)
14222                 AutoPlayGameLoop();
14223         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14224             Reset(FALSE, TRUE);
14225             SendToICS(ics_prefix);
14226             SendToICS("refresh\n");
14227         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14228             ForwardInner(forwardMostMove);
14229         }
14230         pauseExamInvalid = FALSE;
14231     } else {
14232         switch (gameMode) {
14233           default:
14234             return;
14235           case IcsExamining:
14236             pauseExamForwardMostMove = forwardMostMove;
14237             pauseExamInvalid = FALSE;
14238             /* fall through */
14239           case IcsObserving:
14240           case IcsPlayingWhite:
14241           case IcsPlayingBlack:
14242             pausing = TRUE;
14243             ModeHighlight();
14244             return;
14245           case PlayFromGameFile:
14246             (void) StopLoadGameTimer();
14247             pausing = TRUE;
14248             ModeHighlight();
14249             break;
14250           case BeginningOfGame:
14251             if (appData.icsActive) return;
14252             /* else fall through */
14253           case MachinePlaysWhite:
14254           case MachinePlaysBlack:
14255           case TwoMachinesPlay:
14256             if (forwardMostMove == 0)
14257               return;           /* don't pause if no one has moved */
14258             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14259                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14260                 if(onMove->pause) {           // thinking engine can be paused
14261                     PauseEngine(onMove);      // do it
14262                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14263                         PauseEngine(onMove->other);
14264                     else
14265                         SendToProgram("easy\n", onMove->other);
14266                     StopClocks();
14267                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14268             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14269                 if(first.pause) {
14270                     PauseEngine(&first);
14271                     StopClocks();
14272                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14273             } else { // human on move, pause pondering by either method
14274                 if(first.pause)
14275                     PauseEngine(&first);
14276                 else if(appData.ponderNextMove)
14277                     SendToProgram("easy\n", &first);
14278                 StopClocks();
14279             }
14280             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14281           case AnalyzeMode:
14282             pausing = TRUE;
14283             ModeHighlight();
14284             break;
14285         }
14286     }
14287 }
14288
14289 void
14290 EditCommentEvent ()
14291 {
14292     char title[MSG_SIZ];
14293
14294     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14295       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14296     } else {
14297       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14298                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14299                parseList[currentMove - 1]);
14300     }
14301
14302     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14303 }
14304
14305
14306 void
14307 EditTagsEvent ()
14308 {
14309     char *tags = PGNTags(&gameInfo);
14310     bookUp = FALSE;
14311     EditTagsPopUp(tags, NULL);
14312     free(tags);
14313 }
14314
14315 void
14316 ToggleSecond ()
14317 {
14318   if(second.analyzing) {
14319     SendToProgram("exit\n", &second);
14320     second.analyzing = FALSE;
14321   } else {
14322     if (second.pr == NoProc) StartChessProgram(&second);
14323     InitChessProgram(&second, FALSE);
14324     FeedMovesToProgram(&second, currentMove);
14325
14326     SendToProgram("analyze\n", &second);
14327     second.analyzing = TRUE;
14328   }
14329 }
14330
14331 /* Toggle ShowThinking */
14332 void
14333 ToggleShowThinking()
14334 {
14335   appData.showThinking = !appData.showThinking;
14336   ShowThinkingEvent();
14337 }
14338
14339 int
14340 AnalyzeModeEvent ()
14341 {
14342     char buf[MSG_SIZ];
14343
14344     if (!first.analysisSupport) {
14345       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14346       DisplayError(buf, 0);
14347       return 0;
14348     }
14349     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14350     if (appData.icsActive) {
14351         if (gameMode != IcsObserving) {
14352           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14353             DisplayError(buf, 0);
14354             /* secure check */
14355             if (appData.icsEngineAnalyze) {
14356                 if (appData.debugMode)
14357                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14358                 ExitAnalyzeMode();
14359                 ModeHighlight();
14360             }
14361             return 0;
14362         }
14363         /* if enable, user wants to disable icsEngineAnalyze */
14364         if (appData.icsEngineAnalyze) {
14365                 ExitAnalyzeMode();
14366                 ModeHighlight();
14367                 return 0;
14368         }
14369         appData.icsEngineAnalyze = TRUE;
14370         if (appData.debugMode)
14371             fprintf(debugFP, "ICS engine analyze starting... \n");
14372     }
14373
14374     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14375     if (appData.noChessProgram || gameMode == AnalyzeMode)
14376       return 0;
14377
14378     if (gameMode != AnalyzeFile) {
14379         if (!appData.icsEngineAnalyze) {
14380                EditGameEvent();
14381                if (gameMode != EditGame) return 0;
14382         }
14383         if (!appData.showThinking) ToggleShowThinking();
14384         ResurrectChessProgram();
14385         SendToProgram("analyze\n", &first);
14386         first.analyzing = TRUE;
14387         /*first.maybeThinking = TRUE;*/
14388         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14389         EngineOutputPopUp();
14390     }
14391     if (!appData.icsEngineAnalyze) {
14392         gameMode = AnalyzeMode;
14393         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14394     }
14395     pausing = FALSE;
14396     ModeHighlight();
14397     SetGameInfo();
14398
14399     StartAnalysisClock();
14400     GetTimeMark(&lastNodeCountTime);
14401     lastNodeCount = 0;
14402     return 1;
14403 }
14404
14405 void
14406 AnalyzeFileEvent ()
14407 {
14408     if (appData.noChessProgram || gameMode == AnalyzeFile)
14409       return;
14410
14411     if (!first.analysisSupport) {
14412       char buf[MSG_SIZ];
14413       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14414       DisplayError(buf, 0);
14415       return;
14416     }
14417
14418     if (gameMode != AnalyzeMode) {
14419         keepInfo = 1; // mere annotating should not alter PGN tags
14420         EditGameEvent();
14421         keepInfo = 0;
14422         if (gameMode != EditGame) return;
14423         if (!appData.showThinking) ToggleShowThinking();
14424         ResurrectChessProgram();
14425         SendToProgram("analyze\n", &first);
14426         first.analyzing = TRUE;
14427         /*first.maybeThinking = TRUE;*/
14428         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14429         EngineOutputPopUp();
14430     }
14431     gameMode = AnalyzeFile;
14432     pausing = FALSE;
14433     ModeHighlight();
14434
14435     StartAnalysisClock();
14436     GetTimeMark(&lastNodeCountTime);
14437     lastNodeCount = 0;
14438     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14439     AnalysisPeriodicEvent(1);
14440 }
14441
14442 void
14443 MachineWhiteEvent ()
14444 {
14445     char buf[MSG_SIZ];
14446     char *bookHit = NULL;
14447
14448     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14449       return;
14450
14451
14452     if (gameMode == PlayFromGameFile ||
14453         gameMode == TwoMachinesPlay  ||
14454         gameMode == Training         ||
14455         gameMode == AnalyzeMode      ||
14456         gameMode == EndOfGame)
14457         EditGameEvent();
14458
14459     if (gameMode == EditPosition)
14460         EditPositionDone(TRUE);
14461
14462     if (!WhiteOnMove(currentMove)) {
14463         DisplayError(_("It is not White's turn"), 0);
14464         return;
14465     }
14466
14467     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14468       ExitAnalyzeMode();
14469
14470     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14471         gameMode == AnalyzeFile)
14472         TruncateGame();
14473
14474     ResurrectChessProgram();    /* in case it isn't running */
14475     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14476         gameMode = MachinePlaysWhite;
14477         ResetClocks();
14478     } else
14479     gameMode = MachinePlaysWhite;
14480     pausing = FALSE;
14481     ModeHighlight();
14482     SetGameInfo();
14483     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14484     DisplayTitle(buf);
14485     if (first.sendName) {
14486       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14487       SendToProgram(buf, &first);
14488     }
14489     if (first.sendTime) {
14490       if (first.useColors) {
14491         SendToProgram("black\n", &first); /*gnu kludge*/
14492       }
14493       SendTimeRemaining(&first, TRUE);
14494     }
14495     if (first.useColors) {
14496       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14497     }
14498     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14499     SetMachineThinkingEnables();
14500     first.maybeThinking = TRUE;
14501     StartClocks();
14502     firstMove = FALSE;
14503
14504     if (appData.autoFlipView && !flipView) {
14505       flipView = !flipView;
14506       DrawPosition(FALSE, NULL);
14507       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14508     }
14509
14510     if(bookHit) { // [HGM] book: simulate book reply
14511         static char bookMove[MSG_SIZ]; // a bit generous?
14512
14513         programStats.nodes = programStats.depth = programStats.time =
14514         programStats.score = programStats.got_only_move = 0;
14515         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14516
14517         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14518         strcat(bookMove, bookHit);
14519         HandleMachineMove(bookMove, &first);
14520     }
14521 }
14522
14523 void
14524 MachineBlackEvent ()
14525 {
14526   char buf[MSG_SIZ];
14527   char *bookHit = NULL;
14528
14529     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14530         return;
14531
14532
14533     if (gameMode == PlayFromGameFile ||
14534         gameMode == TwoMachinesPlay  ||
14535         gameMode == Training         ||
14536         gameMode == AnalyzeMode      ||
14537         gameMode == EndOfGame)
14538         EditGameEvent();
14539
14540     if (gameMode == EditPosition)
14541         EditPositionDone(TRUE);
14542
14543     if (WhiteOnMove(currentMove)) {
14544         DisplayError(_("It is not Black's turn"), 0);
14545         return;
14546     }
14547
14548     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14549       ExitAnalyzeMode();
14550
14551     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14552         gameMode == AnalyzeFile)
14553         TruncateGame();
14554
14555     ResurrectChessProgram();    /* in case it isn't running */
14556     gameMode = MachinePlaysBlack;
14557     pausing = FALSE;
14558     ModeHighlight();
14559     SetGameInfo();
14560     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14561     DisplayTitle(buf);
14562     if (first.sendName) {
14563       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14564       SendToProgram(buf, &first);
14565     }
14566     if (first.sendTime) {
14567       if (first.useColors) {
14568         SendToProgram("white\n", &first); /*gnu kludge*/
14569       }
14570       SendTimeRemaining(&first, FALSE);
14571     }
14572     if (first.useColors) {
14573       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14574     }
14575     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14576     SetMachineThinkingEnables();
14577     first.maybeThinking = TRUE;
14578     StartClocks();
14579
14580     if (appData.autoFlipView && flipView) {
14581       flipView = !flipView;
14582       DrawPosition(FALSE, NULL);
14583       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14584     }
14585     if(bookHit) { // [HGM] book: simulate book reply
14586         static char bookMove[MSG_SIZ]; // a bit generous?
14587
14588         programStats.nodes = programStats.depth = programStats.time =
14589         programStats.score = programStats.got_only_move = 0;
14590         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14591
14592         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14593         strcat(bookMove, bookHit);
14594         HandleMachineMove(bookMove, &first);
14595     }
14596 }
14597
14598
14599 void
14600 DisplayTwoMachinesTitle ()
14601 {
14602     char buf[MSG_SIZ];
14603     if (appData.matchGames > 0) {
14604         if(appData.tourneyFile[0]) {
14605           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14606                    gameInfo.white, _("vs."), gameInfo.black,
14607                    nextGame+1, appData.matchGames+1,
14608                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14609         } else
14610         if (first.twoMachinesColor[0] == 'w') {
14611           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14612                    gameInfo.white, _("vs."),  gameInfo.black,
14613                    first.matchWins, second.matchWins,
14614                    matchGame - 1 - (first.matchWins + second.matchWins));
14615         } else {
14616           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14617                    gameInfo.white, _("vs."), gameInfo.black,
14618                    second.matchWins, first.matchWins,
14619                    matchGame - 1 - (first.matchWins + second.matchWins));
14620         }
14621     } else {
14622       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14623     }
14624     DisplayTitle(buf);
14625 }
14626
14627 void
14628 SettingsMenuIfReady ()
14629 {
14630   if (second.lastPing != second.lastPong) {
14631     DisplayMessage("", _("Waiting for second chess program"));
14632     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14633     return;
14634   }
14635   ThawUI();
14636   DisplayMessage("", "");
14637   SettingsPopUp(&second);
14638 }
14639
14640 int
14641 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14642 {
14643     char buf[MSG_SIZ];
14644     if (cps->pr == NoProc) {
14645         StartChessProgram(cps);
14646         if (cps->protocolVersion == 1) {
14647           retry();
14648           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14649         } else {
14650           /* kludge: allow timeout for initial "feature" command */
14651           if(retry != TwoMachinesEventIfReady) FreezeUI();
14652           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14653           DisplayMessage("", buf);
14654           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14655         }
14656         return 1;
14657     }
14658     return 0;
14659 }
14660
14661 void
14662 TwoMachinesEvent P((void))
14663 {
14664     int i;
14665     char buf[MSG_SIZ];
14666     ChessProgramState *onmove;
14667     char *bookHit = NULL;
14668     static int stalling = 0;
14669     TimeMark now;
14670     long wait;
14671
14672     if (appData.noChessProgram) return;
14673
14674     switch (gameMode) {
14675       case TwoMachinesPlay:
14676         return;
14677       case MachinePlaysWhite:
14678       case MachinePlaysBlack:
14679         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14680             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14681             return;
14682         }
14683         /* fall through */
14684       case BeginningOfGame:
14685       case PlayFromGameFile:
14686       case EndOfGame:
14687         EditGameEvent();
14688         if (gameMode != EditGame) return;
14689         break;
14690       case EditPosition:
14691         EditPositionDone(TRUE);
14692         break;
14693       case AnalyzeMode:
14694       case AnalyzeFile:
14695         ExitAnalyzeMode();
14696         break;
14697       case EditGame:
14698       default:
14699         break;
14700     }
14701
14702 //    forwardMostMove = currentMove;
14703     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14704     startingEngine = TRUE;
14705
14706     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14707
14708     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14709     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14710       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14711       return;
14712     }
14713     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14714
14715     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14716                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14717         startingEngine = matchMode = FALSE;
14718         DisplayError("second engine does not play this", 0);
14719         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14720         EditGameEvent(); // switch back to EditGame mode
14721         return;
14722     }
14723
14724     if(!stalling) {
14725       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14726       SendToProgram("force\n", &second);
14727       stalling = 1;
14728       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14729       return;
14730     }
14731     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14732     if(appData.matchPause>10000 || appData.matchPause<10)
14733                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14734     wait = SubtractTimeMarks(&now, &pauseStart);
14735     if(wait < appData.matchPause) {
14736         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14737         return;
14738     }
14739     // we are now committed to starting the game
14740     stalling = 0;
14741     DisplayMessage("", "");
14742     if (startedFromSetupPosition) {
14743         SendBoard(&second, backwardMostMove);
14744     if (appData.debugMode) {
14745         fprintf(debugFP, "Two Machines\n");
14746     }
14747     }
14748     for (i = backwardMostMove; i < forwardMostMove; i++) {
14749         SendMoveToProgram(i, &second);
14750     }
14751
14752     gameMode = TwoMachinesPlay;
14753     pausing = startingEngine = FALSE;
14754     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14755     SetGameInfo();
14756     DisplayTwoMachinesTitle();
14757     firstMove = TRUE;
14758     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14759         onmove = &first;
14760     } else {
14761         onmove = &second;
14762     }
14763     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14764     SendToProgram(first.computerString, &first);
14765     if (first.sendName) {
14766       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14767       SendToProgram(buf, &first);
14768     }
14769     SendToProgram(second.computerString, &second);
14770     if (second.sendName) {
14771       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14772       SendToProgram(buf, &second);
14773     }
14774
14775     ResetClocks();
14776     if (!first.sendTime || !second.sendTime) {
14777         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14778         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14779     }
14780     if (onmove->sendTime) {
14781       if (onmove->useColors) {
14782         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14783       }
14784       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14785     }
14786     if (onmove->useColors) {
14787       SendToProgram(onmove->twoMachinesColor, onmove);
14788     }
14789     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14790 //    SendToProgram("go\n", onmove);
14791     onmove->maybeThinking = TRUE;
14792     SetMachineThinkingEnables();
14793
14794     StartClocks();
14795
14796     if(bookHit) { // [HGM] book: simulate book reply
14797         static char bookMove[MSG_SIZ]; // a bit generous?
14798
14799         programStats.nodes = programStats.depth = programStats.time =
14800         programStats.score = programStats.got_only_move = 0;
14801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14802
14803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14804         strcat(bookMove, bookHit);
14805         savedMessage = bookMove; // args for deferred call
14806         savedState = onmove;
14807         ScheduleDelayedEvent(DeferredBookMove, 1);
14808     }
14809 }
14810
14811 void
14812 TrainingEvent ()
14813 {
14814     if (gameMode == Training) {
14815       SetTrainingModeOff();
14816       gameMode = PlayFromGameFile;
14817       DisplayMessage("", _("Training mode off"));
14818     } else {
14819       gameMode = Training;
14820       animateTraining = appData.animate;
14821
14822       /* make sure we are not already at the end of the game */
14823       if (currentMove < forwardMostMove) {
14824         SetTrainingModeOn();
14825         DisplayMessage("", _("Training mode on"));
14826       } else {
14827         gameMode = PlayFromGameFile;
14828         DisplayError(_("Already at end of game"), 0);
14829       }
14830     }
14831     ModeHighlight();
14832 }
14833
14834 void
14835 IcsClientEvent ()
14836 {
14837     if (!appData.icsActive) return;
14838     switch (gameMode) {
14839       case IcsPlayingWhite:
14840       case IcsPlayingBlack:
14841       case IcsObserving:
14842       case IcsIdle:
14843       case BeginningOfGame:
14844       case IcsExamining:
14845         return;
14846
14847       case EditGame:
14848         break;
14849
14850       case EditPosition:
14851         EditPositionDone(TRUE);
14852         break;
14853
14854       case AnalyzeMode:
14855       case AnalyzeFile:
14856         ExitAnalyzeMode();
14857         break;
14858
14859       default:
14860         EditGameEvent();
14861         break;
14862     }
14863
14864     gameMode = IcsIdle;
14865     ModeHighlight();
14866     return;
14867 }
14868
14869 void
14870 EditGameEvent ()
14871 {
14872     int i;
14873
14874     switch (gameMode) {
14875       case Training:
14876         SetTrainingModeOff();
14877         break;
14878       case MachinePlaysWhite:
14879       case MachinePlaysBlack:
14880       case BeginningOfGame:
14881         SendToProgram("force\n", &first);
14882         SetUserThinkingEnables();
14883         break;
14884       case PlayFromGameFile:
14885         (void) StopLoadGameTimer();
14886         if (gameFileFP != NULL) {
14887             gameFileFP = NULL;
14888         }
14889         break;
14890       case EditPosition:
14891         EditPositionDone(TRUE);
14892         break;
14893       case AnalyzeMode:
14894       case AnalyzeFile:
14895         ExitAnalyzeMode();
14896         SendToProgram("force\n", &first);
14897         break;
14898       case TwoMachinesPlay:
14899         GameEnds(EndOfFile, NULL, GE_PLAYER);
14900         ResurrectChessProgram();
14901         SetUserThinkingEnables();
14902         break;
14903       case EndOfGame:
14904         ResurrectChessProgram();
14905         break;
14906       case IcsPlayingBlack:
14907       case IcsPlayingWhite:
14908         DisplayError(_("Warning: You are still playing a game"), 0);
14909         break;
14910       case IcsObserving:
14911         DisplayError(_("Warning: You are still observing a game"), 0);
14912         break;
14913       case IcsExamining:
14914         DisplayError(_("Warning: You are still examining a game"), 0);
14915         break;
14916       case IcsIdle:
14917         break;
14918       case EditGame:
14919       default:
14920         return;
14921     }
14922
14923     pausing = FALSE;
14924     StopClocks();
14925     first.offeredDraw = second.offeredDraw = 0;
14926
14927     if (gameMode == PlayFromGameFile) {
14928         whiteTimeRemaining = timeRemaining[0][currentMove];
14929         blackTimeRemaining = timeRemaining[1][currentMove];
14930         DisplayTitle("");
14931     }
14932
14933     if (gameMode == MachinePlaysWhite ||
14934         gameMode == MachinePlaysBlack ||
14935         gameMode == TwoMachinesPlay ||
14936         gameMode == EndOfGame) {
14937         i = forwardMostMove;
14938         while (i > currentMove) {
14939             SendToProgram("undo\n", &first);
14940             i--;
14941         }
14942         if(!adjustedClock) {
14943         whiteTimeRemaining = timeRemaining[0][currentMove];
14944         blackTimeRemaining = timeRemaining[1][currentMove];
14945         DisplayBothClocks();
14946         }
14947         if (whiteFlag || blackFlag) {
14948             whiteFlag = blackFlag = 0;
14949         }
14950         DisplayTitle("");
14951     }
14952
14953     gameMode = EditGame;
14954     ModeHighlight();
14955     SetGameInfo();
14956 }
14957
14958
14959 void
14960 EditPositionEvent ()
14961 {
14962     if (gameMode == EditPosition) {
14963         EditGameEvent();
14964         return;
14965     }
14966
14967     EditGameEvent();
14968     if (gameMode != EditGame) return;
14969
14970     gameMode = EditPosition;
14971     ModeHighlight();
14972     SetGameInfo();
14973     if (currentMove > 0)
14974       CopyBoard(boards[0], boards[currentMove]);
14975
14976     blackPlaysFirst = !WhiteOnMove(currentMove);
14977     ResetClocks();
14978     currentMove = forwardMostMove = backwardMostMove = 0;
14979     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14980     DisplayMove(-1);
14981     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14982 }
14983
14984 void
14985 ExitAnalyzeMode ()
14986 {
14987     /* [DM] icsEngineAnalyze - possible call from other functions */
14988     if (appData.icsEngineAnalyze) {
14989         appData.icsEngineAnalyze = FALSE;
14990
14991         DisplayMessage("",_("Close ICS engine analyze..."));
14992     }
14993     if (first.analysisSupport && first.analyzing) {
14994       SendToBoth("exit\n");
14995       first.analyzing = second.analyzing = FALSE;
14996     }
14997     thinkOutput[0] = NULLCHAR;
14998 }
14999
15000 void
15001 EditPositionDone (Boolean fakeRights)
15002 {
15003     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15004
15005     startedFromSetupPosition = TRUE;
15006     InitChessProgram(&first, FALSE);
15007     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15008       boards[0][EP_STATUS] = EP_NONE;
15009       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15010       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15011         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15012         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15013       } else boards[0][CASTLING][2] = NoRights;
15014       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15015         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15016         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15017       } else boards[0][CASTLING][5] = NoRights;
15018       if(gameInfo.variant == VariantSChess) {
15019         int i;
15020         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15021           boards[0][VIRGIN][i] = 0;
15022           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15023           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15024         }
15025       }
15026     }
15027     SendToProgram("force\n", &first);
15028     if (blackPlaysFirst) {
15029         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15030         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15031         currentMove = forwardMostMove = backwardMostMove = 1;
15032         CopyBoard(boards[1], boards[0]);
15033     } else {
15034         currentMove = forwardMostMove = backwardMostMove = 0;
15035     }
15036     SendBoard(&first, forwardMostMove);
15037     if (appData.debugMode) {
15038         fprintf(debugFP, "EditPosDone\n");
15039     }
15040     DisplayTitle("");
15041     DisplayMessage("", "");
15042     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15043     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15044     gameMode = EditGame;
15045     ModeHighlight();
15046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15047     ClearHighlights(); /* [AS] */
15048 }
15049
15050 /* Pause for `ms' milliseconds */
15051 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15052 void
15053 TimeDelay (long ms)
15054 {
15055     TimeMark m1, m2;
15056
15057     GetTimeMark(&m1);
15058     do {
15059         GetTimeMark(&m2);
15060     } while (SubtractTimeMarks(&m2, &m1) < ms);
15061 }
15062
15063 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15064 void
15065 SendMultiLineToICS (char *buf)
15066 {
15067     char temp[MSG_SIZ+1], *p;
15068     int len;
15069
15070     len = strlen(buf);
15071     if (len > MSG_SIZ)
15072       len = MSG_SIZ;
15073
15074     strncpy(temp, buf, len);
15075     temp[len] = 0;
15076
15077     p = temp;
15078     while (*p) {
15079         if (*p == '\n' || *p == '\r')
15080           *p = ' ';
15081         ++p;
15082     }
15083
15084     strcat(temp, "\n");
15085     SendToICS(temp);
15086     SendToPlayer(temp, strlen(temp));
15087 }
15088
15089 void
15090 SetWhiteToPlayEvent ()
15091 {
15092     if (gameMode == EditPosition) {
15093         blackPlaysFirst = FALSE;
15094         DisplayBothClocks();    /* works because currentMove is 0 */
15095     } else if (gameMode == IcsExamining) {
15096         SendToICS(ics_prefix);
15097         SendToICS("tomove white\n");
15098     }
15099 }
15100
15101 void
15102 SetBlackToPlayEvent ()
15103 {
15104     if (gameMode == EditPosition) {
15105         blackPlaysFirst = TRUE;
15106         currentMove = 1;        /* kludge */
15107         DisplayBothClocks();
15108         currentMove = 0;
15109     } else if (gameMode == IcsExamining) {
15110         SendToICS(ics_prefix);
15111         SendToICS("tomove black\n");
15112     }
15113 }
15114
15115 void
15116 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15117 {
15118     char buf[MSG_SIZ];
15119     ChessSquare piece = boards[0][y][x];
15120     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15121     static int lastVariant;
15122
15123     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15124
15125     switch (selection) {
15126       case ClearBoard:
15127         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15128         MarkTargetSquares(1);
15129         CopyBoard(currentBoard, boards[0]);
15130         CopyBoard(menuBoard, initialPosition);
15131         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15132             SendToICS(ics_prefix);
15133             SendToICS("bsetup clear\n");
15134         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15135             SendToICS(ics_prefix);
15136             SendToICS("clearboard\n");
15137         } else {
15138             int nonEmpty = 0;
15139             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15140                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15141                 for (y = 0; y < BOARD_HEIGHT; y++) {
15142                     if (gameMode == IcsExamining) {
15143                         if (boards[currentMove][y][x] != EmptySquare) {
15144                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15145                                     AAA + x, ONE + y);
15146                             SendToICS(buf);
15147                         }
15148                     } else if(boards[0][y][x] != DarkSquare) {
15149                         if(boards[0][y][x] != p) nonEmpty++;
15150                         boards[0][y][x] = p;
15151                     }
15152                 }
15153             }
15154             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15155                 int r;
15156                 for(r = 0; r < BOARD_HEIGHT; r++) {
15157                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15158                     ChessSquare p = menuBoard[r][x];
15159                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15160                   }
15161                 }
15162                 DisplayMessage("Clicking clock again restores position", "");
15163                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15164                 if(!nonEmpty) { // asked to clear an empty board
15165                     CopyBoard(boards[0], menuBoard);
15166                 } else
15167                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15168                     CopyBoard(boards[0], initialPosition);
15169                 } else
15170                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15171                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15172                     CopyBoard(boards[0], erasedBoard);
15173                 } else
15174                     CopyBoard(erasedBoard, currentBoard);
15175
15176             }
15177         }
15178         if (gameMode == EditPosition) {
15179             DrawPosition(FALSE, boards[0]);
15180         }
15181         break;
15182
15183       case WhitePlay:
15184         SetWhiteToPlayEvent();
15185         break;
15186
15187       case BlackPlay:
15188         SetBlackToPlayEvent();
15189         break;
15190
15191       case EmptySquare:
15192         if (gameMode == IcsExamining) {
15193             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15194             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15195             SendToICS(buf);
15196         } else {
15197             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15198                 if(x == BOARD_LEFT-2) {
15199                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15200                     boards[0][y][1] = 0;
15201                 } else
15202                 if(x == BOARD_RGHT+1) {
15203                     if(y >= gameInfo.holdingsSize) break;
15204                     boards[0][y][BOARD_WIDTH-2] = 0;
15205                 } else break;
15206             }
15207             boards[0][y][x] = EmptySquare;
15208             DrawPosition(FALSE, boards[0]);
15209         }
15210         break;
15211
15212       case PromotePiece:
15213         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15214            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15215             selection = (ChessSquare) (PROMOTED piece);
15216         } else if(piece == EmptySquare) selection = WhiteSilver;
15217         else selection = (ChessSquare)((int)piece - 1);
15218         goto defaultlabel;
15219
15220       case DemotePiece:
15221         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15222            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15223             selection = (ChessSquare) (DEMOTED piece);
15224         } else if(piece == EmptySquare) selection = BlackSilver;
15225         else selection = (ChessSquare)((int)piece + 1);
15226         goto defaultlabel;
15227
15228       case WhiteQueen:
15229       case BlackQueen:
15230         if(gameInfo.variant == VariantShatranj ||
15231            gameInfo.variant == VariantXiangqi  ||
15232            gameInfo.variant == VariantCourier  ||
15233            gameInfo.variant == VariantASEAN    ||
15234            gameInfo.variant == VariantMakruk     )
15235             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15236         goto defaultlabel;
15237
15238       case WhiteKing:
15239       case BlackKing:
15240         if(gameInfo.variant == VariantXiangqi)
15241             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15242         if(gameInfo.variant == VariantKnightmate)
15243             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15244       default:
15245         defaultlabel:
15246         if (gameMode == IcsExamining) {
15247             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15248             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15249                      PieceToChar(selection), AAA + x, ONE + y);
15250             SendToICS(buf);
15251         } else {
15252             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15253                 int n;
15254                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15255                     n = PieceToNumber(selection - BlackPawn);
15256                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15257                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15258                     boards[0][BOARD_HEIGHT-1-n][1]++;
15259                 } else
15260                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15261                     n = PieceToNumber(selection);
15262                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15263                     boards[0][n][BOARD_WIDTH-1] = selection;
15264                     boards[0][n][BOARD_WIDTH-2]++;
15265                 }
15266             } else
15267             boards[0][y][x] = selection;
15268             DrawPosition(TRUE, boards[0]);
15269             ClearHighlights();
15270             fromX = fromY = -1;
15271         }
15272         break;
15273     }
15274 }
15275
15276
15277 void
15278 DropMenuEvent (ChessSquare selection, int x, int y)
15279 {
15280     ChessMove moveType;
15281
15282     switch (gameMode) {
15283       case IcsPlayingWhite:
15284       case MachinePlaysBlack:
15285         if (!WhiteOnMove(currentMove)) {
15286             DisplayMoveError(_("It is Black's turn"));
15287             return;
15288         }
15289         moveType = WhiteDrop;
15290         break;
15291       case IcsPlayingBlack:
15292       case MachinePlaysWhite:
15293         if (WhiteOnMove(currentMove)) {
15294             DisplayMoveError(_("It is White's turn"));
15295             return;
15296         }
15297         moveType = BlackDrop;
15298         break;
15299       case EditGame:
15300         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15301         break;
15302       default:
15303         return;
15304     }
15305
15306     if (moveType == BlackDrop && selection < BlackPawn) {
15307       selection = (ChessSquare) ((int) selection
15308                                  + (int) BlackPawn - (int) WhitePawn);
15309     }
15310     if (boards[currentMove][y][x] != EmptySquare) {
15311         DisplayMoveError(_("That square is occupied"));
15312         return;
15313     }
15314
15315     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15316 }
15317
15318 void
15319 AcceptEvent ()
15320 {
15321     /* Accept a pending offer of any kind from opponent */
15322
15323     if (appData.icsActive) {
15324         SendToICS(ics_prefix);
15325         SendToICS("accept\n");
15326     } else if (cmailMsgLoaded) {
15327         if (currentMove == cmailOldMove &&
15328             commentList[cmailOldMove] != NULL &&
15329             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15330                    "Black offers a draw" : "White offers a draw")) {
15331             TruncateGame();
15332             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15333             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15334         } else {
15335             DisplayError(_("There is no pending offer on this move"), 0);
15336             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15337         }
15338     } else {
15339         /* Not used for offers from chess program */
15340     }
15341 }
15342
15343 void
15344 DeclineEvent ()
15345 {
15346     /* Decline a pending offer of any kind from opponent */
15347
15348     if (appData.icsActive) {
15349         SendToICS(ics_prefix);
15350         SendToICS("decline\n");
15351     } else if (cmailMsgLoaded) {
15352         if (currentMove == cmailOldMove &&
15353             commentList[cmailOldMove] != NULL &&
15354             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15355                    "Black offers a draw" : "White offers a draw")) {
15356 #ifdef NOTDEF
15357             AppendComment(cmailOldMove, "Draw declined", TRUE);
15358             DisplayComment(cmailOldMove - 1, "Draw declined");
15359 #endif /*NOTDEF*/
15360         } else {
15361             DisplayError(_("There is no pending offer on this move"), 0);
15362         }
15363     } else {
15364         /* Not used for offers from chess program */
15365     }
15366 }
15367
15368 void
15369 RematchEvent ()
15370 {
15371     /* Issue ICS rematch command */
15372     if (appData.icsActive) {
15373         SendToICS(ics_prefix);
15374         SendToICS("rematch\n");
15375     }
15376 }
15377
15378 void
15379 CallFlagEvent ()
15380 {
15381     /* Call your opponent's flag (claim a win on time) */
15382     if (appData.icsActive) {
15383         SendToICS(ics_prefix);
15384         SendToICS("flag\n");
15385     } else {
15386         switch (gameMode) {
15387           default:
15388             return;
15389           case MachinePlaysWhite:
15390             if (whiteFlag) {
15391                 if (blackFlag)
15392                   GameEnds(GameIsDrawn, "Both players ran out of time",
15393                            GE_PLAYER);
15394                 else
15395                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15396             } else {
15397                 DisplayError(_("Your opponent is not out of time"), 0);
15398             }
15399             break;
15400           case MachinePlaysBlack:
15401             if (blackFlag) {
15402                 if (whiteFlag)
15403                   GameEnds(GameIsDrawn, "Both players ran out of time",
15404                            GE_PLAYER);
15405                 else
15406                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15407             } else {
15408                 DisplayError(_("Your opponent is not out of time"), 0);
15409             }
15410             break;
15411         }
15412     }
15413 }
15414
15415 void
15416 ClockClick (int which)
15417 {       // [HGM] code moved to back-end from winboard.c
15418         if(which) { // black clock
15419           if (gameMode == EditPosition || gameMode == IcsExamining) {
15420             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15421             SetBlackToPlayEvent();
15422           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15423                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15424           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15425           } else if (shiftKey) {
15426             AdjustClock(which, -1);
15427           } else if (gameMode == IcsPlayingWhite ||
15428                      gameMode == MachinePlaysBlack) {
15429             CallFlagEvent();
15430           }
15431         } else { // white clock
15432           if (gameMode == EditPosition || gameMode == IcsExamining) {
15433             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15434             SetWhiteToPlayEvent();
15435           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15436                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15437           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15438           } else if (shiftKey) {
15439             AdjustClock(which, -1);
15440           } else if (gameMode == IcsPlayingBlack ||
15441                    gameMode == MachinePlaysWhite) {
15442             CallFlagEvent();
15443           }
15444         }
15445 }
15446
15447 void
15448 DrawEvent ()
15449 {
15450     /* Offer draw or accept pending draw offer from opponent */
15451
15452     if (appData.icsActive) {
15453         /* Note: tournament rules require draw offers to be
15454            made after you make your move but before you punch
15455            your clock.  Currently ICS doesn't let you do that;
15456            instead, you immediately punch your clock after making
15457            a move, but you can offer a draw at any time. */
15458
15459         SendToICS(ics_prefix);
15460         SendToICS("draw\n");
15461         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15462     } else if (cmailMsgLoaded) {
15463         if (currentMove == cmailOldMove &&
15464             commentList[cmailOldMove] != NULL &&
15465             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15466                    "Black offers a draw" : "White offers a draw")) {
15467             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15468             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15469         } else if (currentMove == cmailOldMove + 1) {
15470             char *offer = WhiteOnMove(cmailOldMove) ?
15471               "White offers a draw" : "Black offers a draw";
15472             AppendComment(currentMove, offer, TRUE);
15473             DisplayComment(currentMove - 1, offer);
15474             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15475         } else {
15476             DisplayError(_("You must make your move before offering a draw"), 0);
15477             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15478         }
15479     } else if (first.offeredDraw) {
15480         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15481     } else {
15482         if (first.sendDrawOffers) {
15483             SendToProgram("draw\n", &first);
15484             userOfferedDraw = TRUE;
15485         }
15486     }
15487 }
15488
15489 void
15490 AdjournEvent ()
15491 {
15492     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15493
15494     if (appData.icsActive) {
15495         SendToICS(ics_prefix);
15496         SendToICS("adjourn\n");
15497     } else {
15498         /* Currently GNU Chess doesn't offer or accept Adjourns */
15499     }
15500 }
15501
15502
15503 void
15504 AbortEvent ()
15505 {
15506     /* Offer Abort or accept pending Abort offer from opponent */
15507
15508     if (appData.icsActive) {
15509         SendToICS(ics_prefix);
15510         SendToICS("abort\n");
15511     } else {
15512         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15513     }
15514 }
15515
15516 void
15517 ResignEvent ()
15518 {
15519     /* Resign.  You can do this even if it's not your turn. */
15520
15521     if (appData.icsActive) {
15522         SendToICS(ics_prefix);
15523         SendToICS("resign\n");
15524     } else {
15525         switch (gameMode) {
15526           case MachinePlaysWhite:
15527             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15528             break;
15529           case MachinePlaysBlack:
15530             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15531             break;
15532           case EditGame:
15533             if (cmailMsgLoaded) {
15534                 TruncateGame();
15535                 if (WhiteOnMove(cmailOldMove)) {
15536                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15537                 } else {
15538                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15539                 }
15540                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15541             }
15542             break;
15543           default:
15544             break;
15545         }
15546     }
15547 }
15548
15549
15550 void
15551 StopObservingEvent ()
15552 {
15553     /* Stop observing current games */
15554     SendToICS(ics_prefix);
15555     SendToICS("unobserve\n");
15556 }
15557
15558 void
15559 StopExaminingEvent ()
15560 {
15561     /* Stop observing current game */
15562     SendToICS(ics_prefix);
15563     SendToICS("unexamine\n");
15564 }
15565
15566 void
15567 ForwardInner (int target)
15568 {
15569     int limit; int oldSeekGraphUp = seekGraphUp;
15570
15571     if (appData.debugMode)
15572         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15573                 target, currentMove, forwardMostMove);
15574
15575     if (gameMode == EditPosition)
15576       return;
15577
15578     seekGraphUp = FALSE;
15579     MarkTargetSquares(1);
15580     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15581
15582     if (gameMode == PlayFromGameFile && !pausing)
15583       PauseEvent();
15584
15585     if (gameMode == IcsExamining && pausing)
15586       limit = pauseExamForwardMostMove;
15587     else
15588       limit = forwardMostMove;
15589
15590     if (target > limit) target = limit;
15591
15592     if (target > 0 && moveList[target - 1][0]) {
15593         int fromX, fromY, toX, toY;
15594         toX = moveList[target - 1][2] - AAA;
15595         toY = moveList[target - 1][3] - ONE;
15596         if (moveList[target - 1][1] == '@') {
15597             if (appData.highlightLastMove) {
15598                 SetHighlights(-1, -1, toX, toY);
15599             }
15600         } else {
15601             int viaX = moveList[target - 1][5] - AAA;
15602             int viaY = moveList[target - 1][6] - ONE;
15603             fromX = moveList[target - 1][0] - AAA;
15604             fromY = moveList[target - 1][1] - ONE;
15605             if (target == currentMove + 1) {
15606                 if(moveList[target - 1][4] == ';') { // multi-leg
15607                     ChessSquare piece = boards[currentMove][viaY][viaX];
15608                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15609                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15610                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15611                     boards[currentMove][viaY][viaX] = piece;
15612                 } else
15613                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15614             }
15615             if (appData.highlightLastMove) {
15616                 SetHighlights(fromX, fromY, toX, toY);
15617             }
15618         }
15619     }
15620     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15621         gameMode == Training || gameMode == PlayFromGameFile ||
15622         gameMode == AnalyzeFile) {
15623         while (currentMove < target) {
15624             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15625             SendMoveToProgram(currentMove++, &first);
15626         }
15627     } else {
15628         currentMove = target;
15629     }
15630
15631     if (gameMode == EditGame || gameMode == EndOfGame) {
15632         whiteTimeRemaining = timeRemaining[0][currentMove];
15633         blackTimeRemaining = timeRemaining[1][currentMove];
15634     }
15635     DisplayBothClocks();
15636     DisplayMove(currentMove - 1);
15637     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15638     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15639     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15640         DisplayComment(currentMove - 1, commentList[currentMove]);
15641     }
15642     ClearMap(); // [HGM] exclude: invalidate map
15643 }
15644
15645
15646 void
15647 ForwardEvent ()
15648 {
15649     if (gameMode == IcsExamining && !pausing) {
15650         SendToICS(ics_prefix);
15651         SendToICS("forward\n");
15652     } else {
15653         ForwardInner(currentMove + 1);
15654     }
15655 }
15656
15657 void
15658 ToEndEvent ()
15659 {
15660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15661         /* to optimze, we temporarily turn off analysis mode while we feed
15662          * the remaining moves to the engine. Otherwise we get analysis output
15663          * after each move.
15664          */
15665         if (first.analysisSupport) {
15666           SendToProgram("exit\nforce\n", &first);
15667           first.analyzing = FALSE;
15668         }
15669     }
15670
15671     if (gameMode == IcsExamining && !pausing) {
15672         SendToICS(ics_prefix);
15673         SendToICS("forward 999999\n");
15674     } else {
15675         ForwardInner(forwardMostMove);
15676     }
15677
15678     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15679         /* we have fed all the moves, so reactivate analysis mode */
15680         SendToProgram("analyze\n", &first);
15681         first.analyzing = TRUE;
15682         /*first.maybeThinking = TRUE;*/
15683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15684     }
15685 }
15686
15687 void
15688 BackwardInner (int target)
15689 {
15690     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15691
15692     if (appData.debugMode)
15693         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15694                 target, currentMove, forwardMostMove);
15695
15696     if (gameMode == EditPosition) return;
15697     seekGraphUp = FALSE;
15698     MarkTargetSquares(1);
15699     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15700     if (currentMove <= backwardMostMove) {
15701         ClearHighlights();
15702         DrawPosition(full_redraw, boards[currentMove]);
15703         return;
15704     }
15705     if (gameMode == PlayFromGameFile && !pausing)
15706       PauseEvent();
15707
15708     if (moveList[target][0]) {
15709         int fromX, fromY, toX, toY;
15710         toX = moveList[target][2] - AAA;
15711         toY = moveList[target][3] - ONE;
15712         if (moveList[target][1] == '@') {
15713             if (appData.highlightLastMove) {
15714                 SetHighlights(-1, -1, toX, toY);
15715             }
15716         } else {
15717             fromX = moveList[target][0] - AAA;
15718             fromY = moveList[target][1] - ONE;
15719             if (target == currentMove - 1) {
15720                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15721             }
15722             if (appData.highlightLastMove) {
15723                 SetHighlights(fromX, fromY, toX, toY);
15724             }
15725         }
15726     }
15727     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15728         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15729         while (currentMove > target) {
15730             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15731                 // null move cannot be undone. Reload program with move history before it.
15732                 int i;
15733                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15734                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15735                 }
15736                 SendBoard(&first, i);
15737               if(second.analyzing) SendBoard(&second, i);
15738                 for(currentMove=i; currentMove<target; currentMove++) {
15739                     SendMoveToProgram(currentMove, &first);
15740                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15741                 }
15742                 break;
15743             }
15744             SendToBoth("undo\n");
15745             currentMove--;
15746         }
15747     } else {
15748         currentMove = target;
15749     }
15750
15751     if (gameMode == EditGame || gameMode == EndOfGame) {
15752         whiteTimeRemaining = timeRemaining[0][currentMove];
15753         blackTimeRemaining = timeRemaining[1][currentMove];
15754     }
15755     DisplayBothClocks();
15756     DisplayMove(currentMove - 1);
15757     DrawPosition(full_redraw, boards[currentMove]);
15758     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15759     // [HGM] PV info: routine tests if comment empty
15760     DisplayComment(currentMove - 1, commentList[currentMove]);
15761     ClearMap(); // [HGM] exclude: invalidate map
15762 }
15763
15764 void
15765 BackwardEvent ()
15766 {
15767     if (gameMode == IcsExamining && !pausing) {
15768         SendToICS(ics_prefix);
15769         SendToICS("backward\n");
15770     } else {
15771         BackwardInner(currentMove - 1);
15772     }
15773 }
15774
15775 void
15776 ToStartEvent ()
15777 {
15778     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15779         /* to optimize, we temporarily turn off analysis mode while we undo
15780          * all the moves. Otherwise we get analysis output after each undo.
15781          */
15782         if (first.analysisSupport) {
15783           SendToProgram("exit\nforce\n", &first);
15784           first.analyzing = FALSE;
15785         }
15786     }
15787
15788     if (gameMode == IcsExamining && !pausing) {
15789         SendToICS(ics_prefix);
15790         SendToICS("backward 999999\n");
15791     } else {
15792         BackwardInner(backwardMostMove);
15793     }
15794
15795     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15796         /* we have fed all the moves, so reactivate analysis mode */
15797         SendToProgram("analyze\n", &first);
15798         first.analyzing = TRUE;
15799         /*first.maybeThinking = TRUE;*/
15800         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15801     }
15802 }
15803
15804 void
15805 ToNrEvent (int to)
15806 {
15807   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15808   if (to >= forwardMostMove) to = forwardMostMove;
15809   if (to <= backwardMostMove) to = backwardMostMove;
15810   if (to < currentMove) {
15811     BackwardInner(to);
15812   } else {
15813     ForwardInner(to);
15814   }
15815 }
15816
15817 void
15818 RevertEvent (Boolean annotate)
15819 {
15820     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15821         return;
15822     }
15823     if (gameMode != IcsExamining) {
15824         DisplayError(_("You are not examining a game"), 0);
15825         return;
15826     }
15827     if (pausing) {
15828         DisplayError(_("You can't revert while pausing"), 0);
15829         return;
15830     }
15831     SendToICS(ics_prefix);
15832     SendToICS("revert\n");
15833 }
15834
15835 void
15836 RetractMoveEvent ()
15837 {
15838     switch (gameMode) {
15839       case MachinePlaysWhite:
15840       case MachinePlaysBlack:
15841         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15842             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15843             return;
15844         }
15845         if (forwardMostMove < 2) return;
15846         currentMove = forwardMostMove = forwardMostMove - 2;
15847         whiteTimeRemaining = timeRemaining[0][currentMove];
15848         blackTimeRemaining = timeRemaining[1][currentMove];
15849         DisplayBothClocks();
15850         DisplayMove(currentMove - 1);
15851         ClearHighlights();/*!! could figure this out*/
15852         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15853         SendToProgram("remove\n", &first);
15854         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15855         break;
15856
15857       case BeginningOfGame:
15858       default:
15859         break;
15860
15861       case IcsPlayingWhite:
15862       case IcsPlayingBlack:
15863         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15864             SendToICS(ics_prefix);
15865             SendToICS("takeback 2\n");
15866         } else {
15867             SendToICS(ics_prefix);
15868             SendToICS("takeback 1\n");
15869         }
15870         break;
15871     }
15872 }
15873
15874 void
15875 MoveNowEvent ()
15876 {
15877     ChessProgramState *cps;
15878
15879     switch (gameMode) {
15880       case MachinePlaysWhite:
15881         if (!WhiteOnMove(forwardMostMove)) {
15882             DisplayError(_("It is your turn"), 0);
15883             return;
15884         }
15885         cps = &first;
15886         break;
15887       case MachinePlaysBlack:
15888         if (WhiteOnMove(forwardMostMove)) {
15889             DisplayError(_("It is your turn"), 0);
15890             return;
15891         }
15892         cps = &first;
15893         break;
15894       case TwoMachinesPlay:
15895         if (WhiteOnMove(forwardMostMove) ==
15896             (first.twoMachinesColor[0] == 'w')) {
15897             cps = &first;
15898         } else {
15899             cps = &second;
15900         }
15901         break;
15902       case BeginningOfGame:
15903       default:
15904         return;
15905     }
15906     SendToProgram("?\n", cps);
15907 }
15908
15909 void
15910 TruncateGameEvent ()
15911 {
15912     EditGameEvent();
15913     if (gameMode != EditGame) return;
15914     TruncateGame();
15915 }
15916
15917 void
15918 TruncateGame ()
15919 {
15920     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15921     if (forwardMostMove > currentMove) {
15922         if (gameInfo.resultDetails != NULL) {
15923             free(gameInfo.resultDetails);
15924             gameInfo.resultDetails = NULL;
15925             gameInfo.result = GameUnfinished;
15926         }
15927         forwardMostMove = currentMove;
15928         HistorySet(parseList, backwardMostMove, forwardMostMove,
15929                    currentMove-1);
15930     }
15931 }
15932
15933 void
15934 HintEvent ()
15935 {
15936     if (appData.noChessProgram) return;
15937     switch (gameMode) {
15938       case MachinePlaysWhite:
15939         if (WhiteOnMove(forwardMostMove)) {
15940             DisplayError(_("Wait until your turn."), 0);
15941             return;
15942         }
15943         break;
15944       case BeginningOfGame:
15945       case MachinePlaysBlack:
15946         if (!WhiteOnMove(forwardMostMove)) {
15947             DisplayError(_("Wait until your turn."), 0);
15948             return;
15949         }
15950         break;
15951       default:
15952         DisplayError(_("No hint available"), 0);
15953         return;
15954     }
15955     SendToProgram("hint\n", &first);
15956     hintRequested = TRUE;
15957 }
15958
15959 int
15960 SaveSelected (FILE *g, int dummy, char *dummy2)
15961 {
15962     ListGame * lg = (ListGame *) gameList.head;
15963     int nItem, cnt=0;
15964     FILE *f;
15965
15966     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15967         DisplayError(_("Game list not loaded or empty"), 0);
15968         return 0;
15969     }
15970
15971     creatingBook = TRUE; // suppresses stuff during load game
15972
15973     /* Get list size */
15974     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15975         if(lg->position >= 0) { // selected?
15976             LoadGame(f, nItem, "", TRUE);
15977             SaveGamePGN2(g); // leaves g open
15978             cnt++; DoEvents();
15979         }
15980         lg = (ListGame *) lg->node.succ;
15981     }
15982
15983     fclose(g);
15984     creatingBook = FALSE;
15985
15986     return cnt;
15987 }
15988
15989 void
15990 CreateBookEvent ()
15991 {
15992     ListGame * lg = (ListGame *) gameList.head;
15993     FILE *f, *g;
15994     int nItem;
15995     static int secondTime = FALSE;
15996
15997     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15998         DisplayError(_("Game list not loaded or empty"), 0);
15999         return;
16000     }
16001
16002     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16003         fclose(g);
16004         secondTime++;
16005         DisplayNote(_("Book file exists! Try again for overwrite."));
16006         return;
16007     }
16008
16009     creatingBook = TRUE;
16010     secondTime = FALSE;
16011
16012     /* Get list size */
16013     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16014         if(lg->position >= 0) {
16015             LoadGame(f, nItem, "", TRUE);
16016             AddGameToBook(TRUE);
16017             DoEvents();
16018         }
16019         lg = (ListGame *) lg->node.succ;
16020     }
16021
16022     creatingBook = FALSE;
16023     FlushBook();
16024 }
16025
16026 void
16027 BookEvent ()
16028 {
16029     if (appData.noChessProgram) return;
16030     switch (gameMode) {
16031       case MachinePlaysWhite:
16032         if (WhiteOnMove(forwardMostMove)) {
16033             DisplayError(_("Wait until your turn."), 0);
16034             return;
16035         }
16036         break;
16037       case BeginningOfGame:
16038       case MachinePlaysBlack:
16039         if (!WhiteOnMove(forwardMostMove)) {
16040             DisplayError(_("Wait until your turn."), 0);
16041             return;
16042         }
16043         break;
16044       case EditPosition:
16045         EditPositionDone(TRUE);
16046         break;
16047       case TwoMachinesPlay:
16048         return;
16049       default:
16050         break;
16051     }
16052     SendToProgram("bk\n", &first);
16053     bookOutput[0] = NULLCHAR;
16054     bookRequested = TRUE;
16055 }
16056
16057 void
16058 AboutGameEvent ()
16059 {
16060     char *tags = PGNTags(&gameInfo);
16061     TagsPopUp(tags, CmailMsg());
16062     free(tags);
16063 }
16064
16065 /* end button procedures */
16066
16067 void
16068 PrintPosition (FILE *fp, int move)
16069 {
16070     int i, j;
16071
16072     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16073         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16074             char c = PieceToChar(boards[move][i][j]);
16075             fputc(c == 'x' ? '.' : c, fp);
16076             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16077         }
16078     }
16079     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16080       fprintf(fp, "white to play\n");
16081     else
16082       fprintf(fp, "black to play\n");
16083 }
16084
16085 void
16086 PrintOpponents (FILE *fp)
16087 {
16088     if (gameInfo.white != NULL) {
16089         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16090     } else {
16091         fprintf(fp, "\n");
16092     }
16093 }
16094
16095 /* Find last component of program's own name, using some heuristics */
16096 void
16097 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16098 {
16099     char *p, *q, c;
16100     int local = (strcmp(host, "localhost") == 0);
16101     while (!local && (p = strchr(prog, ';')) != NULL) {
16102         p++;
16103         while (*p == ' ') p++;
16104         prog = p;
16105     }
16106     if (*prog == '"' || *prog == '\'') {
16107         q = strchr(prog + 1, *prog);
16108     } else {
16109         q = strchr(prog, ' ');
16110     }
16111     if (q == NULL) q = prog + strlen(prog);
16112     p = q;
16113     while (p >= prog && *p != '/' && *p != '\\') p--;
16114     p++;
16115     if(p == prog && *p == '"') p++;
16116     c = *q; *q = 0;
16117     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16118     memcpy(buf, p, q - p);
16119     buf[q - p] = NULLCHAR;
16120     if (!local) {
16121         strcat(buf, "@");
16122         strcat(buf, host);
16123     }
16124 }
16125
16126 char *
16127 TimeControlTagValue ()
16128 {
16129     char buf[MSG_SIZ];
16130     if (!appData.clockMode) {
16131       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16132     } else if (movesPerSession > 0) {
16133       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16134     } else if (timeIncrement == 0) {
16135       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16136     } else {
16137       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16138     }
16139     return StrSave(buf);
16140 }
16141
16142 void
16143 SetGameInfo ()
16144 {
16145     /* This routine is used only for certain modes */
16146     VariantClass v = gameInfo.variant;
16147     ChessMove r = GameUnfinished;
16148     char *p = NULL;
16149
16150     if(keepInfo) return;
16151
16152     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16153         r = gameInfo.result;
16154         p = gameInfo.resultDetails;
16155         gameInfo.resultDetails = NULL;
16156     }
16157     ClearGameInfo(&gameInfo);
16158     gameInfo.variant = v;
16159
16160     switch (gameMode) {
16161       case MachinePlaysWhite:
16162         gameInfo.event = StrSave( appData.pgnEventHeader );
16163         gameInfo.site = StrSave(HostName());
16164         gameInfo.date = PGNDate();
16165         gameInfo.round = StrSave("-");
16166         gameInfo.white = StrSave(first.tidy);
16167         gameInfo.black = StrSave(UserName());
16168         gameInfo.timeControl = TimeControlTagValue();
16169         break;
16170
16171       case MachinePlaysBlack:
16172         gameInfo.event = StrSave( appData.pgnEventHeader );
16173         gameInfo.site = StrSave(HostName());
16174         gameInfo.date = PGNDate();
16175         gameInfo.round = StrSave("-");
16176         gameInfo.white = StrSave(UserName());
16177         gameInfo.black = StrSave(first.tidy);
16178         gameInfo.timeControl = TimeControlTagValue();
16179         break;
16180
16181       case TwoMachinesPlay:
16182         gameInfo.event = StrSave( appData.pgnEventHeader );
16183         gameInfo.site = StrSave(HostName());
16184         gameInfo.date = PGNDate();
16185         if (roundNr > 0) {
16186             char buf[MSG_SIZ];
16187             snprintf(buf, MSG_SIZ, "%d", roundNr);
16188             gameInfo.round = StrSave(buf);
16189         } else {
16190             gameInfo.round = StrSave("-");
16191         }
16192         if (first.twoMachinesColor[0] == 'w') {
16193             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16194             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16195         } else {
16196             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16197             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16198         }
16199         gameInfo.timeControl = TimeControlTagValue();
16200         break;
16201
16202       case EditGame:
16203         gameInfo.event = StrSave("Edited game");
16204         gameInfo.site = StrSave(HostName());
16205         gameInfo.date = PGNDate();
16206         gameInfo.round = StrSave("-");
16207         gameInfo.white = StrSave("-");
16208         gameInfo.black = StrSave("-");
16209         gameInfo.result = r;
16210         gameInfo.resultDetails = p;
16211         break;
16212
16213       case EditPosition:
16214         gameInfo.event = StrSave("Edited position");
16215         gameInfo.site = StrSave(HostName());
16216         gameInfo.date = PGNDate();
16217         gameInfo.round = StrSave("-");
16218         gameInfo.white = StrSave("-");
16219         gameInfo.black = StrSave("-");
16220         break;
16221
16222       case IcsPlayingWhite:
16223       case IcsPlayingBlack:
16224       case IcsObserving:
16225       case IcsExamining:
16226         break;
16227
16228       case PlayFromGameFile:
16229         gameInfo.event = StrSave("Game from non-PGN file");
16230         gameInfo.site = StrSave(HostName());
16231         gameInfo.date = PGNDate();
16232         gameInfo.round = StrSave("-");
16233         gameInfo.white = StrSave("?");
16234         gameInfo.black = StrSave("?");
16235         break;
16236
16237       default:
16238         break;
16239     }
16240 }
16241
16242 void
16243 ReplaceComment (int index, char *text)
16244 {
16245     int len;
16246     char *p;
16247     float score;
16248
16249     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16250        pvInfoList[index-1].depth == len &&
16251        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16252        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16253     while (*text == '\n') text++;
16254     len = strlen(text);
16255     while (len > 0 && text[len - 1] == '\n') len--;
16256
16257     if (commentList[index] != NULL)
16258       free(commentList[index]);
16259
16260     if (len == 0) {
16261         commentList[index] = NULL;
16262         return;
16263     }
16264   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16265       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16266       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16267     commentList[index] = (char *) malloc(len + 2);
16268     strncpy(commentList[index], text, len);
16269     commentList[index][len] = '\n';
16270     commentList[index][len + 1] = NULLCHAR;
16271   } else {
16272     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16273     char *p;
16274     commentList[index] = (char *) malloc(len + 7);
16275     safeStrCpy(commentList[index], "{\n", 3);
16276     safeStrCpy(commentList[index]+2, text, len+1);
16277     commentList[index][len+2] = NULLCHAR;
16278     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16279     strcat(commentList[index], "\n}\n");
16280   }
16281 }
16282
16283 void
16284 CrushCRs (char *text)
16285 {
16286   char *p = text;
16287   char *q = text;
16288   char ch;
16289
16290   do {
16291     ch = *p++;
16292     if (ch == '\r') continue;
16293     *q++ = ch;
16294   } while (ch != '\0');
16295 }
16296
16297 void
16298 AppendComment (int index, char *text, Boolean addBraces)
16299 /* addBraces  tells if we should add {} */
16300 {
16301     int oldlen, len;
16302     char *old;
16303
16304 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16305     if(addBraces == 3) addBraces = 0; else // force appending literally
16306     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16307
16308     CrushCRs(text);
16309     while (*text == '\n') text++;
16310     len = strlen(text);
16311     while (len > 0 && text[len - 1] == '\n') len--;
16312     text[len] = NULLCHAR;
16313
16314     if (len == 0) return;
16315
16316     if (commentList[index] != NULL) {
16317       Boolean addClosingBrace = addBraces;
16318         old = commentList[index];
16319         oldlen = strlen(old);
16320         while(commentList[index][oldlen-1] ==  '\n')
16321           commentList[index][--oldlen] = NULLCHAR;
16322         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16323         safeStrCpy(commentList[index], old, oldlen + len + 6);
16324         free(old);
16325         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16326         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16327           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16328           while (*text == '\n') { text++; len--; }
16329           commentList[index][--oldlen] = NULLCHAR;
16330       }
16331         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16332         else          strcat(commentList[index], "\n");
16333         strcat(commentList[index], text);
16334         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16335         else          strcat(commentList[index], "\n");
16336     } else {
16337         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16338         if(addBraces)
16339           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16340         else commentList[index][0] = NULLCHAR;
16341         strcat(commentList[index], text);
16342         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16343         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16344     }
16345 }
16346
16347 static char *
16348 FindStr (char * text, char * sub_text)
16349 {
16350     char * result = strstr( text, sub_text );
16351
16352     if( result != NULL ) {
16353         result += strlen( sub_text );
16354     }
16355
16356     return result;
16357 }
16358
16359 /* [AS] Try to extract PV info from PGN comment */
16360 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16361 char *
16362 GetInfoFromComment (int index, char * text)
16363 {
16364     char * sep = text, *p;
16365
16366     if( text != NULL && index > 0 ) {
16367         int score = 0;
16368         int depth = 0;
16369         int time = -1, sec = 0, deci;
16370         char * s_eval = FindStr( text, "[%eval " );
16371         char * s_emt = FindStr( text, "[%emt " );
16372 #if 0
16373         if( s_eval != NULL || s_emt != NULL ) {
16374 #else
16375         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16376 #endif
16377             /* New style */
16378             char delim;
16379
16380             if( s_eval != NULL ) {
16381                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16382                     return text;
16383                 }
16384
16385                 if( delim != ']' ) {
16386                     return text;
16387                 }
16388             }
16389
16390             if( s_emt != NULL ) {
16391             }
16392                 return text;
16393         }
16394         else {
16395             /* We expect something like: [+|-]nnn.nn/dd */
16396             int score_lo = 0;
16397
16398             if(*text != '{') return text; // [HGM] braces: must be normal comment
16399
16400             sep = strchr( text, '/' );
16401             if( sep == NULL || sep < (text+4) ) {
16402                 return text;
16403             }
16404
16405             p = text;
16406             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16407             if(p[1] == '(') { // comment starts with PV
16408                p = strchr(p, ')'); // locate end of PV
16409                if(p == NULL || sep < p+5) return text;
16410                // at this point we have something like "{(.*) +0.23/6 ..."
16411                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16412                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16413                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16414             }
16415             time = -1; sec = -1; deci = -1;
16416             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16417                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16418                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16419                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16420                 return text;
16421             }
16422
16423             if( score_lo < 0 || score_lo >= 100 ) {
16424                 return text;
16425             }
16426
16427             if(sec >= 0) time = 600*time + 10*sec; else
16428             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16429
16430             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16431
16432             /* [HGM] PV time: now locate end of PV info */
16433             while( *++sep >= '0' && *sep <= '9'); // strip depth
16434             if(time >= 0)
16435             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16436             if(sec >= 0)
16437             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16438             if(deci >= 0)
16439             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16440             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16441         }
16442
16443         if( depth <= 0 ) {
16444             return text;
16445         }
16446
16447         if( time < 0 ) {
16448             time = -1;
16449         }
16450
16451         pvInfoList[index-1].depth = depth;
16452         pvInfoList[index-1].score = score;
16453         pvInfoList[index-1].time  = 10*time; // centi-sec
16454         if(*sep == '}') *sep = 0; else *--sep = '{';
16455         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16456     }
16457     return sep;
16458 }
16459
16460 void
16461 SendToProgram (char *message, ChessProgramState *cps)
16462 {
16463     int count, outCount, error;
16464     char buf[MSG_SIZ];
16465
16466     if (cps->pr == NoProc) return;
16467     Attention(cps);
16468
16469     if (appData.debugMode) {
16470         TimeMark now;
16471         GetTimeMark(&now);
16472         fprintf(debugFP, "%ld >%-6s: %s",
16473                 SubtractTimeMarks(&now, &programStartTime),
16474                 cps->which, message);
16475         if(serverFP)
16476             fprintf(serverFP, "%ld >%-6s: %s",
16477                 SubtractTimeMarks(&now, &programStartTime),
16478                 cps->which, message), fflush(serverFP);
16479     }
16480
16481     count = strlen(message);
16482     outCount = OutputToProcess(cps->pr, message, count, &error);
16483     if (outCount < count && !exiting
16484                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16485       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16486       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16487         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16488             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16489                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16490                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16491                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16492             } else {
16493                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16494                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16495                 gameInfo.result = res;
16496             }
16497             gameInfo.resultDetails = StrSave(buf);
16498         }
16499         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16500         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16501     }
16502 }
16503
16504 void
16505 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16506 {
16507     char *end_str;
16508     char buf[MSG_SIZ];
16509     ChessProgramState *cps = (ChessProgramState *)closure;
16510
16511     if (isr != cps->isr) return; /* Killed intentionally */
16512     if (count <= 0) {
16513         if (count == 0) {
16514             RemoveInputSource(cps->isr);
16515             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16516                     _(cps->which), cps->program);
16517             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16518             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16519                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16520                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16521                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16522                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16523                 } else {
16524                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16525                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16526                     gameInfo.result = res;
16527                 }
16528                 gameInfo.resultDetails = StrSave(buf);
16529             }
16530             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16531             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16532         } else {
16533             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16534                     _(cps->which), cps->program);
16535             RemoveInputSource(cps->isr);
16536
16537             /* [AS] Program is misbehaving badly... kill it */
16538             if( count == -2 ) {
16539                 DestroyChildProcess( cps->pr, 9 );
16540                 cps->pr = NoProc;
16541             }
16542
16543             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16544         }
16545         return;
16546     }
16547
16548     if ((end_str = strchr(message, '\r')) != NULL)
16549       *end_str = NULLCHAR;
16550     if ((end_str = strchr(message, '\n')) != NULL)
16551       *end_str = NULLCHAR;
16552
16553     if (appData.debugMode) {
16554         TimeMark now; int print = 1;
16555         char *quote = ""; char c; int i;
16556
16557         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16558                 char start = message[0];
16559                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16560                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16561                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16562                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16563                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16564                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16565                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16566                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16567                    sscanf(message, "hint: %c", &c)!=1 &&
16568                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16569                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16570                     print = (appData.engineComments >= 2);
16571                 }
16572                 message[0] = start; // restore original message
16573         }
16574         if(print) {
16575                 GetTimeMark(&now);
16576                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16577                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16578                         quote,
16579                         message);
16580                 if(serverFP)
16581                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16582                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16583                         quote,
16584                         message), fflush(serverFP);
16585         }
16586     }
16587
16588     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16589     if (appData.icsEngineAnalyze) {
16590         if (strstr(message, "whisper") != NULL ||
16591              strstr(message, "kibitz") != NULL ||
16592             strstr(message, "tellics") != NULL) return;
16593     }
16594
16595     HandleMachineMove(message, cps);
16596 }
16597
16598
16599 void
16600 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16601 {
16602     char buf[MSG_SIZ];
16603     int seconds;
16604
16605     if( timeControl_2 > 0 ) {
16606         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16607             tc = timeControl_2;
16608         }
16609     }
16610     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16611     inc /= cps->timeOdds;
16612     st  /= cps->timeOdds;
16613
16614     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16615
16616     if (st > 0) {
16617       /* Set exact time per move, normally using st command */
16618       if (cps->stKludge) {
16619         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16620         seconds = st % 60;
16621         if (seconds == 0) {
16622           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16623         } else {
16624           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16625         }
16626       } else {
16627         snprintf(buf, MSG_SIZ, "st %d\n", st);
16628       }
16629     } else {
16630       /* Set conventional or incremental time control, using level command */
16631       if (seconds == 0) {
16632         /* Note old gnuchess bug -- minutes:seconds used to not work.
16633            Fixed in later versions, but still avoid :seconds
16634            when seconds is 0. */
16635         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16636       } else {
16637         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16638                  seconds, inc/1000.);
16639       }
16640     }
16641     SendToProgram(buf, cps);
16642
16643     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16644     /* Orthogonally, limit search to given depth */
16645     if (sd > 0) {
16646       if (cps->sdKludge) {
16647         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16648       } else {
16649         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16650       }
16651       SendToProgram(buf, cps);
16652     }
16653
16654     if(cps->nps >= 0) { /* [HGM] nps */
16655         if(cps->supportsNPS == FALSE)
16656           cps->nps = -1; // don't use if engine explicitly says not supported!
16657         else {
16658           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16659           SendToProgram(buf, cps);
16660         }
16661     }
16662 }
16663
16664 ChessProgramState *
16665 WhitePlayer ()
16666 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16667 {
16668     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16669        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16670         return &second;
16671     return &first;
16672 }
16673
16674 void
16675 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16676 {
16677     char message[MSG_SIZ];
16678     long time, otime;
16679
16680     /* Note: this routine must be called when the clocks are stopped
16681        or when they have *just* been set or switched; otherwise
16682        it will be off by the time since the current tick started.
16683     */
16684     if (machineWhite) {
16685         time = whiteTimeRemaining / 10;
16686         otime = blackTimeRemaining / 10;
16687     } else {
16688         time = blackTimeRemaining / 10;
16689         otime = whiteTimeRemaining / 10;
16690     }
16691     /* [HGM] translate opponent's time by time-odds factor */
16692     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16693
16694     if (time <= 0) time = 1;
16695     if (otime <= 0) otime = 1;
16696
16697     snprintf(message, MSG_SIZ, "time %ld\n", time);
16698     SendToProgram(message, cps);
16699
16700     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16701     SendToProgram(message, cps);
16702 }
16703
16704 char *
16705 EngineDefinedVariant (ChessProgramState *cps, int n)
16706 {   // return name of n-th unknown variant that engine supports
16707     static char buf[MSG_SIZ];
16708     char *p, *s = cps->variants;
16709     if(!s) return NULL;
16710     do { // parse string from variants feature
16711       VariantClass v;
16712         p = strchr(s, ',');
16713         if(p) *p = NULLCHAR;
16714       v = StringToVariant(s);
16715       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16716         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16717             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16718                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16719                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16720                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16721             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16722         }
16723         if(p) *p++ = ',';
16724         if(n < 0) return buf;
16725     } while(s = p);
16726     return NULL;
16727 }
16728
16729 int
16730 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16731 {
16732   char buf[MSG_SIZ];
16733   int len = strlen(name);
16734   int val;
16735
16736   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16737     (*p) += len + 1;
16738     sscanf(*p, "%d", &val);
16739     *loc = (val != 0);
16740     while (**p && **p != ' ')
16741       (*p)++;
16742     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16743     SendToProgram(buf, cps);
16744     return TRUE;
16745   }
16746   return FALSE;
16747 }
16748
16749 int
16750 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16751 {
16752   char buf[MSG_SIZ];
16753   int len = strlen(name);
16754   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16755     (*p) += len + 1;
16756     sscanf(*p, "%d", loc);
16757     while (**p && **p != ' ') (*p)++;
16758     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16759     SendToProgram(buf, cps);
16760     return TRUE;
16761   }
16762   return FALSE;
16763 }
16764
16765 int
16766 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16767 {
16768   char buf[MSG_SIZ];
16769   int len = strlen(name);
16770   if (strncmp((*p), name, len) == 0
16771       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16772     (*p) += len + 2;
16773     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16774     sscanf(*p, "%[^\"]", *loc);
16775     while (**p && **p != '\"') (*p)++;
16776     if (**p == '\"') (*p)++;
16777     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16778     SendToProgram(buf, cps);
16779     return TRUE;
16780   }
16781   return FALSE;
16782 }
16783
16784 int
16785 ParseOption (Option *opt, ChessProgramState *cps)
16786 // [HGM] options: process the string that defines an engine option, and determine
16787 // name, type, default value, and allowed value range
16788 {
16789         char *p, *q, buf[MSG_SIZ];
16790         int n, min = (-1)<<31, max = 1<<31, def;
16791
16792         if(p = strstr(opt->name, " -spin ")) {
16793             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16794             if(max < min) max = min; // enforce consistency
16795             if(def < min) def = min;
16796             if(def > max) def = max;
16797             opt->value = def;
16798             opt->min = min;
16799             opt->max = max;
16800             opt->type = Spin;
16801         } else if((p = strstr(opt->name, " -slider "))) {
16802             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16803             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16804             if(max < min) max = min; // enforce consistency
16805             if(def < min) def = min;
16806             if(def > max) def = max;
16807             opt->value = def;
16808             opt->min = min;
16809             opt->max = max;
16810             opt->type = Spin; // Slider;
16811         } else if((p = strstr(opt->name, " -string "))) {
16812             opt->textValue = p+9;
16813             opt->type = TextBox;
16814         } else if((p = strstr(opt->name, " -file "))) {
16815             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16816             opt->textValue = p+7;
16817             opt->type = FileName; // FileName;
16818         } else if((p = strstr(opt->name, " -path "))) {
16819             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16820             opt->textValue = p+7;
16821             opt->type = PathName; // PathName;
16822         } else if(p = strstr(opt->name, " -check ")) {
16823             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16824             opt->value = (def != 0);
16825             opt->type = CheckBox;
16826         } else if(p = strstr(opt->name, " -combo ")) {
16827             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16828             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16829             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16830             opt->value = n = 0;
16831             while(q = StrStr(q, " /// ")) {
16832                 n++; *q = 0;    // count choices, and null-terminate each of them
16833                 q += 5;
16834                 if(*q == '*') { // remember default, which is marked with * prefix
16835                     q++;
16836                     opt->value = n;
16837                 }
16838                 cps->comboList[cps->comboCnt++] = q;
16839             }
16840             cps->comboList[cps->comboCnt++] = NULL;
16841             opt->max = n + 1;
16842             opt->type = ComboBox;
16843         } else if(p = strstr(opt->name, " -button")) {
16844             opt->type = Button;
16845         } else if(p = strstr(opt->name, " -save")) {
16846             opt->type = SaveButton;
16847         } else return FALSE;
16848         *p = 0; // terminate option name
16849         // now look if the command-line options define a setting for this engine option.
16850         if(cps->optionSettings && cps->optionSettings[0])
16851             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16852         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16853           snprintf(buf, MSG_SIZ, "option %s", p);
16854                 if(p = strstr(buf, ",")) *p = 0;
16855                 if(q = strchr(buf, '=')) switch(opt->type) {
16856                     case ComboBox:
16857                         for(n=0; n<opt->max; n++)
16858                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16859                         break;
16860                     case TextBox:
16861                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16862                         break;
16863                     case Spin:
16864                     case CheckBox:
16865                         opt->value = atoi(q+1);
16866                     default:
16867                         break;
16868                 }
16869                 strcat(buf, "\n");
16870                 SendToProgram(buf, cps);
16871         }
16872         return TRUE;
16873 }
16874
16875 void
16876 FeatureDone (ChessProgramState *cps, int val)
16877 {
16878   DelayedEventCallback cb = GetDelayedEvent();
16879   if ((cb == InitBackEnd3 && cps == &first) ||
16880       (cb == SettingsMenuIfReady && cps == &second) ||
16881       (cb == LoadEngine) ||
16882       (cb == TwoMachinesEventIfReady)) {
16883     CancelDelayedEvent();
16884     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16885   }
16886   cps->initDone = val;
16887   if(val) cps->reload = FALSE;
16888 }
16889
16890 /* Parse feature command from engine */
16891 void
16892 ParseFeatures (char *args, ChessProgramState *cps)
16893 {
16894   char *p = args;
16895   char *q = NULL;
16896   int val;
16897   char buf[MSG_SIZ];
16898
16899   for (;;) {
16900     while (*p == ' ') p++;
16901     if (*p == NULLCHAR) return;
16902
16903     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16904     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16905     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16906     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16907     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16908     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16909     if (BoolFeature(&p, "reuse", &val, cps)) {
16910       /* Engine can disable reuse, but can't enable it if user said no */
16911       if (!val) cps->reuse = FALSE;
16912       continue;
16913     }
16914     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16915     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16916       if (gameMode == TwoMachinesPlay) {
16917         DisplayTwoMachinesTitle();
16918       } else {
16919         DisplayTitle("");
16920       }
16921       continue;
16922     }
16923     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16924     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16925     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16926     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16927     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16928     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16929     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16930     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16931     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16932     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16933     if (IntFeature(&p, "done", &val, cps)) {
16934       FeatureDone(cps, val);
16935       continue;
16936     }
16937     /* Added by Tord: */
16938     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16939     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16940     /* End of additions by Tord */
16941
16942     /* [HGM] added features: */
16943     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16944     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16945     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16946     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16947     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16948     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16949     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16950     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16951         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16952         FREE(cps->option[cps->nrOptions].name);
16953         cps->option[cps->nrOptions].name = q; q = NULL;
16954         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16955           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16956             SendToProgram(buf, cps);
16957             continue;
16958         }
16959         if(cps->nrOptions >= MAX_OPTIONS) {
16960             cps->nrOptions--;
16961             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16962             DisplayError(buf, 0);
16963         }
16964         continue;
16965     }
16966     /* End of additions by HGM */
16967
16968     /* unknown feature: complain and skip */
16969     q = p;
16970     while (*q && *q != '=') q++;
16971     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16972     SendToProgram(buf, cps);
16973     p = q;
16974     if (*p == '=') {
16975       p++;
16976       if (*p == '\"') {
16977         p++;
16978         while (*p && *p != '\"') p++;
16979         if (*p == '\"') p++;
16980       } else {
16981         while (*p && *p != ' ') p++;
16982       }
16983     }
16984   }
16985
16986 }
16987
16988 void
16989 PeriodicUpdatesEvent (int newState)
16990 {
16991     if (newState == appData.periodicUpdates)
16992       return;
16993
16994     appData.periodicUpdates=newState;
16995
16996     /* Display type changes, so update it now */
16997 //    DisplayAnalysis();
16998
16999     /* Get the ball rolling again... */
17000     if (newState) {
17001         AnalysisPeriodicEvent(1);
17002         StartAnalysisClock();
17003     }
17004 }
17005
17006 void
17007 PonderNextMoveEvent (int newState)
17008 {
17009     if (newState == appData.ponderNextMove) return;
17010     if (gameMode == EditPosition) EditPositionDone(TRUE);
17011     if (newState) {
17012         SendToProgram("hard\n", &first);
17013         if (gameMode == TwoMachinesPlay) {
17014             SendToProgram("hard\n", &second);
17015         }
17016     } else {
17017         SendToProgram("easy\n", &first);
17018         thinkOutput[0] = NULLCHAR;
17019         if (gameMode == TwoMachinesPlay) {
17020             SendToProgram("easy\n", &second);
17021         }
17022     }
17023     appData.ponderNextMove = newState;
17024 }
17025
17026 void
17027 NewSettingEvent (int option, int *feature, char *command, int value)
17028 {
17029     char buf[MSG_SIZ];
17030
17031     if (gameMode == EditPosition) EditPositionDone(TRUE);
17032     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17033     if(feature == NULL || *feature) SendToProgram(buf, &first);
17034     if (gameMode == TwoMachinesPlay) {
17035         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17036     }
17037 }
17038
17039 void
17040 ShowThinkingEvent ()
17041 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17042 {
17043     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17044     int newState = appData.showThinking
17045         // [HGM] thinking: other features now need thinking output as well
17046         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17047
17048     if (oldState == newState) return;
17049     oldState = newState;
17050     if (gameMode == EditPosition) EditPositionDone(TRUE);
17051     if (oldState) {
17052         SendToProgram("post\n", &first);
17053         if (gameMode == TwoMachinesPlay) {
17054             SendToProgram("post\n", &second);
17055         }
17056     } else {
17057         SendToProgram("nopost\n", &first);
17058         thinkOutput[0] = NULLCHAR;
17059         if (gameMode == TwoMachinesPlay) {
17060             SendToProgram("nopost\n", &second);
17061         }
17062     }
17063 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17064 }
17065
17066 void
17067 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17068 {
17069   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17070   if (pr == NoProc) return;
17071   AskQuestion(title, question, replyPrefix, pr);
17072 }
17073
17074 void
17075 TypeInEvent (char firstChar)
17076 {
17077     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17078         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17079         gameMode == AnalyzeMode || gameMode == EditGame ||
17080         gameMode == EditPosition || gameMode == IcsExamining ||
17081         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17082         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17083                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17084                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17085         gameMode == Training) PopUpMoveDialog(firstChar);
17086 }
17087
17088 void
17089 TypeInDoneEvent (char *move)
17090 {
17091         Board board;
17092         int n, fromX, fromY, toX, toY;
17093         char promoChar;
17094         ChessMove moveType;
17095
17096         // [HGM] FENedit
17097         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17098                 EditPositionPasteFEN(move);
17099                 return;
17100         }
17101         // [HGM] movenum: allow move number to be typed in any mode
17102         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17103           ToNrEvent(2*n-1);
17104           return;
17105         }
17106         // undocumented kludge: allow command-line option to be typed in!
17107         // (potentially fatal, and does not implement the effect of the option.)
17108         // should only be used for options that are values on which future decisions will be made,
17109         // and definitely not on options that would be used during initialization.
17110         if(strstr(move, "!!! -") == move) {
17111             ParseArgsFromString(move+4);
17112             return;
17113         }
17114
17115       if (gameMode != EditGame && currentMove != forwardMostMove &&
17116         gameMode != Training) {
17117         DisplayMoveError(_("Displayed move is not current"));
17118       } else {
17119         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17120           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17121         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17122         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17123           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17124           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17125         } else {
17126           DisplayMoveError(_("Could not parse move"));
17127         }
17128       }
17129 }
17130
17131 void
17132 DisplayMove (int moveNumber)
17133 {
17134     char message[MSG_SIZ];
17135     char res[MSG_SIZ];
17136     char cpThinkOutput[MSG_SIZ];
17137
17138     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17139
17140     if (moveNumber == forwardMostMove - 1 ||
17141         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17142
17143         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17144
17145         if (strchr(cpThinkOutput, '\n')) {
17146             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17147         }
17148     } else {
17149         *cpThinkOutput = NULLCHAR;
17150     }
17151
17152     /* [AS] Hide thinking from human user */
17153     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17154         *cpThinkOutput = NULLCHAR;
17155         if( thinkOutput[0] != NULLCHAR ) {
17156             int i;
17157
17158             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17159                 cpThinkOutput[i] = '.';
17160             }
17161             cpThinkOutput[i] = NULLCHAR;
17162             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17163         }
17164     }
17165
17166     if (moveNumber == forwardMostMove - 1 &&
17167         gameInfo.resultDetails != NULL) {
17168         if (gameInfo.resultDetails[0] == NULLCHAR) {
17169           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17170         } else {
17171           snprintf(res, MSG_SIZ, " {%s} %s",
17172                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17173         }
17174     } else {
17175         res[0] = NULLCHAR;
17176     }
17177
17178     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17179         DisplayMessage(res, cpThinkOutput);
17180     } else {
17181       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17182                 WhiteOnMove(moveNumber) ? " " : ".. ",
17183                 parseList[moveNumber], res);
17184         DisplayMessage(message, cpThinkOutput);
17185     }
17186 }
17187
17188 void
17189 DisplayComment (int moveNumber, char *text)
17190 {
17191     char title[MSG_SIZ];
17192
17193     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17194       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17195     } else {
17196       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17197               WhiteOnMove(moveNumber) ? " " : ".. ",
17198               parseList[moveNumber]);
17199     }
17200     if (text != NULL && (appData.autoDisplayComment || commentUp))
17201         CommentPopUp(title, text);
17202 }
17203
17204 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17205  * might be busy thinking or pondering.  It can be omitted if your
17206  * gnuchess is configured to stop thinking immediately on any user
17207  * input.  However, that gnuchess feature depends on the FIONREAD
17208  * ioctl, which does not work properly on some flavors of Unix.
17209  */
17210 void
17211 Attention (ChessProgramState *cps)
17212 {
17213 #if ATTENTION
17214     if (!cps->useSigint) return;
17215     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17216     switch (gameMode) {
17217       case MachinePlaysWhite:
17218       case MachinePlaysBlack:
17219       case TwoMachinesPlay:
17220       case IcsPlayingWhite:
17221       case IcsPlayingBlack:
17222       case AnalyzeMode:
17223       case AnalyzeFile:
17224         /* Skip if we know it isn't thinking */
17225         if (!cps->maybeThinking) return;
17226         if (appData.debugMode)
17227           fprintf(debugFP, "Interrupting %s\n", cps->which);
17228         InterruptChildProcess(cps->pr);
17229         cps->maybeThinking = FALSE;
17230         break;
17231       default:
17232         break;
17233     }
17234 #endif /*ATTENTION*/
17235 }
17236
17237 int
17238 CheckFlags ()
17239 {
17240     if (whiteTimeRemaining <= 0) {
17241         if (!whiteFlag) {
17242             whiteFlag = TRUE;
17243             if (appData.icsActive) {
17244                 if (appData.autoCallFlag &&
17245                     gameMode == IcsPlayingBlack && !blackFlag) {
17246                   SendToICS(ics_prefix);
17247                   SendToICS("flag\n");
17248                 }
17249             } else {
17250                 if (blackFlag) {
17251                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17252                 } else {
17253                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17254                     if (appData.autoCallFlag) {
17255                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17256                         return TRUE;
17257                     }
17258                 }
17259             }
17260         }
17261     }
17262     if (blackTimeRemaining <= 0) {
17263         if (!blackFlag) {
17264             blackFlag = TRUE;
17265             if (appData.icsActive) {
17266                 if (appData.autoCallFlag &&
17267                     gameMode == IcsPlayingWhite && !whiteFlag) {
17268                   SendToICS(ics_prefix);
17269                   SendToICS("flag\n");
17270                 }
17271             } else {
17272                 if (whiteFlag) {
17273                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17274                 } else {
17275                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17276                     if (appData.autoCallFlag) {
17277                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17278                         return TRUE;
17279                     }
17280                 }
17281             }
17282         }
17283     }
17284     return FALSE;
17285 }
17286
17287 void
17288 CheckTimeControl ()
17289 {
17290     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17291         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17292
17293     /*
17294      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17295      */
17296     if ( !WhiteOnMove(forwardMostMove) ) {
17297         /* White made time control */
17298         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17299         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17300         /* [HGM] time odds: correct new time quota for time odds! */
17301                                             / WhitePlayer()->timeOdds;
17302         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17303     } else {
17304         lastBlack -= blackTimeRemaining;
17305         /* Black made time control */
17306         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17307                                             / WhitePlayer()->other->timeOdds;
17308         lastWhite = whiteTimeRemaining;
17309     }
17310 }
17311
17312 void
17313 DisplayBothClocks ()
17314 {
17315     int wom = gameMode == EditPosition ?
17316       !blackPlaysFirst : WhiteOnMove(currentMove);
17317     DisplayWhiteClock(whiteTimeRemaining, wom);
17318     DisplayBlackClock(blackTimeRemaining, !wom);
17319 }
17320
17321
17322 /* Timekeeping seems to be a portability nightmare.  I think everyone
17323    has ftime(), but I'm really not sure, so I'm including some ifdefs
17324    to use other calls if you don't.  Clocks will be less accurate if
17325    you have neither ftime nor gettimeofday.
17326 */
17327
17328 /* VS 2008 requires the #include outside of the function */
17329 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17330 #include <sys/timeb.h>
17331 #endif
17332
17333 /* Get the current time as a TimeMark */
17334 void
17335 GetTimeMark (TimeMark *tm)
17336 {
17337 #if HAVE_GETTIMEOFDAY
17338
17339     struct timeval timeVal;
17340     struct timezone timeZone;
17341
17342     gettimeofday(&timeVal, &timeZone);
17343     tm->sec = (long) timeVal.tv_sec;
17344     tm->ms = (int) (timeVal.tv_usec / 1000L);
17345
17346 #else /*!HAVE_GETTIMEOFDAY*/
17347 #if HAVE_FTIME
17348
17349 // include <sys/timeb.h> / moved to just above start of function
17350     struct timeb timeB;
17351
17352     ftime(&timeB);
17353     tm->sec = (long) timeB.time;
17354     tm->ms = (int) timeB.millitm;
17355
17356 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17357     tm->sec = (long) time(NULL);
17358     tm->ms = 0;
17359 #endif
17360 #endif
17361 }
17362
17363 /* Return the difference in milliseconds between two
17364    time marks.  We assume the difference will fit in a long!
17365 */
17366 long
17367 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17368 {
17369     return 1000L*(tm2->sec - tm1->sec) +
17370            (long) (tm2->ms - tm1->ms);
17371 }
17372
17373
17374 /*
17375  * Code to manage the game clocks.
17376  *
17377  * In tournament play, black starts the clock and then white makes a move.
17378  * We give the human user a slight advantage if he is playing white---the
17379  * clocks don't run until he makes his first move, so it takes zero time.
17380  * Also, we don't account for network lag, so we could get out of sync
17381  * with GNU Chess's clock -- but then, referees are always right.
17382  */
17383
17384 static TimeMark tickStartTM;
17385 static long intendedTickLength;
17386
17387 long
17388 NextTickLength (long timeRemaining)
17389 {
17390     long nominalTickLength, nextTickLength;
17391
17392     if (timeRemaining > 0L && timeRemaining <= 10000L)
17393       nominalTickLength = 100L;
17394     else
17395       nominalTickLength = 1000L;
17396     nextTickLength = timeRemaining % nominalTickLength;
17397     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17398
17399     return nextTickLength;
17400 }
17401
17402 /* Adjust clock one minute up or down */
17403 void
17404 AdjustClock (Boolean which, int dir)
17405 {
17406     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17407     if(which) blackTimeRemaining += 60000*dir;
17408     else      whiteTimeRemaining += 60000*dir;
17409     DisplayBothClocks();
17410     adjustedClock = TRUE;
17411 }
17412
17413 /* Stop clocks and reset to a fresh time control */
17414 void
17415 ResetClocks ()
17416 {
17417     (void) StopClockTimer();
17418     if (appData.icsActive) {
17419         whiteTimeRemaining = blackTimeRemaining = 0;
17420     } else if (searchTime) {
17421         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17422         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17423     } else { /* [HGM] correct new time quote for time odds */
17424         whiteTC = blackTC = fullTimeControlString;
17425         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17426         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17427     }
17428     if (whiteFlag || blackFlag) {
17429         DisplayTitle("");
17430         whiteFlag = blackFlag = FALSE;
17431     }
17432     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17433     DisplayBothClocks();
17434     adjustedClock = FALSE;
17435 }
17436
17437 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17438
17439 /* Decrement running clock by amount of time that has passed */
17440 void
17441 DecrementClocks ()
17442 {
17443     long timeRemaining;
17444     long lastTickLength, fudge;
17445     TimeMark now;
17446
17447     if (!appData.clockMode) return;
17448     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17449
17450     GetTimeMark(&now);
17451
17452     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17453
17454     /* Fudge if we woke up a little too soon */
17455     fudge = intendedTickLength - lastTickLength;
17456     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17457
17458     if (WhiteOnMove(forwardMostMove)) {
17459         if(whiteNPS >= 0) lastTickLength = 0;
17460         timeRemaining = whiteTimeRemaining -= lastTickLength;
17461         if(timeRemaining < 0 && !appData.icsActive) {
17462             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17463             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17464                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17465                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17466             }
17467         }
17468         DisplayWhiteClock(whiteTimeRemaining - fudge,
17469                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17470     } else {
17471         if(blackNPS >= 0) lastTickLength = 0;
17472         timeRemaining = blackTimeRemaining -= lastTickLength;
17473         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17474             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17475             if(suddenDeath) {
17476                 blackStartMove = forwardMostMove;
17477                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17478             }
17479         }
17480         DisplayBlackClock(blackTimeRemaining - fudge,
17481                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17482     }
17483     if (CheckFlags()) return;
17484
17485     if(twoBoards) { // count down secondary board's clocks as well
17486         activePartnerTime -= lastTickLength;
17487         partnerUp = 1;
17488         if(activePartner == 'W')
17489             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17490         else
17491             DisplayBlackClock(activePartnerTime, TRUE);
17492         partnerUp = 0;
17493     }
17494
17495     tickStartTM = now;
17496     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17497     StartClockTimer(intendedTickLength);
17498
17499     /* if the time remaining has fallen below the alarm threshold, sound the
17500      * alarm. if the alarm has sounded and (due to a takeback or time control
17501      * with increment) the time remaining has increased to a level above the
17502      * threshold, reset the alarm so it can sound again.
17503      */
17504
17505     if (appData.icsActive && appData.icsAlarm) {
17506
17507         /* make sure we are dealing with the user's clock */
17508         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17509                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17510            )) return;
17511
17512         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17513             alarmSounded = FALSE;
17514         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17515             PlayAlarmSound();
17516             alarmSounded = TRUE;
17517         }
17518     }
17519 }
17520
17521
17522 /* A player has just moved, so stop the previously running
17523    clock and (if in clock mode) start the other one.
17524    We redisplay both clocks in case we're in ICS mode, because
17525    ICS gives us an update to both clocks after every move.
17526    Note that this routine is called *after* forwardMostMove
17527    is updated, so the last fractional tick must be subtracted
17528    from the color that is *not* on move now.
17529 */
17530 void
17531 SwitchClocks (int newMoveNr)
17532 {
17533     long lastTickLength;
17534     TimeMark now;
17535     int flagged = FALSE;
17536
17537     GetTimeMark(&now);
17538
17539     if (StopClockTimer() && appData.clockMode) {
17540         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17541         if (!WhiteOnMove(forwardMostMove)) {
17542             if(blackNPS >= 0) lastTickLength = 0;
17543             blackTimeRemaining -= lastTickLength;
17544            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17545 //         if(pvInfoList[forwardMostMove].time == -1)
17546                  pvInfoList[forwardMostMove].time =               // use GUI time
17547                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17548         } else {
17549            if(whiteNPS >= 0) lastTickLength = 0;
17550            whiteTimeRemaining -= lastTickLength;
17551            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17552 //         if(pvInfoList[forwardMostMove].time == -1)
17553                  pvInfoList[forwardMostMove].time =
17554                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17555         }
17556         flagged = CheckFlags();
17557     }
17558     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17559     CheckTimeControl();
17560
17561     if (flagged || !appData.clockMode) return;
17562
17563     switch (gameMode) {
17564       case MachinePlaysBlack:
17565       case MachinePlaysWhite:
17566       case BeginningOfGame:
17567         if (pausing) return;
17568         break;
17569
17570       case EditGame:
17571       case PlayFromGameFile:
17572       case IcsExamining:
17573         return;
17574
17575       default:
17576         break;
17577     }
17578
17579     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17580         if(WhiteOnMove(forwardMostMove))
17581              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17582         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17583     }
17584
17585     tickStartTM = now;
17586     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17587       whiteTimeRemaining : blackTimeRemaining);
17588     StartClockTimer(intendedTickLength);
17589 }
17590
17591
17592 /* Stop both clocks */
17593 void
17594 StopClocks ()
17595 {
17596     long lastTickLength;
17597     TimeMark now;
17598
17599     if (!StopClockTimer()) return;
17600     if (!appData.clockMode) return;
17601
17602     GetTimeMark(&now);
17603
17604     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17605     if (WhiteOnMove(forwardMostMove)) {
17606         if(whiteNPS >= 0) lastTickLength = 0;
17607         whiteTimeRemaining -= lastTickLength;
17608         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17609     } else {
17610         if(blackNPS >= 0) lastTickLength = 0;
17611         blackTimeRemaining -= lastTickLength;
17612         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17613     }
17614     CheckFlags();
17615 }
17616
17617 /* Start clock of player on move.  Time may have been reset, so
17618    if clock is already running, stop and restart it. */
17619 void
17620 StartClocks ()
17621 {
17622     (void) StopClockTimer(); /* in case it was running already */
17623     DisplayBothClocks();
17624     if (CheckFlags()) return;
17625
17626     if (!appData.clockMode) return;
17627     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17628
17629     GetTimeMark(&tickStartTM);
17630     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17631       whiteTimeRemaining : blackTimeRemaining);
17632
17633    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17634     whiteNPS = blackNPS = -1;
17635     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17636        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17637         whiteNPS = first.nps;
17638     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17639        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17640         blackNPS = first.nps;
17641     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17642         whiteNPS = second.nps;
17643     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17644         blackNPS = second.nps;
17645     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17646
17647     StartClockTimer(intendedTickLength);
17648 }
17649
17650 char *
17651 TimeString (long ms)
17652 {
17653     long second, minute, hour, day;
17654     char *sign = "";
17655     static char buf[32];
17656
17657     if (ms > 0 && ms <= 9900) {
17658       /* convert milliseconds to tenths, rounding up */
17659       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17660
17661       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17662       return buf;
17663     }
17664
17665     /* convert milliseconds to seconds, rounding up */
17666     /* use floating point to avoid strangeness of integer division
17667        with negative dividends on many machines */
17668     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17669
17670     if (second < 0) {
17671         sign = "-";
17672         second = -second;
17673     }
17674
17675     day = second / (60 * 60 * 24);
17676     second = second % (60 * 60 * 24);
17677     hour = second / (60 * 60);
17678     second = second % (60 * 60);
17679     minute = second / 60;
17680     second = second % 60;
17681
17682     if (day > 0)
17683       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17684               sign, day, hour, minute, second);
17685     else if (hour > 0)
17686       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17687     else
17688       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17689
17690     return buf;
17691 }
17692
17693
17694 /*
17695  * This is necessary because some C libraries aren't ANSI C compliant yet.
17696  */
17697 char *
17698 StrStr (char *string, char *match)
17699 {
17700     int i, length;
17701
17702     length = strlen(match);
17703
17704     for (i = strlen(string) - length; i >= 0; i--, string++)
17705       if (!strncmp(match, string, length))
17706         return string;
17707
17708     return NULL;
17709 }
17710
17711 char *
17712 StrCaseStr (char *string, char *match)
17713 {
17714     int i, j, length;
17715
17716     length = strlen(match);
17717
17718     for (i = strlen(string) - length; i >= 0; i--, string++) {
17719         for (j = 0; j < length; j++) {
17720             if (ToLower(match[j]) != ToLower(string[j]))
17721               break;
17722         }
17723         if (j == length) return string;
17724     }
17725
17726     return NULL;
17727 }
17728
17729 #ifndef _amigados
17730 int
17731 StrCaseCmp (char *s1, char *s2)
17732 {
17733     char c1, c2;
17734
17735     for (;;) {
17736         c1 = ToLower(*s1++);
17737         c2 = ToLower(*s2++);
17738         if (c1 > c2) return 1;
17739         if (c1 < c2) return -1;
17740         if (c1 == NULLCHAR) return 0;
17741     }
17742 }
17743
17744
17745 int
17746 ToLower (int c)
17747 {
17748     return isupper(c) ? tolower(c) : c;
17749 }
17750
17751
17752 int
17753 ToUpper (int c)
17754 {
17755     return islower(c) ? toupper(c) : c;
17756 }
17757 #endif /* !_amigados    */
17758
17759 char *
17760 StrSave (char *s)
17761 {
17762   char *ret;
17763
17764   if ((ret = (char *) malloc(strlen(s) + 1)))
17765     {
17766       safeStrCpy(ret, s, strlen(s)+1);
17767     }
17768   return ret;
17769 }
17770
17771 char *
17772 StrSavePtr (char *s, char **savePtr)
17773 {
17774     if (*savePtr) {
17775         free(*savePtr);
17776     }
17777     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17778       safeStrCpy(*savePtr, s, strlen(s)+1);
17779     }
17780     return(*savePtr);
17781 }
17782
17783 char *
17784 PGNDate ()
17785 {
17786     time_t clock;
17787     struct tm *tm;
17788     char buf[MSG_SIZ];
17789
17790     clock = time((time_t *)NULL);
17791     tm = localtime(&clock);
17792     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17793             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17794     return StrSave(buf);
17795 }
17796
17797
17798 char *
17799 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17800 {
17801     int i, j, fromX, fromY, toX, toY;
17802     int whiteToPlay;
17803     char buf[MSG_SIZ];
17804     char *p, *q;
17805     int emptycount;
17806     ChessSquare piece;
17807
17808     whiteToPlay = (gameMode == EditPosition) ?
17809       !blackPlaysFirst : (move % 2 == 0);
17810     p = buf;
17811
17812     /* Piece placement data */
17813     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17814         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17815         emptycount = 0;
17816         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17817             if (boards[move][i][j] == EmptySquare) {
17818                 emptycount++;
17819             } else { ChessSquare piece = boards[move][i][j];
17820                 if (emptycount > 0) {
17821                     if(emptycount<10) /* [HGM] can be >= 10 */
17822                         *p++ = '0' + emptycount;
17823                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17824                     emptycount = 0;
17825                 }
17826                 if(PieceToChar(piece) == '+') {
17827                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17828                     *p++ = '+';
17829                     piece = (ChessSquare)(CHUDEMOTED piece);
17830                 }
17831                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17832                 if(p[-1] == '~') {
17833                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17834                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17835                     *p++ = '~';
17836                 }
17837             }
17838         }
17839         if (emptycount > 0) {
17840             if(emptycount<10) /* [HGM] can be >= 10 */
17841                 *p++ = '0' + emptycount;
17842             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17843             emptycount = 0;
17844         }
17845         *p++ = '/';
17846     }
17847     *(p - 1) = ' ';
17848
17849     /* [HGM] print Crazyhouse or Shogi holdings */
17850     if( gameInfo.holdingsWidth ) {
17851         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17852         q = p;
17853         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17854             piece = boards[move][i][BOARD_WIDTH-1];
17855             if( piece != EmptySquare )
17856               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17857                   *p++ = PieceToChar(piece);
17858         }
17859         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17860             piece = boards[move][BOARD_HEIGHT-i-1][0];
17861             if( piece != EmptySquare )
17862               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17863                   *p++ = PieceToChar(piece);
17864         }
17865
17866         if( q == p ) *p++ = '-';
17867         *p++ = ']';
17868         *p++ = ' ';
17869     }
17870
17871     /* Active color */
17872     *p++ = whiteToPlay ? 'w' : 'b';
17873     *p++ = ' ';
17874
17875   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17876     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17877   } else {
17878   if(nrCastlingRights) {
17879      int handW=0, handB=0;
17880      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17881         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17882         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17883      }
17884      q = p;
17885      if(appData.fischerCastling) {
17886         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17887            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17888                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17889         } else {
17890        /* [HGM] write directly from rights */
17891            if(boards[move][CASTLING][2] != NoRights &&
17892               boards[move][CASTLING][0] != NoRights   )
17893                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17894            if(boards[move][CASTLING][2] != NoRights &&
17895               boards[move][CASTLING][1] != NoRights   )
17896                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17897         }
17898         if(handB) {
17899            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17900                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17901         } else {
17902            if(boards[move][CASTLING][5] != NoRights &&
17903               boards[move][CASTLING][3] != NoRights   )
17904                 *p++ = boards[move][CASTLING][3] + AAA;
17905            if(boards[move][CASTLING][5] != NoRights &&
17906               boards[move][CASTLING][4] != NoRights   )
17907                 *p++ = boards[move][CASTLING][4] + AAA;
17908         }
17909      } else {
17910
17911         /* [HGM] write true castling rights */
17912         if( nrCastlingRights == 6 ) {
17913             int q, k=0;
17914             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17915                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17916             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17917                  boards[move][CASTLING][2] != NoRights  );
17918             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17919                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17920                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17921                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17922             }
17923             if(q) *p++ = 'Q';
17924             k = 0;
17925             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17926                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17927             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17928                  boards[move][CASTLING][5] != NoRights  );
17929             if(handB) {
17930                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17931                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17932                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17933             }
17934             if(q) *p++ = 'q';
17935         }
17936      }
17937      if (q == p) *p++ = '-'; /* No castling rights */
17938      *p++ = ' ';
17939   }
17940
17941   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17942      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17943      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17944     /* En passant target square */
17945     if (move > backwardMostMove) {
17946         fromX = moveList[move - 1][0] - AAA;
17947         fromY = moveList[move - 1][1] - ONE;
17948         toX = moveList[move - 1][2] - AAA;
17949         toY = moveList[move - 1][3] - ONE;
17950         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17951             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17952             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17953             fromX == toX) {
17954             /* 2-square pawn move just happened */
17955             *p++ = toX + AAA;
17956             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17957         } else {
17958             *p++ = '-';
17959         }
17960     } else if(move == backwardMostMove) {
17961         // [HGM] perhaps we should always do it like this, and forget the above?
17962         if((signed char)boards[move][EP_STATUS] >= 0) {
17963             *p++ = boards[move][EP_STATUS] + AAA;
17964             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17965         } else {
17966             *p++ = '-';
17967         }
17968     } else {
17969         *p++ = '-';
17970     }
17971     *p++ = ' ';
17972   }
17973   }
17974
17975     if(moveCounts)
17976     {   int i = 0, j=move;
17977
17978         /* [HGM] find reversible plies */
17979         if (appData.debugMode) { int k;
17980             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17981             for(k=backwardMostMove; k<=forwardMostMove; k++)
17982                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17983
17984         }
17985
17986         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17987         if( j == backwardMostMove ) i += initialRulePlies;
17988         sprintf(p, "%d ", i);
17989         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17990
17991         /* Fullmove number */
17992         sprintf(p, "%d", (move / 2) + 1);
17993     } else *--p = NULLCHAR;
17994
17995     return StrSave(buf);
17996 }
17997
17998 Boolean
17999 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18000 {
18001     int i, j, k, w=0, subst=0, shuffle=0;
18002     char *p, c;
18003     int emptycount, virgin[BOARD_FILES];
18004     ChessSquare piece;
18005
18006     p = fen;
18007
18008     /* Piece placement data */
18009     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18010         j = 0;
18011         for (;;) {
18012             if (*p == '/' || *p == ' ' || *p == '[' ) {
18013                 if(j > w) w = j;
18014                 emptycount = gameInfo.boardWidth - j;
18015                 while (emptycount--)
18016                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18017                 if (*p == '/') p++;
18018                 else if(autoSize) { // we stumbled unexpectedly into end of board
18019                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18020                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18021                     }
18022                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18023                 }
18024                 break;
18025 #if(BOARD_FILES >= 10)*0
18026             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18027                 p++; emptycount=10;
18028                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18029                 while (emptycount--)
18030                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18031 #endif
18032             } else if (*p == '*') {
18033                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18034             } else if (isdigit(*p)) {
18035                 emptycount = *p++ - '0';
18036                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18037                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18038                 while (emptycount--)
18039                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18040             } else if (*p == '<') {
18041                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18042                 else if (i != 0 || !shuffle) return FALSE;
18043                 p++;
18044             } else if (shuffle && *p == '>') {
18045                 p++; // for now ignore closing shuffle range, and assume rank-end
18046             } else if (*p == '?') {
18047                 if (j >= gameInfo.boardWidth) return FALSE;
18048                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18049                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18050             } else if (*p == '+' || isalpha(*p)) {
18051                 if (j >= gameInfo.boardWidth) return FALSE;
18052                 if(*p=='+') {
18053                     piece = CharToPiece(*++p);
18054                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18055                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18056                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18057                 } else piece = CharToPiece(*p++);
18058
18059                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18060                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18061                     piece = (ChessSquare) (PROMOTED piece);
18062                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18063                     p++;
18064                 }
18065                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18066             } else {
18067                 return FALSE;
18068             }
18069         }
18070     }
18071     while (*p == '/' || *p == ' ') p++;
18072
18073     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18074
18075     /* [HGM] by default clear Crazyhouse holdings, if present */
18076     if(gameInfo.holdingsWidth) {
18077        for(i=0; i<BOARD_HEIGHT; i++) {
18078            board[i][0]             = EmptySquare; /* black holdings */
18079            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18080            board[i][1]             = (ChessSquare) 0; /* black counts */
18081            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18082        }
18083     }
18084
18085     /* [HGM] look for Crazyhouse holdings here */
18086     while(*p==' ') p++;
18087     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18088         int swap=0, wcnt=0, bcnt=0;
18089         if(*p == '[') p++;
18090         if(*p == '<') swap++, p++;
18091         if(*p == '-' ) p++; /* empty holdings */ else {
18092             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18093             /* if we would allow FEN reading to set board size, we would   */
18094             /* have to add holdings and shift the board read so far here   */
18095             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18096                 p++;
18097                 if((int) piece >= (int) BlackPawn ) {
18098                     i = (int)piece - (int)BlackPawn;
18099                     i = PieceToNumber((ChessSquare)i);
18100                     if( i >= gameInfo.holdingsSize ) return FALSE;
18101                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18102                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18103                     bcnt++;
18104                 } else {
18105                     i = (int)piece - (int)WhitePawn;
18106                     i = PieceToNumber((ChessSquare)i);
18107                     if( i >= gameInfo.holdingsSize ) return FALSE;
18108                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18109                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18110                     wcnt++;
18111                 }
18112             }
18113             if(subst) { // substitute back-rank question marks by holdings pieces
18114                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18115                     int k, m, n = bcnt + 1;
18116                     if(board[0][j] == ClearBoard) {
18117                         if(!wcnt) return FALSE;
18118                         n = rand() % wcnt;
18119                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18120                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18121                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18122                             break;
18123                         }
18124                     }
18125                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18126                         if(!bcnt) return FALSE;
18127                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18128                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18129                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18130                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18131                             break;
18132                         }
18133                     }
18134                 }
18135                 subst = 0;
18136             }
18137         }
18138         if(*p == ']') p++;
18139     }
18140
18141     if(subst) return FALSE; // substitution requested, but no holdings
18142
18143     while(*p == ' ') p++;
18144
18145     /* Active color */
18146     c = *p++;
18147     if(appData.colorNickNames) {
18148       if( c == appData.colorNickNames[0] ) c = 'w'; else
18149       if( c == appData.colorNickNames[1] ) c = 'b';
18150     }
18151     switch (c) {
18152       case 'w':
18153         *blackPlaysFirst = FALSE;
18154         break;
18155       case 'b':
18156         *blackPlaysFirst = TRUE;
18157         break;
18158       default:
18159         return FALSE;
18160     }
18161
18162     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18163     /* return the extra info in global variiables             */
18164
18165     /* set defaults in case FEN is incomplete */
18166     board[EP_STATUS] = EP_UNKNOWN;
18167     for(i=0; i<nrCastlingRights; i++ ) {
18168         board[CASTLING][i] =
18169             appData.fischerCastling ? NoRights : initialRights[i];
18170     }   /* assume possible unless obviously impossible */
18171     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18172     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18173     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18174                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18175     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18176     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18177     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18178                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18179     FENrulePlies = 0;
18180
18181     while(*p==' ') p++;
18182     if(nrCastlingRights) {
18183       int fischer = 0;
18184       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18185       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18186           /* castling indicator present, so default becomes no castlings */
18187           for(i=0; i<nrCastlingRights; i++ ) {
18188                  board[CASTLING][i] = NoRights;
18189           }
18190       }
18191       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18192              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18193              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18194              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18195         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18196
18197         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18198             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18199             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18200         }
18201         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18202             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18203         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18204                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18205         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18206                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18207         switch(c) {
18208           case'K':
18209               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18210               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18211               board[CASTLING][2] = whiteKingFile;
18212               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18213               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18214               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18215               break;
18216           case'Q':
18217               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18218               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18219               board[CASTLING][2] = whiteKingFile;
18220               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18221               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18222               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18223               break;
18224           case'k':
18225               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18226               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18227               board[CASTLING][5] = blackKingFile;
18228               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18229               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18230               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18231               break;
18232           case'q':
18233               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18234               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18235               board[CASTLING][5] = blackKingFile;
18236               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18237               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18238               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18239           case '-':
18240               break;
18241           default: /* FRC castlings */
18242               if(c >= 'a') { /* black rights */
18243                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18244                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18245                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18246                   if(i == BOARD_RGHT) break;
18247                   board[CASTLING][5] = i;
18248                   c -= AAA;
18249                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18250                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18251                   if(c > i)
18252                       board[CASTLING][3] = c;
18253                   else
18254                       board[CASTLING][4] = c;
18255               } else { /* white rights */
18256                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18257                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18258                     if(board[0][i] == WhiteKing) break;
18259                   if(i == BOARD_RGHT) break;
18260                   board[CASTLING][2] = i;
18261                   c -= AAA - 'a' + 'A';
18262                   if(board[0][c] >= WhiteKing) break;
18263                   if(c > i)
18264                       board[CASTLING][0] = c;
18265                   else
18266                       board[CASTLING][1] = c;
18267               }
18268         }
18269       }
18270       for(i=0; i<nrCastlingRights; i++)
18271         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18272       if(gameInfo.variant == VariantSChess)
18273         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18274       if(fischer && shuffle) appData.fischerCastling = TRUE;
18275     if (appData.debugMode) {
18276         fprintf(debugFP, "FEN castling rights:");
18277         for(i=0; i<nrCastlingRights; i++)
18278         fprintf(debugFP, " %d", board[CASTLING][i]);
18279         fprintf(debugFP, "\n");
18280     }
18281
18282       while(*p==' ') p++;
18283     }
18284
18285     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18286
18287     /* read e.p. field in games that know e.p. capture */
18288     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18289        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18290        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18291       if(*p=='-') {
18292         p++; board[EP_STATUS] = EP_NONE;
18293       } else {
18294          char c = *p++ - AAA;
18295
18296          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18297          if(*p >= '0' && *p <='9') p++;
18298          board[EP_STATUS] = c;
18299       }
18300     }
18301
18302
18303     if(sscanf(p, "%d", &i) == 1) {
18304         FENrulePlies = i; /* 50-move ply counter */
18305         /* (The move number is still ignored)    */
18306     }
18307
18308     return TRUE;
18309 }
18310
18311 void
18312 EditPositionPasteFEN (char *fen)
18313 {
18314   if (fen != NULL) {
18315     Board initial_position;
18316
18317     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18318       DisplayError(_("Bad FEN position in clipboard"), 0);
18319       return ;
18320     } else {
18321       int savedBlackPlaysFirst = blackPlaysFirst;
18322       EditPositionEvent();
18323       blackPlaysFirst = savedBlackPlaysFirst;
18324       CopyBoard(boards[0], initial_position);
18325       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18326       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18327       DisplayBothClocks();
18328       DrawPosition(FALSE, boards[currentMove]);
18329     }
18330   }
18331 }
18332
18333 static char cseq[12] = "\\   ";
18334
18335 Boolean
18336 set_cont_sequence (char *new_seq)
18337 {
18338     int len;
18339     Boolean ret;
18340
18341     // handle bad attempts to set the sequence
18342         if (!new_seq)
18343                 return 0; // acceptable error - no debug
18344
18345     len = strlen(new_seq);
18346     ret = (len > 0) && (len < sizeof(cseq));
18347     if (ret)
18348       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18349     else if (appData.debugMode)
18350       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18351     return ret;
18352 }
18353
18354 /*
18355     reformat a source message so words don't cross the width boundary.  internal
18356     newlines are not removed.  returns the wrapped size (no null character unless
18357     included in source message).  If dest is NULL, only calculate the size required
18358     for the dest buffer.  lp argument indicats line position upon entry, and it's
18359     passed back upon exit.
18360 */
18361 int
18362 wrap (char *dest, char *src, int count, int width, int *lp)
18363 {
18364     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18365
18366     cseq_len = strlen(cseq);
18367     old_line = line = *lp;
18368     ansi = len = clen = 0;
18369
18370     for (i=0; i < count; i++)
18371     {
18372         if (src[i] == '\033')
18373             ansi = 1;
18374
18375         // if we hit the width, back up
18376         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18377         {
18378             // store i & len in case the word is too long
18379             old_i = i, old_len = len;
18380
18381             // find the end of the last word
18382             while (i && src[i] != ' ' && src[i] != '\n')
18383             {
18384                 i--;
18385                 len--;
18386             }
18387
18388             // word too long?  restore i & len before splitting it
18389             if ((old_i-i+clen) >= width)
18390             {
18391                 i = old_i;
18392                 len = old_len;
18393             }
18394
18395             // extra space?
18396             if (i && src[i-1] == ' ')
18397                 len--;
18398
18399             if (src[i] != ' ' && src[i] != '\n')
18400             {
18401                 i--;
18402                 if (len)
18403                     len--;
18404             }
18405
18406             // now append the newline and continuation sequence
18407             if (dest)
18408                 dest[len] = '\n';
18409             len++;
18410             if (dest)
18411                 strncpy(dest+len, cseq, cseq_len);
18412             len += cseq_len;
18413             line = cseq_len;
18414             clen = cseq_len;
18415             continue;
18416         }
18417
18418         if (dest)
18419             dest[len] = src[i];
18420         len++;
18421         if (!ansi)
18422             line++;
18423         if (src[i] == '\n')
18424             line = 0;
18425         if (src[i] == 'm')
18426             ansi = 0;
18427     }
18428     if (dest && appData.debugMode)
18429     {
18430         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18431             count, width, line, len, *lp);
18432         show_bytes(debugFP, src, count);
18433         fprintf(debugFP, "\ndest: ");
18434         show_bytes(debugFP, dest, len);
18435         fprintf(debugFP, "\n");
18436     }
18437     *lp = dest ? line : old_line;
18438
18439     return len;
18440 }
18441
18442 // [HGM] vari: routines for shelving variations
18443 Boolean modeRestore = FALSE;
18444
18445 void
18446 PushInner (int firstMove, int lastMove)
18447 {
18448         int i, j, nrMoves = lastMove - firstMove;
18449
18450         // push current tail of game on stack
18451         savedResult[storedGames] = gameInfo.result;
18452         savedDetails[storedGames] = gameInfo.resultDetails;
18453         gameInfo.resultDetails = NULL;
18454         savedFirst[storedGames] = firstMove;
18455         savedLast [storedGames] = lastMove;
18456         savedFramePtr[storedGames] = framePtr;
18457         framePtr -= nrMoves; // reserve space for the boards
18458         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18459             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18460             for(j=0; j<MOVE_LEN; j++)
18461                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18462             for(j=0; j<2*MOVE_LEN; j++)
18463                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18464             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18465             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18466             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18467             pvInfoList[firstMove+i-1].depth = 0;
18468             commentList[framePtr+i] = commentList[firstMove+i];
18469             commentList[firstMove+i] = NULL;
18470         }
18471
18472         storedGames++;
18473         forwardMostMove = firstMove; // truncate game so we can start variation
18474 }
18475
18476 void
18477 PushTail (int firstMove, int lastMove)
18478 {
18479         if(appData.icsActive) { // only in local mode
18480                 forwardMostMove = currentMove; // mimic old ICS behavior
18481                 return;
18482         }
18483         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18484
18485         PushInner(firstMove, lastMove);
18486         if(storedGames == 1) GreyRevert(FALSE);
18487         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18488 }
18489
18490 void
18491 PopInner (Boolean annotate)
18492 {
18493         int i, j, nrMoves;
18494         char buf[8000], moveBuf[20];
18495
18496         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18497         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18498         nrMoves = savedLast[storedGames] - currentMove;
18499         if(annotate) {
18500                 int cnt = 10;
18501                 if(!WhiteOnMove(currentMove))
18502                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18503                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18504                 for(i=currentMove; i<forwardMostMove; i++) {
18505                         if(WhiteOnMove(i))
18506                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18507                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18508                         strcat(buf, moveBuf);
18509                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18510                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18511                 }
18512                 strcat(buf, ")");
18513         }
18514         for(i=1; i<=nrMoves; i++) { // copy last variation back
18515             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18516             for(j=0; j<MOVE_LEN; j++)
18517                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18518             for(j=0; j<2*MOVE_LEN; j++)
18519                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18520             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18521             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18522             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18523             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18524             commentList[currentMove+i] = commentList[framePtr+i];
18525             commentList[framePtr+i] = NULL;
18526         }
18527         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18528         framePtr = savedFramePtr[storedGames];
18529         gameInfo.result = savedResult[storedGames];
18530         if(gameInfo.resultDetails != NULL) {
18531             free(gameInfo.resultDetails);
18532       }
18533         gameInfo.resultDetails = savedDetails[storedGames];
18534         forwardMostMove = currentMove + nrMoves;
18535 }
18536
18537 Boolean
18538 PopTail (Boolean annotate)
18539 {
18540         if(appData.icsActive) return FALSE; // only in local mode
18541         if(!storedGames) return FALSE; // sanity
18542         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18543
18544         PopInner(annotate);
18545         if(currentMove < forwardMostMove) ForwardEvent(); else
18546         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18547
18548         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18549         return TRUE;
18550 }
18551
18552 void
18553 CleanupTail ()
18554 {       // remove all shelved variations
18555         int i;
18556         for(i=0; i<storedGames; i++) {
18557             if(savedDetails[i])
18558                 free(savedDetails[i]);
18559             savedDetails[i] = NULL;
18560         }
18561         for(i=framePtr; i<MAX_MOVES; i++) {
18562                 if(commentList[i]) free(commentList[i]);
18563                 commentList[i] = NULL;
18564         }
18565         framePtr = MAX_MOVES-1;
18566         storedGames = 0;
18567 }
18568
18569 void
18570 LoadVariation (int index, char *text)
18571 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18572         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18573         int level = 0, move;
18574
18575         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18576         // first find outermost bracketing variation
18577         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18578             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18579                 if(*p == '{') wait = '}'; else
18580                 if(*p == '[') wait = ']'; else
18581                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18582                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18583             }
18584             if(*p == wait) wait = NULLCHAR; // closing ]} found
18585             p++;
18586         }
18587         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18588         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18589         end[1] = NULLCHAR; // clip off comment beyond variation
18590         ToNrEvent(currentMove-1);
18591         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18592         // kludge: use ParsePV() to append variation to game
18593         move = currentMove;
18594         ParsePV(start, TRUE, TRUE);
18595         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18596         ClearPremoveHighlights();
18597         CommentPopDown();
18598         ToNrEvent(currentMove+1);
18599 }
18600
18601 void
18602 LoadTheme ()
18603 {
18604     char *p, *q, buf[MSG_SIZ];
18605     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18606         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18607         ParseArgsFromString(buf);
18608         ActivateTheme(TRUE); // also redo colors
18609         return;
18610     }
18611     p = nickName;
18612     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18613     {
18614         int len;
18615         q = appData.themeNames;
18616         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18617       if(appData.useBitmaps) {
18618         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18619                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18620                 appData.liteBackTextureMode,
18621                 appData.darkBackTextureMode );
18622       } else {
18623         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18624                 Col2Text(2),   // lightSquareColor
18625                 Col2Text(3) ); // darkSquareColor
18626       }
18627       if(appData.useBorder) {
18628         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18629                 appData.border);
18630       } else {
18631         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18632       }
18633       if(appData.useFont) {
18634         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18635                 appData.renderPiecesWithFont,
18636                 appData.fontToPieceTable,
18637                 Col2Text(9),    // appData.fontBackColorWhite
18638                 Col2Text(10) ); // appData.fontForeColorBlack
18639       } else {
18640         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18641                 appData.pieceDirectory);
18642         if(!appData.pieceDirectory[0])
18643           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18644                 Col2Text(0),   // whitePieceColor
18645                 Col2Text(1) ); // blackPieceColor
18646       }
18647       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18648                 Col2Text(4),   // highlightSquareColor
18649                 Col2Text(5) ); // premoveHighlightColor
18650         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18651         if(insert != q) insert[-1] = NULLCHAR;
18652         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18653         if(q)   free(q);
18654     }
18655     ActivateTheme(FALSE);
18656 }