Fix pasting of moves after starting from position file
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 signed char  initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <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
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #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"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         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
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 Partner (ChessSquare *p)
5384 { // change piece into promotion partner if one shogi-promotes to the other
5385   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5386   ChessSquare partner;
5387   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5388   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5389   *p = partner;
5390   return 1;
5391 }
5392
5393 void
5394 Sweep (int step)
5395 {
5396     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5397     static int toggleFlag;
5398     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5399     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5400     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5401     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5402     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5403     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5404     do {
5405         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5406         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5407         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5408         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5409         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5410         if(!step) step = -1;
5411     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5412             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5413             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5414             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5415     if(toX >= 0) {
5416         int victim = boards[currentMove][toY][toX];
5417         boards[currentMove][toY][toX] = promoSweep;
5418         DrawPosition(FALSE, boards[currentMove]);
5419         boards[currentMove][toY][toX] = victim;
5420     } else
5421     ChangeDragPiece(promoSweep);
5422 }
5423
5424 int
5425 PromoScroll (int x, int y)
5426 {
5427   int step = 0;
5428
5429   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5430   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5431   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5432   if(!step) return FALSE;
5433   lastX = x; lastY = y;
5434   if((promoSweep < BlackPawn) == flipView) step = -step;
5435   if(step > 0) selectFlag = 1;
5436   if(!selectFlag) Sweep(step);
5437   return FALSE;
5438 }
5439
5440 void
5441 NextPiece (int step)
5442 {
5443     ChessSquare piece = boards[currentMove][toY][toX];
5444     do {
5445         pieceSweep -= step;
5446         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5447         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5448         if(!step) step = -1;
5449     } while(PieceToChar(pieceSweep) == '.');
5450     boards[currentMove][toY][toX] = pieceSweep;
5451     DrawPosition(FALSE, boards[currentMove]);
5452     boards[currentMove][toY][toX] = piece;
5453 }
5454 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5455 void
5456 AlphaRank (char *move, int n)
5457 {
5458 //    char *p = move, c; int x, y;
5459
5460     if (appData.debugMode) {
5461         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5462     }
5463
5464     if(move[1]=='*' &&
5465        move[2]>='0' && move[2]<='9' &&
5466        move[3]>='a' && move[3]<='x'    ) {
5467         move[1] = '@';
5468         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5469         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5470     } else
5471     if(move[0]>='0' && move[0]<='9' &&
5472        move[1]>='a' && move[1]<='x' &&
5473        move[2]>='0' && move[2]<='9' &&
5474        move[3]>='a' && move[3]<='x'    ) {
5475         /* input move, Shogi -> normal */
5476         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5477         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5478         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5479         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5480     } else
5481     if(move[1]=='@' &&
5482        move[3]>='0' && move[3]<='9' &&
5483        move[2]>='a' && move[2]<='x'    ) {
5484         move[1] = '*';
5485         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5486         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5487     } else
5488     if(
5489        move[0]>='a' && move[0]<='x' &&
5490        move[3]>='0' && move[3]<='9' &&
5491        move[2]>='a' && move[2]<='x'    ) {
5492          /* output move, normal -> Shogi */
5493         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5494         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5495         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5496         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5497         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5498     }
5499     if (appData.debugMode) {
5500         fprintf(debugFP, "   out = '%s'\n", move);
5501     }
5502 }
5503
5504 char yy_textstr[8000];
5505
5506 /* Parser for moves from gnuchess, ICS, or user typein box */
5507 Boolean
5508 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5509 {
5510     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5511
5512     switch (*moveType) {
5513       case WhitePromotion:
5514       case BlackPromotion:
5515       case WhiteNonPromotion:
5516       case BlackNonPromotion:
5517       case NormalMove:
5518       case FirstLeg:
5519       case WhiteCapturesEnPassant:
5520       case BlackCapturesEnPassant:
5521       case WhiteKingSideCastle:
5522       case WhiteQueenSideCastle:
5523       case BlackKingSideCastle:
5524       case BlackQueenSideCastle:
5525       case WhiteKingSideCastleWild:
5526       case WhiteQueenSideCastleWild:
5527       case BlackKingSideCastleWild:
5528       case BlackQueenSideCastleWild:
5529       /* Code added by Tord: */
5530       case WhiteHSideCastleFR:
5531       case WhiteASideCastleFR:
5532       case BlackHSideCastleFR:
5533       case BlackASideCastleFR:
5534       /* End of code added by Tord */
5535       case IllegalMove:         /* bug or odd chess variant */
5536         if(currentMoveString[1] == '@') { // illegal drop
5537           *fromX = WhiteOnMove(moveNum) ?
5538             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5539             (int) CharToPiece(ToLower(currentMoveString[0]));
5540           goto drop;
5541         }
5542         *fromX = currentMoveString[0] - AAA;
5543         *fromY = currentMoveString[1] - ONE;
5544         *toX = currentMoveString[2] - AAA;
5545         *toY = currentMoveString[3] - ONE;
5546         *promoChar = currentMoveString[4];
5547         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5548             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5549     if (appData.debugMode) {
5550         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5551     }
5552             *fromX = *fromY = *toX = *toY = 0;
5553             return FALSE;
5554         }
5555         if (appData.testLegality) {
5556           return (*moveType != IllegalMove);
5557         } else {
5558           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5559                          // [HGM] lion: if this is a double move we are less critical
5560                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5561         }
5562
5563       case WhiteDrop:
5564       case BlackDrop:
5565         *fromX = *moveType == WhiteDrop ?
5566           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5567           (int) CharToPiece(ToLower(currentMoveString[0]));
5568       drop:
5569         *fromY = DROP_RANK;
5570         *toX = currentMoveString[2] - AAA;
5571         *toY = currentMoveString[3] - ONE;
5572         *promoChar = NULLCHAR;
5573         return TRUE;
5574
5575       case AmbiguousMove:
5576       case ImpossibleMove:
5577       case EndOfFile:
5578       case ElapsedTime:
5579       case Comment:
5580       case PGNTag:
5581       case NAG:
5582       case WhiteWins:
5583       case BlackWins:
5584       case GameIsDrawn:
5585       default:
5586     if (appData.debugMode) {
5587         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5588     }
5589         /* bug? */
5590         *fromX = *fromY = *toX = *toY = 0;
5591         *promoChar = NULLCHAR;
5592         return FALSE;
5593     }
5594 }
5595
5596 Boolean pushed = FALSE;
5597 char *lastParseAttempt;
5598
5599 void
5600 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5601 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5602   int fromX, fromY, toX, toY; char promoChar;
5603   ChessMove moveType;
5604   Boolean valid;
5605   int nr = 0;
5606
5607   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5608   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5609     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5610     pushed = TRUE;
5611   }
5612   endPV = forwardMostMove;
5613   do {
5614     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5615     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5616     lastParseAttempt = pv;
5617     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5618     if(!valid && nr == 0 &&
5619        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5620         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5621         // Hande case where played move is different from leading PV move
5622         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5623         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5624         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5625         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5626           endPV += 2; // if position different, keep this
5627           moveList[endPV-1][0] = fromX + AAA;
5628           moveList[endPV-1][1] = fromY + ONE;
5629           moveList[endPV-1][2] = toX + AAA;
5630           moveList[endPV-1][3] = toY + ONE;
5631           parseList[endPV-1][0] = NULLCHAR;
5632           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5633         }
5634       }
5635     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5636     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5637     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5638     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5639         valid++; // allow comments in PV
5640         continue;
5641     }
5642     nr++;
5643     if(endPV+1 > framePtr) break; // no space, truncate
5644     if(!valid) break;
5645     endPV++;
5646     CopyBoard(boards[endPV], boards[endPV-1]);
5647     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5648     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5649     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5650     CoordsToAlgebraic(boards[endPV - 1],
5651                              PosFlags(endPV - 1),
5652                              fromY, fromX, toY, toX, promoChar,
5653                              parseList[endPV - 1]);
5654   } while(valid);
5655   if(atEnd == 2) return; // used hidden, for PV conversion
5656   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5657   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5658   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5659                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5660   DrawPosition(TRUE, boards[currentMove]);
5661 }
5662
5663 int
5664 MultiPV (ChessProgramState *cps)
5665 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5666         int i;
5667         for(i=0; i<cps->nrOptions; i++)
5668             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5669                 return i;
5670         return -1;
5671 }
5672
5673 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5674
5675 Boolean
5676 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5677 {
5678         int startPV, multi, lineStart, origIndex = index;
5679         char *p, buf2[MSG_SIZ];
5680         ChessProgramState *cps = (pane ? &second : &first);
5681
5682         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5683         lastX = x; lastY = y;
5684         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5685         lineStart = startPV = index;
5686         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5687         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5688         index = startPV;
5689         do{ while(buf[index] && buf[index] != '\n') index++;
5690         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5691         buf[index] = 0;
5692         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5693                 int n = cps->option[multi].value;
5694                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5695                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5696                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5697                 cps->option[multi].value = n;
5698                 *start = *end = 0;
5699                 return FALSE;
5700         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5701                 ExcludeClick(origIndex - lineStart);
5702                 return FALSE;
5703         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5704                 Collapse(origIndex - lineStart);
5705                 return FALSE;
5706         }
5707         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5708         *start = startPV; *end = index-1;
5709         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5710         return TRUE;
5711 }
5712
5713 char *
5714 PvToSAN (char *pv)
5715 {
5716         static char buf[10*MSG_SIZ];
5717         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5718         *buf = NULLCHAR;
5719         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5720         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5721         for(i = forwardMostMove; i<endPV; i++){
5722             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5723             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5724             k += strlen(buf+k);
5725         }
5726         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5727         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5728         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5729         endPV = savedEnd;
5730         return buf;
5731 }
5732
5733 Boolean
5734 LoadPV (int x, int y)
5735 { // called on right mouse click to load PV
5736   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5737   lastX = x; lastY = y;
5738   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5739   extendGame = FALSE;
5740   return TRUE;
5741 }
5742
5743 void
5744 UnLoadPV ()
5745 {
5746   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5747   if(endPV < 0) return;
5748   if(appData.autoCopyPV) CopyFENToClipboard();
5749   endPV = -1;
5750   if(extendGame && currentMove > forwardMostMove) {
5751         Boolean saveAnimate = appData.animate;
5752         if(pushed) {
5753             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5754                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5755             } else storedGames--; // abandon shelved tail of original game
5756         }
5757         pushed = FALSE;
5758         forwardMostMove = currentMove;
5759         currentMove = oldFMM;
5760         appData.animate = FALSE;
5761         ToNrEvent(forwardMostMove);
5762         appData.animate = saveAnimate;
5763   }
5764   currentMove = forwardMostMove;
5765   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5766   ClearPremoveHighlights();
5767   DrawPosition(TRUE, boards[currentMove]);
5768 }
5769
5770 void
5771 MovePV (int x, int y, int h)
5772 { // step through PV based on mouse coordinates (called on mouse move)
5773   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5774
5775   // we must somehow check if right button is still down (might be released off board!)
5776   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5777   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5778   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5779   if(!step) return;
5780   lastX = x; lastY = y;
5781
5782   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5783   if(endPV < 0) return;
5784   if(y < margin) step = 1; else
5785   if(y > h - margin) step = -1;
5786   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5787   currentMove += step;
5788   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5789   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5790                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5791   DrawPosition(FALSE, boards[currentMove]);
5792 }
5793
5794
5795 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5796 // All positions will have equal probability, but the current method will not provide a unique
5797 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5798 #define DARK 1
5799 #define LITE 2
5800 #define ANY 3
5801
5802 int squaresLeft[4];
5803 int piecesLeft[(int)BlackPawn];
5804 int seed, nrOfShuffles;
5805
5806 void
5807 GetPositionNumber ()
5808 {       // sets global variable seed
5809         int i;
5810
5811         seed = appData.defaultFrcPosition;
5812         if(seed < 0) { // randomize based on time for negative FRC position numbers
5813                 for(i=0; i<50; i++) seed += random();
5814                 seed = random() ^ random() >> 8 ^ random() << 8;
5815                 if(seed<0) seed = -seed;
5816         }
5817 }
5818
5819 int
5820 put (Board board, int pieceType, int rank, int n, int shade)
5821 // put the piece on the (n-1)-th empty squares of the given shade
5822 {
5823         int i;
5824
5825         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5826                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5827                         board[rank][i] = (ChessSquare) pieceType;
5828                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5829                         squaresLeft[ANY]--;
5830                         piecesLeft[pieceType]--;
5831                         return i;
5832                 }
5833         }
5834         return -1;
5835 }
5836
5837
5838 void
5839 AddOnePiece (Board board, int pieceType, int rank, int shade)
5840 // calculate where the next piece goes, (any empty square), and put it there
5841 {
5842         int i;
5843
5844         i = seed % squaresLeft[shade];
5845         nrOfShuffles *= squaresLeft[shade];
5846         seed /= squaresLeft[shade];
5847         put(board, pieceType, rank, i, shade);
5848 }
5849
5850 void
5851 AddTwoPieces (Board board, int pieceType, int rank)
5852 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5853 {
5854         int i, n=squaresLeft[ANY], j=n-1, k;
5855
5856         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5857         i = seed % k;  // pick one
5858         nrOfShuffles *= k;
5859         seed /= k;
5860         while(i >= j) i -= j--;
5861         j = n - 1 - j; i += j;
5862         put(board, pieceType, rank, j, ANY);
5863         put(board, pieceType, rank, i, ANY);
5864 }
5865
5866 void
5867 SetUpShuffle (Board board, int number)
5868 {
5869         int i, p, first=1;
5870
5871         GetPositionNumber(); nrOfShuffles = 1;
5872
5873         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5874         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5875         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5876
5877         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5878
5879         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5880             p = (int) board[0][i];
5881             if(p < (int) BlackPawn) piecesLeft[p] ++;
5882             board[0][i] = EmptySquare;
5883         }
5884
5885         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5886             // shuffles restricted to allow normal castling put KRR first
5887             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5888                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5889             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5890                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5891             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5892                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5893             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5894                 put(board, WhiteRook, 0, 0, ANY);
5895             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5896         }
5897
5898         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5899             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5900             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5901                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5902                 while(piecesLeft[p] >= 2) {
5903                     AddOnePiece(board, p, 0, LITE);
5904                     AddOnePiece(board, p, 0, DARK);
5905                 }
5906                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5907             }
5908
5909         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5910             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5911             // but we leave King and Rooks for last, to possibly obey FRC restriction
5912             if(p == (int)WhiteRook) continue;
5913             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5914             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5915         }
5916
5917         // now everything is placed, except perhaps King (Unicorn) and Rooks
5918
5919         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5920             // Last King gets castling rights
5921             while(piecesLeft[(int)WhiteUnicorn]) {
5922                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5923                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5924             }
5925
5926             while(piecesLeft[(int)WhiteKing]) {
5927                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5928                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5929             }
5930
5931
5932         } else {
5933             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5934             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5935         }
5936
5937         // Only Rooks can be left; simply place them all
5938         while(piecesLeft[(int)WhiteRook]) {
5939                 i = put(board, WhiteRook, 0, 0, ANY);
5940                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5941                         if(first) {
5942                                 first=0;
5943                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5944                         }
5945                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5946                 }
5947         }
5948         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5949             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5950         }
5951
5952         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5953 }
5954
5955 int
5956 ptclen (const char *s, char *escapes)
5957 {
5958     int n = 0;
5959     if(!*escapes) return strlen(s);
5960     while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
5961     return n;
5962 }
5963
5964 int
5965 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5966 /* [HGM] moved here from winboard.c because of its general usefulness */
5967 /*       Basically a safe strcpy that uses the last character as King */
5968 {
5969     int result = FALSE; int NrPieces, offs;
5970
5971     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5972                     && NrPieces >= 12 && !(NrPieces&1)) {
5973         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5974
5975         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5976         for( i=offs=0; i<NrPieces/2-1; i++ ) {
5977             char *p;
5978             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5979             table[i + offs] = map[j++];
5980             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
5981         }
5982         table[(int) WhiteKing]  = map[j++];
5983         for( i=offs=0; i<NrPieces/2-1; i++ ) {
5984             char *p;
5985             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5986             table[WHITE_TO_BLACK i + offs] = map[j++];
5987             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
5988         }
5989         table[(int) BlackKing]  = map[j++];
5990
5991         result = TRUE;
5992     }
5993
5994     return result;
5995 }
5996
5997 int
5998 SetCharTable (unsigned char *table, const char * map)
5999 {
6000     return SetCharTableEsc(table, map, "");
6001 }
6002
6003 void
6004 Prelude (Board board)
6005 {       // [HGM] superchess: random selection of exo-pieces
6006         int i, j, k; ChessSquare p;
6007         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6008
6009         GetPositionNumber(); // use FRC position number
6010
6011         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6012             SetCharTable(pieceToChar, appData.pieceToCharTable);
6013             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6014                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6015         }
6016
6017         j = seed%4;                 seed /= 4;
6018         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6019         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6020         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6021         j = seed%3 + (seed%3 >= j); seed /= 3;
6022         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6023         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6024         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6025         j = seed%3;                 seed /= 3;
6026         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6027         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6028         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6029         j = seed%2 + (seed%2 >= j); seed /= 2;
6030         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6031         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6032         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6033         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6034         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6035         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6036         put(board, exoPieces[0],    0, 0, ANY);
6037         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6038 }
6039
6040 void
6041 InitPosition (int redraw)
6042 {
6043     ChessSquare (* pieces)[BOARD_FILES];
6044     int i, j, pawnRow=1, pieceRows=1, overrule,
6045     oldx = gameInfo.boardWidth,
6046     oldy = gameInfo.boardHeight,
6047     oldh = gameInfo.holdingsWidth;
6048     static int oldv;
6049
6050     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6051
6052     /* [AS] Initialize pv info list [HGM] and game status */
6053     {
6054         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6055             pvInfoList[i].depth = 0;
6056             boards[i][EP_STATUS] = EP_NONE;
6057             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6058         }
6059
6060         initialRulePlies = 0; /* 50-move counter start */
6061
6062         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6063         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6064     }
6065
6066
6067     /* [HGM] logic here is completely changed. In stead of full positions */
6068     /* the initialized data only consist of the two backranks. The switch */
6069     /* selects which one we will use, which is than copied to the Board   */
6070     /* initialPosition, which for the rest is initialized by Pawns and    */
6071     /* empty squares. This initial position is then copied to boards[0],  */
6072     /* possibly after shuffling, so that it remains available.            */
6073
6074     gameInfo.holdingsWidth = 0; /* default board sizes */
6075     gameInfo.boardWidth    = 8;
6076     gameInfo.boardHeight   = 8;
6077     gameInfo.holdingsSize  = 0;
6078     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6079     for(i=0; i<BOARD_FILES-6; i++)
6080       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6081     initialPosition[EP_STATUS] = EP_NONE;
6082     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6083     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6084     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6085          SetCharTable(pieceNickName, appData.pieceNickNames);
6086     else SetCharTable(pieceNickName, "............");
6087     pieces = FIDEArray;
6088
6089     switch (gameInfo.variant) {
6090     case VariantFischeRandom:
6091       shuffleOpenings = TRUE;
6092       appData.fischerCastling = TRUE;
6093     default:
6094       break;
6095     case VariantShatranj:
6096       pieces = ShatranjArray;
6097       nrCastlingRights = 0;
6098       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6099       break;
6100     case VariantMakruk:
6101       pieces = makrukArray;
6102       nrCastlingRights = 0;
6103       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6104       break;
6105     case VariantASEAN:
6106       pieces = aseanArray;
6107       nrCastlingRights = 0;
6108       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6109       break;
6110     case VariantTwoKings:
6111       pieces = twoKingsArray;
6112       break;
6113     case VariantGrand:
6114       pieces = GrandArray;
6115       nrCastlingRights = 0;
6116       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6117       gameInfo.boardWidth = 10;
6118       gameInfo.boardHeight = 10;
6119       gameInfo.holdingsSize = 7;
6120       break;
6121     case VariantCapaRandom:
6122       shuffleOpenings = TRUE;
6123       appData.fischerCastling = TRUE;
6124     case VariantCapablanca:
6125       pieces = CapablancaArray;
6126       gameInfo.boardWidth = 10;
6127       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6128       break;
6129     case VariantGothic:
6130       pieces = GothicArray;
6131       gameInfo.boardWidth = 10;
6132       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6133       break;
6134     case VariantSChess:
6135       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6136       gameInfo.holdingsSize = 7;
6137       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6138       break;
6139     case VariantJanus:
6140       pieces = JanusArray;
6141       gameInfo.boardWidth = 10;
6142       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6143       nrCastlingRights = 6;
6144         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6145         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6146         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6147         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6148         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6149         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6150       break;
6151     case VariantFalcon:
6152       pieces = FalconArray;
6153       gameInfo.boardWidth = 10;
6154       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6155       break;
6156     case VariantXiangqi:
6157       pieces = XiangqiArray;
6158       gameInfo.boardWidth  = 9;
6159       gameInfo.boardHeight = 10;
6160       nrCastlingRights = 0;
6161       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6162       break;
6163     case VariantShogi:
6164       pieces = ShogiArray;
6165       gameInfo.boardWidth  = 9;
6166       gameInfo.boardHeight = 9;
6167       gameInfo.holdingsSize = 7;
6168       nrCastlingRights = 0;
6169       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6170       break;
6171     case VariantChu:
6172       pieces = ChuArray; pieceRows = 3;
6173       gameInfo.boardWidth  = 12;
6174       gameInfo.boardHeight = 12;
6175       nrCastlingRights = 0;
6176       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
6177                                    "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
6178       break;
6179     case VariantCourier:
6180       pieces = CourierArray;
6181       gameInfo.boardWidth  = 12;
6182       nrCastlingRights = 0;
6183       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6184       break;
6185     case VariantKnightmate:
6186       pieces = KnightmateArray;
6187       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6188       break;
6189     case VariantSpartan:
6190       pieces = SpartanArray;
6191       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6192       break;
6193     case VariantLion:
6194       pieces = lionArray;
6195       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6196       break;
6197     case VariantChuChess:
6198       pieces = ChuChessArray;
6199       gameInfo.boardWidth = 10;
6200       gameInfo.boardHeight = 10;
6201       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6202       break;
6203     case VariantFairy:
6204       pieces = fairyArray;
6205       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6206       break;
6207     case VariantGreat:
6208       pieces = GreatArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6211       gameInfo.holdingsSize = 8;
6212       break;
6213     case VariantSuper:
6214       pieces = FIDEArray;
6215       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6216       gameInfo.holdingsSize = 8;
6217       startedFromSetupPosition = TRUE;
6218       break;
6219     case VariantCrazyhouse:
6220     case VariantBughouse:
6221       pieces = FIDEArray;
6222       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6223       gameInfo.holdingsSize = 5;
6224       break;
6225     case VariantWildCastle:
6226       pieces = FIDEArray;
6227       /* !!?shuffle with kings guaranteed to be on d or e file */
6228       shuffleOpenings = 1;
6229       break;
6230     case VariantNoCastle:
6231       pieces = FIDEArray;
6232       nrCastlingRights = 0;
6233       /* !!?unconstrained back-rank shuffle */
6234       shuffleOpenings = 1;
6235       break;
6236     }
6237
6238     overrule = 0;
6239     if(appData.NrFiles >= 0) {
6240         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6241         gameInfo.boardWidth = appData.NrFiles;
6242     }
6243     if(appData.NrRanks >= 0) {
6244         gameInfo.boardHeight = appData.NrRanks;
6245     }
6246     if(appData.holdingsSize >= 0) {
6247         i = appData.holdingsSize;
6248         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6249         gameInfo.holdingsSize = i;
6250     }
6251     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6252     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6253         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6254
6255     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6256     if(pawnRow < 1) pawnRow = 1;
6257     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6258        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6259     if(gameInfo.variant == VariantChu) pawnRow = 3;
6260
6261     /* User pieceToChar list overrules defaults */
6262     if(appData.pieceToCharTable != NULL)
6263         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6264
6265     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6266
6267         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6268             s = (ChessSquare) 0; /* account holding counts in guard band */
6269         for( i=0; i<BOARD_HEIGHT; i++ )
6270             initialPosition[i][j] = s;
6271
6272         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6273         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6274         initialPosition[pawnRow][j] = WhitePawn;
6275         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6276         if(gameInfo.variant == VariantXiangqi) {
6277             if(j&1) {
6278                 initialPosition[pawnRow][j] =
6279                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6280                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6281                    initialPosition[2][j] = WhiteCannon;
6282                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6283                 }
6284             }
6285         }
6286         if(gameInfo.variant == VariantChu) {
6287              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6288                initialPosition[pawnRow+1][j] = WhiteCobra,
6289                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6290              for(i=1; i<pieceRows; i++) {
6291                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6292                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6293              }
6294         }
6295         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6296             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6297                initialPosition[0][j] = WhiteRook;
6298                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6299             }
6300         }
6301         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6302     }
6303     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6304     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6305
6306             j=BOARD_LEFT+1;
6307             initialPosition[1][j] = WhiteBishop;
6308             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6309             j=BOARD_RGHT-2;
6310             initialPosition[1][j] = WhiteRook;
6311             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6312     }
6313
6314     if( nrCastlingRights == -1) {
6315         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6316         /*       This sets default castling rights from none to normal corners   */
6317         /* Variants with other castling rights must set them themselves above    */
6318         nrCastlingRights = 6;
6319
6320         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6321         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6322         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6323         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6324         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6325         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6326      }
6327
6328      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6329      if(gameInfo.variant == VariantGreat) { // promotion commoners
6330         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6331         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6332         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6333         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6334      }
6335      if( gameInfo.variant == VariantSChess ) {
6336       initialPosition[1][0] = BlackMarshall;
6337       initialPosition[2][0] = BlackAngel;
6338       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6339       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6340       initialPosition[1][1] = initialPosition[2][1] =
6341       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6342      }
6343   if (appData.debugMode) {
6344     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6345   }
6346     if(shuffleOpenings) {
6347         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6348         startedFromSetupPosition = TRUE;
6349     }
6350     if(startedFromPositionFile) {
6351       /* [HGM] loadPos: use PositionFile for every new game */
6352       CopyBoard(initialPosition, filePosition);
6353       for(i=0; i<nrCastlingRights; i++)
6354           initialRights[i] = filePosition[CASTLING][i];
6355       startedFromSetupPosition = TRUE;
6356     }
6357
6358     CopyBoard(boards[0], initialPosition);
6359
6360     if(oldx != gameInfo.boardWidth ||
6361        oldy != gameInfo.boardHeight ||
6362        oldv != gameInfo.variant ||
6363        oldh != gameInfo.holdingsWidth
6364                                          )
6365             InitDrawingSizes(-2 ,0);
6366
6367     oldv = gameInfo.variant;
6368     if (redraw)
6369       DrawPosition(TRUE, boards[currentMove]);
6370 }
6371
6372 void
6373 SendBoard (ChessProgramState *cps, int moveNum)
6374 {
6375     char message[MSG_SIZ];
6376
6377     if (cps->useSetboard) {
6378       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6379       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6380       SendToProgram(message, cps);
6381       free(fen);
6382
6383     } else {
6384       ChessSquare *bp;
6385       int i, j, left=0, right=BOARD_WIDTH;
6386       /* Kludge to set black to move, avoiding the troublesome and now
6387        * deprecated "black" command.
6388        */
6389       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6390         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6391
6392       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6393
6394       SendToProgram("edit\n", cps);
6395       SendToProgram("#\n", cps);
6396       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6397         bp = &boards[moveNum][i][left];
6398         for (j = left; j < right; j++, bp++) {
6399           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6400           if ((int) *bp < (int) BlackPawn) {
6401             if(j == BOARD_RGHT+1)
6402                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6403             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6404             if(message[0] == '+' || message[0] == '~') {
6405               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6406                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6407                         AAA + j, ONE + i - '0');
6408             }
6409             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6410                 message[1] = BOARD_RGHT   - 1 - j + '1';
6411                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6412             }
6413             SendToProgram(message, cps);
6414           }
6415         }
6416       }
6417
6418       SendToProgram("c\n", cps);
6419       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6420         bp = &boards[moveNum][i][left];
6421         for (j = left; j < right; j++, bp++) {
6422           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6423           if (((int) *bp != (int) EmptySquare)
6424               && ((int) *bp >= (int) BlackPawn)) {
6425             if(j == BOARD_LEFT-2)
6426                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6427             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6428                     AAA + j, ONE + i - '0');
6429             if(message[0] == '+' || message[0] == '~') {
6430               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6431                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6432                         AAA + j, ONE + i - '0');
6433             }
6434             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6435                 message[1] = BOARD_RGHT   - 1 - j + '1';
6436                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6437             }
6438             SendToProgram(message, cps);
6439           }
6440         }
6441       }
6442
6443       SendToProgram(".\n", cps);
6444     }
6445     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6446 }
6447
6448 char exclusionHeader[MSG_SIZ];
6449 int exCnt, excludePtr;
6450 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6451 static Exclusion excluTab[200];
6452 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6453
6454 static void
6455 WriteMap (int s)
6456 {
6457     int j;
6458     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6459     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6460 }
6461
6462 static void
6463 ClearMap ()
6464 {
6465     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6466     excludePtr = 24; exCnt = 0;
6467     WriteMap(0);
6468 }
6469
6470 static void
6471 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6472 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6473     char buf[2*MOVE_LEN], *p;
6474     Exclusion *e = excluTab;
6475     int i;
6476     for(i=0; i<exCnt; i++)
6477         if(e[i].ff == fromX && e[i].fr == fromY &&
6478            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6479     if(i == exCnt) { // was not in exclude list; add it
6480         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6481         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6482             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6483             return; // abort
6484         }
6485         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6486         excludePtr++; e[i].mark = excludePtr++;
6487         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6488         exCnt++;
6489     }
6490     exclusionHeader[e[i].mark] = state;
6491 }
6492
6493 static int
6494 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6495 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6496     char buf[MSG_SIZ];
6497     int j, k;
6498     ChessMove moveType;
6499     if((signed char)promoChar == -1) { // kludge to indicate best move
6500         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6501             return 1; // if unparsable, abort
6502     }
6503     // update exclusion map (resolving toggle by consulting existing state)
6504     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6505     j = k%8; k >>= 3;
6506     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6507     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6508          excludeMap[k] |=   1<<j;
6509     else excludeMap[k] &= ~(1<<j);
6510     // update header
6511     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6512     // inform engine
6513     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6514     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6515     SendToBoth(buf);
6516     return (state == '+');
6517 }
6518
6519 static void
6520 ExcludeClick (int index)
6521 {
6522     int i, j;
6523     Exclusion *e = excluTab;
6524     if(index < 25) { // none, best or tail clicked
6525         if(index < 13) { // none: include all
6526             WriteMap(0); // clear map
6527             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6528             SendToBoth("include all\n"); // and inform engine
6529         } else if(index > 18) { // tail
6530             if(exclusionHeader[19] == '-') { // tail was excluded
6531                 SendToBoth("include all\n");
6532                 WriteMap(0); // clear map completely
6533                 // now re-exclude selected moves
6534                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6535                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6536             } else { // tail was included or in mixed state
6537                 SendToBoth("exclude all\n");
6538                 WriteMap(0xFF); // fill map completely
6539                 // now re-include selected moves
6540                 j = 0; // count them
6541                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6542                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6543                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6544             }
6545         } else { // best
6546             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6547         }
6548     } else {
6549         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6550             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6551             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6552             break;
6553         }
6554     }
6555 }
6556
6557 ChessSquare
6558 DefaultPromoChoice (int white)
6559 {
6560     ChessSquare result;
6561     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6562        gameInfo.variant == VariantMakruk)
6563         result = WhiteFerz; // no choice
6564     else if(gameInfo.variant == VariantASEAN)
6565         result = WhiteRook; // no choice
6566     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6567         result= WhiteKing; // in Suicide Q is the last thing we want
6568     else if(gameInfo.variant == VariantSpartan)
6569         result = white ? WhiteQueen : WhiteAngel;
6570     else result = WhiteQueen;
6571     if(!white) result = WHITE_TO_BLACK result;
6572     return result;
6573 }
6574
6575 static int autoQueen; // [HGM] oneclick
6576
6577 int
6578 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6579 {
6580     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6581     /* [HGM] add Shogi promotions */
6582     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6583     ChessSquare piece, partner;
6584     ChessMove moveType;
6585     Boolean premove;
6586
6587     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6588     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6589
6590     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6591       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6592         return FALSE;
6593
6594     piece = boards[currentMove][fromY][fromX];
6595     if(gameInfo.variant == VariantChu) {
6596         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6597         promotionZoneSize = BOARD_HEIGHT/3;
6598         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6599     } else if(gameInfo.variant == VariantShogi) {
6600         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6601         highestPromotingPiece = (int)WhiteAlfil;
6602     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6603         promotionZoneSize = 3;
6604     }
6605
6606     // Treat Lance as Pawn when it is not representing Amazon or Lance
6607     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6608         if(piece == WhiteLance) piece = WhitePawn; else
6609         if(piece == BlackLance) piece = BlackPawn;
6610     }
6611
6612     // next weed out all moves that do not touch the promotion zone at all
6613     if((int)piece >= BlackPawn) {
6614         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6615              return FALSE;
6616         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6617         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6618     } else {
6619         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6620            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6621         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6622              return FALSE;
6623     }
6624
6625     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6626
6627     // weed out mandatory Shogi promotions
6628     if(gameInfo.variant == VariantShogi) {
6629         if(piece >= BlackPawn) {
6630             if(toY == 0 && piece == BlackPawn ||
6631                toY == 0 && piece == BlackQueen ||
6632                toY <= 1 && piece == BlackKnight) {
6633                 *promoChoice = '+';
6634                 return FALSE;
6635             }
6636         } else {
6637             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6638                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6639                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6640                 *promoChoice = '+';
6641                 return FALSE;
6642             }
6643         }
6644     }
6645
6646     // weed out obviously illegal Pawn moves
6647     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6648         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6649         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6650         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6651         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6652         // note we are not allowed to test for valid (non-)capture, due to premove
6653     }
6654
6655     // we either have a choice what to promote to, or (in Shogi) whether to promote
6656     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6657        gameInfo.variant == VariantMakruk) {
6658         ChessSquare p=BlackFerz;  // no choice
6659         while(p < EmptySquare) {  //but make sure we use piece that exists
6660             *promoChoice = PieceToChar(p++);
6661             if(*promoChoice != '.') break;
6662         }
6663         return FALSE;
6664     }
6665     // no sense asking what we must promote to if it is going to explode...
6666     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6667         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6668         return FALSE;
6669     }
6670     // give caller the default choice even if we will not make it
6671     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6672     partner = piece; // pieces can promote if the pieceToCharTable says so
6673     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6674     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6675     if(        sweepSelect && gameInfo.variant != VariantGreat
6676                            && gameInfo.variant != VariantGrand
6677                            && gameInfo.variant != VariantSuper) return FALSE;
6678     if(autoQueen) return FALSE; // predetermined
6679
6680     // suppress promotion popup on illegal moves that are not premoves
6681     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6682               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6683     if(appData.testLegality && !premove) {
6684         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6685                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6686         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6687         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6688             return FALSE;
6689     }
6690
6691     return TRUE;
6692 }
6693
6694 int
6695 InPalace (int row, int column)
6696 {   /* [HGM] for Xiangqi */
6697     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6698          column < (BOARD_WIDTH + 4)/2 &&
6699          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6700     return FALSE;
6701 }
6702
6703 int
6704 PieceForSquare (int x, int y)
6705 {
6706   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6707      return -1;
6708   else
6709      return boards[currentMove][y][x];
6710 }
6711
6712 int
6713 OKToStartUserMove (int x, int y)
6714 {
6715     ChessSquare from_piece;
6716     int white_piece;
6717
6718     if (matchMode) return FALSE;
6719     if (gameMode == EditPosition) return TRUE;
6720
6721     if (x >= 0 && y >= 0)
6722       from_piece = boards[currentMove][y][x];
6723     else
6724       from_piece = EmptySquare;
6725
6726     if (from_piece == EmptySquare) return FALSE;
6727
6728     white_piece = (int)from_piece >= (int)WhitePawn &&
6729       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6730
6731     switch (gameMode) {
6732       case AnalyzeFile:
6733       case TwoMachinesPlay:
6734       case EndOfGame:
6735         return FALSE;
6736
6737       case IcsObserving:
6738       case IcsIdle:
6739         return FALSE;
6740
6741       case MachinePlaysWhite:
6742       case IcsPlayingBlack:
6743         if (appData.zippyPlay) return FALSE;
6744         if (white_piece) {
6745             DisplayMoveError(_("You are playing Black"));
6746             return FALSE;
6747         }
6748         break;
6749
6750       case MachinePlaysBlack:
6751       case IcsPlayingWhite:
6752         if (appData.zippyPlay) return FALSE;
6753         if (!white_piece) {
6754             DisplayMoveError(_("You are playing White"));
6755             return FALSE;
6756         }
6757         break;
6758
6759       case PlayFromGameFile:
6760             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6761       case EditGame:
6762         if (!white_piece && WhiteOnMove(currentMove)) {
6763             DisplayMoveError(_("It is White's turn"));
6764             return FALSE;
6765         }
6766         if (white_piece && !WhiteOnMove(currentMove)) {
6767             DisplayMoveError(_("It is Black's turn"));
6768             return FALSE;
6769         }
6770         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6771             /* Editing correspondence game history */
6772             /* Could disallow this or prompt for confirmation */
6773             cmailOldMove = -1;
6774         }
6775         break;
6776
6777       case BeginningOfGame:
6778         if (appData.icsActive) return FALSE;
6779         if (!appData.noChessProgram) {
6780             if (!white_piece) {
6781                 DisplayMoveError(_("You are playing White"));
6782                 return FALSE;
6783             }
6784         }
6785         break;
6786
6787       case Training:
6788         if (!white_piece && WhiteOnMove(currentMove)) {
6789             DisplayMoveError(_("It is White's turn"));
6790             return FALSE;
6791         }
6792         if (white_piece && !WhiteOnMove(currentMove)) {
6793             DisplayMoveError(_("It is Black's turn"));
6794             return FALSE;
6795         }
6796         break;
6797
6798       default:
6799       case IcsExamining:
6800         break;
6801     }
6802     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6803         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6804         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6805         && gameMode != AnalyzeFile && gameMode != Training) {
6806         DisplayMoveError(_("Displayed position is not current"));
6807         return FALSE;
6808     }
6809     return TRUE;
6810 }
6811
6812 Boolean
6813 OnlyMove (int *x, int *y, Boolean captures)
6814 {
6815     DisambiguateClosure cl;
6816     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6817     switch(gameMode) {
6818       case MachinePlaysBlack:
6819       case IcsPlayingWhite:
6820       case BeginningOfGame:
6821         if(!WhiteOnMove(currentMove)) return FALSE;
6822         break;
6823       case MachinePlaysWhite:
6824       case IcsPlayingBlack:
6825         if(WhiteOnMove(currentMove)) return FALSE;
6826         break;
6827       case EditGame:
6828         break;
6829       default:
6830         return FALSE;
6831     }
6832     cl.pieceIn = EmptySquare;
6833     cl.rfIn = *y;
6834     cl.ffIn = *x;
6835     cl.rtIn = -1;
6836     cl.ftIn = -1;
6837     cl.promoCharIn = NULLCHAR;
6838     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6839     if( cl.kind == NormalMove ||
6840         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6841         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6842         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6843       fromX = cl.ff;
6844       fromY = cl.rf;
6845       *x = cl.ft;
6846       *y = cl.rt;
6847       return TRUE;
6848     }
6849     if(cl.kind != ImpossibleMove) return FALSE;
6850     cl.pieceIn = EmptySquare;
6851     cl.rfIn = -1;
6852     cl.ffIn = -1;
6853     cl.rtIn = *y;
6854     cl.ftIn = *x;
6855     cl.promoCharIn = NULLCHAR;
6856     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6857     if( cl.kind == NormalMove ||
6858         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6859         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6860         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6861       fromX = cl.ff;
6862       fromY = cl.rf;
6863       *x = cl.ft;
6864       *y = cl.rt;
6865       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6866       return TRUE;
6867     }
6868     return FALSE;
6869 }
6870
6871 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6872 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6873 int lastLoadGameUseList = FALSE;
6874 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6875 ChessMove lastLoadGameStart = EndOfFile;
6876 int doubleClick;
6877 Boolean addToBookFlag;
6878
6879 void
6880 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6881 {
6882     ChessMove moveType;
6883     ChessSquare pup;
6884     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6885
6886     /* Check if the user is playing in turn.  This is complicated because we
6887        let the user "pick up" a piece before it is his turn.  So the piece he
6888        tried to pick up may have been captured by the time he puts it down!
6889        Therefore we use the color the user is supposed to be playing in this
6890        test, not the color of the piece that is currently on the starting
6891        square---except in EditGame mode, where the user is playing both
6892        sides; fortunately there the capture race can't happen.  (It can
6893        now happen in IcsExamining mode, but that's just too bad.  The user
6894        will get a somewhat confusing message in that case.)
6895        */
6896
6897     switch (gameMode) {
6898       case AnalyzeFile:
6899       case TwoMachinesPlay:
6900       case EndOfGame:
6901       case IcsObserving:
6902       case IcsIdle:
6903         /* We switched into a game mode where moves are not accepted,
6904            perhaps while the mouse button was down. */
6905         return;
6906
6907       case MachinePlaysWhite:
6908         /* User is moving for Black */
6909         if (WhiteOnMove(currentMove)) {
6910             DisplayMoveError(_("It is White's turn"));
6911             return;
6912         }
6913         break;
6914
6915       case MachinePlaysBlack:
6916         /* User is moving for White */
6917         if (!WhiteOnMove(currentMove)) {
6918             DisplayMoveError(_("It is Black's turn"));
6919             return;
6920         }
6921         break;
6922
6923       case PlayFromGameFile:
6924             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6925       case EditGame:
6926       case IcsExamining:
6927       case BeginningOfGame:
6928       case AnalyzeMode:
6929       case Training:
6930         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6931         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6932             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6933             /* User is moving for Black */
6934             if (WhiteOnMove(currentMove)) {
6935                 DisplayMoveError(_("It is White's turn"));
6936                 return;
6937             }
6938         } else {
6939             /* User is moving for White */
6940             if (!WhiteOnMove(currentMove)) {
6941                 DisplayMoveError(_("It is Black's turn"));
6942                 return;
6943             }
6944         }
6945         break;
6946
6947       case IcsPlayingBlack:
6948         /* User is moving for Black */
6949         if (WhiteOnMove(currentMove)) {
6950             if (!appData.premove) {
6951                 DisplayMoveError(_("It is White's turn"));
6952             } else if (toX >= 0 && toY >= 0) {
6953                 premoveToX = toX;
6954                 premoveToY = toY;
6955                 premoveFromX = fromX;
6956                 premoveFromY = fromY;
6957                 premovePromoChar = promoChar;
6958                 gotPremove = 1;
6959                 if (appData.debugMode)
6960                     fprintf(debugFP, "Got premove: fromX %d,"
6961                             "fromY %d, toX %d, toY %d\n",
6962                             fromX, fromY, toX, toY);
6963             }
6964             return;
6965         }
6966         break;
6967
6968       case IcsPlayingWhite:
6969         /* User is moving for White */
6970         if (!WhiteOnMove(currentMove)) {
6971             if (!appData.premove) {
6972                 DisplayMoveError(_("It is Black's turn"));
6973             } else if (toX >= 0 && toY >= 0) {
6974                 premoveToX = toX;
6975                 premoveToY = toY;
6976                 premoveFromX = fromX;
6977                 premoveFromY = fromY;
6978                 premovePromoChar = promoChar;
6979                 gotPremove = 1;
6980                 if (appData.debugMode)
6981                     fprintf(debugFP, "Got premove: fromX %d,"
6982                             "fromY %d, toX %d, toY %d\n",
6983                             fromX, fromY, toX, toY);
6984             }
6985             return;
6986         }
6987         break;
6988
6989       default:
6990         break;
6991
6992       case EditPosition:
6993         /* EditPosition, empty square, or different color piece;
6994            click-click move is possible */
6995         if (toX == -2 || toY == -2) {
6996             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6997             DrawPosition(FALSE, boards[currentMove]);
6998             return;
6999         } else if (toX >= 0 && toY >= 0) {
7000             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7001                 ChessSquare q, p = boards[0][rf][ff];
7002                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7003                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7004                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7005                 if(PieceToChar(q) == '+') gatingPiece = p;
7006             }
7007             boards[0][toY][toX] = boards[0][fromY][fromX];
7008             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7009                 if(boards[0][fromY][0] != EmptySquare) {
7010                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7011                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7012                 }
7013             } else
7014             if(fromX == BOARD_RGHT+1) {
7015                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7016                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7017                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7018                 }
7019             } else
7020             boards[0][fromY][fromX] = gatingPiece;
7021             DrawPosition(FALSE, boards[currentMove]);
7022             return;
7023         }
7024         return;
7025     }
7026
7027     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7028     pup = boards[currentMove][toY][toX];
7029
7030     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7031     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7032          if( pup != EmptySquare ) return;
7033          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7034            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7035                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7036            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7037            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7038            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7039            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7040          fromY = DROP_RANK;
7041     }
7042
7043     /* [HGM] always test for legality, to get promotion info */
7044     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7045                                          fromY, fromX, toY, toX, promoChar);
7046
7047     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7048
7049     /* [HGM] but possibly ignore an IllegalMove result */
7050     if (appData.testLegality) {
7051         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7052             DisplayMoveError(_("Illegal move"));
7053             return;
7054         }
7055     }
7056
7057     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7058         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7059              ClearPremoveHighlights(); // was included
7060         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7061         return;
7062     }
7063
7064     if(addToBookFlag) { // adding moves to book
7065         char buf[MSG_SIZ], move[MSG_SIZ];
7066         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7067         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7068         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7069         AddBookMove(buf);
7070         addToBookFlag = FALSE;
7071         ClearHighlights();
7072         return;
7073     }
7074
7075     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7076 }
7077
7078 /* Common tail of UserMoveEvent and DropMenuEvent */
7079 int
7080 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7081 {
7082     char *bookHit = 0;
7083
7084     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7085         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7086         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7087         if(WhiteOnMove(currentMove)) {
7088             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7089         } else {
7090             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7091         }
7092     }
7093
7094     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7095        move type in caller when we know the move is a legal promotion */
7096     if(moveType == NormalMove && promoChar)
7097         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7098
7099     /* [HGM] <popupFix> The following if has been moved here from
7100        UserMoveEvent(). Because it seemed to belong here (why not allow
7101        piece drops in training games?), and because it can only be
7102        performed after it is known to what we promote. */
7103     if (gameMode == Training) {
7104       /* compare the move played on the board to the next move in the
7105        * game. If they match, display the move and the opponent's response.
7106        * If they don't match, display an error message.
7107        */
7108       int saveAnimate;
7109       Board testBoard;
7110       CopyBoard(testBoard, boards[currentMove]);
7111       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7112
7113       if (CompareBoards(testBoard, boards[currentMove+1])) {
7114         ForwardInner(currentMove+1);
7115
7116         /* Autoplay the opponent's response.
7117          * if appData.animate was TRUE when Training mode was entered,
7118          * the response will be animated.
7119          */
7120         saveAnimate = appData.animate;
7121         appData.animate = animateTraining;
7122         ForwardInner(currentMove+1);
7123         appData.animate = saveAnimate;
7124
7125         /* check for the end of the game */
7126         if (currentMove >= forwardMostMove) {
7127           gameMode = PlayFromGameFile;
7128           ModeHighlight();
7129           SetTrainingModeOff();
7130           DisplayInformation(_("End of game"));
7131         }
7132       } else {
7133         DisplayError(_("Incorrect move"), 0);
7134       }
7135       return 1;
7136     }
7137
7138   /* Ok, now we know that the move is good, so we can kill
7139      the previous line in Analysis Mode */
7140   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7141                                 && currentMove < forwardMostMove) {
7142     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7143     else forwardMostMove = currentMove;
7144   }
7145
7146   ClearMap();
7147
7148   /* If we need the chess program but it's dead, restart it */
7149   ResurrectChessProgram();
7150
7151   /* A user move restarts a paused game*/
7152   if (pausing)
7153     PauseEvent();
7154
7155   thinkOutput[0] = NULLCHAR;
7156
7157   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7158
7159   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7160     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7161     return 1;
7162   }
7163
7164   if (gameMode == BeginningOfGame) {
7165     if (appData.noChessProgram) {
7166       gameMode = EditGame;
7167       SetGameInfo();
7168     } else {
7169       char buf[MSG_SIZ];
7170       gameMode = MachinePlaysBlack;
7171       StartClocks();
7172       SetGameInfo();
7173       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7174       DisplayTitle(buf);
7175       if (first.sendName) {
7176         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7177         SendToProgram(buf, &first);
7178       }
7179       StartClocks();
7180     }
7181     ModeHighlight();
7182   }
7183
7184   /* Relay move to ICS or chess engine */
7185   if (appData.icsActive) {
7186     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7187         gameMode == IcsExamining) {
7188       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7189         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7190         SendToICS("draw ");
7191         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7192       }
7193       // also send plain move, in case ICS does not understand atomic claims
7194       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7195       ics_user_moved = 1;
7196     }
7197   } else {
7198     if (first.sendTime && (gameMode == BeginningOfGame ||
7199                            gameMode == MachinePlaysWhite ||
7200                            gameMode == MachinePlaysBlack)) {
7201       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7202     }
7203     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7204          // [HGM] book: if program might be playing, let it use book
7205         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7206         first.maybeThinking = TRUE;
7207     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7208         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7209         SendBoard(&first, currentMove+1);
7210         if(second.analyzing) {
7211             if(!second.useSetboard) SendToProgram("undo\n", &second);
7212             SendBoard(&second, currentMove+1);
7213         }
7214     } else {
7215         SendMoveToProgram(forwardMostMove-1, &first);
7216         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7217     }
7218     if (currentMove == cmailOldMove + 1) {
7219       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7220     }
7221   }
7222
7223   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7224
7225   switch (gameMode) {
7226   case EditGame:
7227     if(appData.testLegality)
7228     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7229     case MT_NONE:
7230     case MT_CHECK:
7231       break;
7232     case MT_CHECKMATE:
7233     case MT_STAINMATE:
7234       if (WhiteOnMove(currentMove)) {
7235         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7236       } else {
7237         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7238       }
7239       break;
7240     case MT_STALEMATE:
7241       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7242       break;
7243     }
7244     break;
7245
7246   case MachinePlaysBlack:
7247   case MachinePlaysWhite:
7248     /* disable certain menu options while machine is thinking */
7249     SetMachineThinkingEnables();
7250     break;
7251
7252   default:
7253     break;
7254   }
7255
7256   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7257   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7258
7259   if(bookHit) { // [HGM] book: simulate book reply
7260         static char bookMove[MSG_SIZ]; // a bit generous?
7261
7262         programStats.nodes = programStats.depth = programStats.time =
7263         programStats.score = programStats.got_only_move = 0;
7264         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7265
7266         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7267         strcat(bookMove, bookHit);
7268         HandleMachineMove(bookMove, &first);
7269   }
7270   return 1;
7271 }
7272
7273 void
7274 MarkByFEN(char *fen)
7275 {
7276         int r, f;
7277         if(!appData.markers || !appData.highlightDragging) return;
7278         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7279         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7280         while(*fen) {
7281             int s = 0;
7282             marker[r][f] = 0;
7283             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7284             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7285             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7286             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7287             if(*fen == 'T') marker[r][f++] = 0; else
7288             if(*fen == 'Y') marker[r][f++] = 1; else
7289             if(*fen == 'G') marker[r][f++] = 3; else
7290             if(*fen == 'B') marker[r][f++] = 4; else
7291             if(*fen == 'C') marker[r][f++] = 5; else
7292             if(*fen == 'M') marker[r][f++] = 6; else
7293             if(*fen == 'W') marker[r][f++] = 7; else
7294             if(*fen == 'D') marker[r][f++] = 8; else
7295             if(*fen == 'R') marker[r][f++] = 2; else {
7296                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7297               f += s; fen -= s>0;
7298             }
7299             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7300             if(r < 0) break;
7301             fen++;
7302         }
7303         DrawPosition(TRUE, NULL);
7304 }
7305
7306 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7307
7308 void
7309 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7310 {
7311     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7312     Markers *m = (Markers *) closure;
7313     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7314         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7315                          || kind == WhiteCapturesEnPassant
7316                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7317     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7318 }
7319
7320 static int hoverSavedValid;
7321
7322 void
7323 MarkTargetSquares (int clear)
7324 {
7325   int x, y, sum=0;
7326   if(clear) { // no reason to ever suppress clearing
7327     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7328     hoverSavedValid = 0;
7329     if(!sum) return; // nothing was cleared,no redraw needed
7330   } else {
7331     int capt = 0;
7332     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7333        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7334     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7335     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7336       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7337       if(capt)
7338       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7339     }
7340   }
7341   DrawPosition(FALSE, NULL);
7342 }
7343
7344 int
7345 Explode (Board board, int fromX, int fromY, int toX, int toY)
7346 {
7347     if(gameInfo.variant == VariantAtomic &&
7348        (board[toY][toX] != EmptySquare ||                     // capture?
7349         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7350                          board[fromY][fromX] == BlackPawn   )
7351       )) {
7352         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7353         return TRUE;
7354     }
7355     return FALSE;
7356 }
7357
7358 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7359
7360 int
7361 CanPromote (ChessSquare piece, int y)
7362 {
7363         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7364         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7365         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7366         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7367            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7368            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7369          gameInfo.variant == VariantMakruk) return FALSE;
7370         return (piece == BlackPawn && y <= zone ||
7371                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7372                 piece == BlackLance && y <= zone ||
7373                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7374 }
7375
7376 void
7377 HoverEvent (int xPix, int yPix, int x, int y)
7378 {
7379         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7380         int r, f;
7381         if(!first.highlight) return;
7382         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7383         if(x == oldX && y == oldY) return; // only do something if we enter new square
7384         oldFromX = fromX; oldFromY = fromY;
7385         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7386           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7387             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7388           hoverSavedValid = 1;
7389         } else if(oldX != x || oldY != y) {
7390           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7391           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7392           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7393             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7394           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7395             char buf[MSG_SIZ];
7396             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7397             SendToProgram(buf, &first);
7398           }
7399           oldX = x; oldY = y;
7400 //        SetHighlights(fromX, fromY, x, y);
7401         }
7402 }
7403
7404 void ReportClick(char *action, int x, int y)
7405 {
7406         char buf[MSG_SIZ]; // Inform engine of what user does
7407         int r, f;
7408         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7409           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7410             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7411         if(!first.highlight || gameMode == EditPosition) return;
7412         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7413         SendToProgram(buf, &first);
7414 }
7415
7416 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7417
7418 void
7419 LeftClick (ClickType clickType, int xPix, int yPix)
7420 {
7421     int x, y;
7422     Boolean saveAnimate;
7423     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7424     char promoChoice = NULLCHAR;
7425     ChessSquare piece;
7426     static TimeMark lastClickTime, prevClickTime;
7427
7428     x = EventToSquare(xPix, BOARD_WIDTH);
7429     y = EventToSquare(yPix, BOARD_HEIGHT);
7430     if (!flipView && y >= 0) {
7431         y = BOARD_HEIGHT - 1 - y;
7432     }
7433     if (flipView && x >= 0) {
7434         x = BOARD_WIDTH - 1 - x;
7435     }
7436
7437     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7438         static int dummy;
7439         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7440         right = TRUE;
7441         return;
7442     }
7443
7444     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7445
7446     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7447
7448     if (clickType == Press) ErrorPopDown();
7449     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7450
7451     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7452         defaultPromoChoice = promoSweep;
7453         promoSweep = EmptySquare;   // terminate sweep
7454         promoDefaultAltered = TRUE;
7455         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7456     }
7457
7458     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7459         if(clickType == Release) return; // ignore upclick of click-click destination
7460         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7461         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7462         if(gameInfo.holdingsWidth &&
7463                 (WhiteOnMove(currentMove)
7464                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7465                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7466             // click in right holdings, for determining promotion piece
7467             ChessSquare p = boards[currentMove][y][x];
7468             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7469             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7470             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7471                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7472                 fromX = fromY = -1;
7473                 return;
7474             }
7475         }
7476         DrawPosition(FALSE, boards[currentMove]);
7477         return;
7478     }
7479
7480     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7481     if(clickType == Press
7482             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7483               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7484               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7485         return;
7486
7487     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7488         // could be static click on premove from-square: abort premove
7489         gotPremove = 0;
7490         ClearPremoveHighlights();
7491     }
7492
7493     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7494         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7495
7496     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7497         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7498                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7499         defaultPromoChoice = DefaultPromoChoice(side);
7500     }
7501
7502     autoQueen = appData.alwaysPromoteToQueen;
7503
7504     if (fromX == -1) {
7505       int originalY = y;
7506       gatingPiece = EmptySquare;
7507       if (clickType != Press) {
7508         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7509             DragPieceEnd(xPix, yPix); dragging = 0;
7510             DrawPosition(FALSE, NULL);
7511         }
7512         return;
7513       }
7514       doubleClick = FALSE;
7515       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7516         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7517       }
7518       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7519       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7520          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7521          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7522             /* First square */
7523             if (OKToStartUserMove(fromX, fromY)) {
7524                 second = 0;
7525                 ReportClick("lift", x, y);
7526                 MarkTargetSquares(0);
7527                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7528                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7529                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7530                     promoSweep = defaultPromoChoice;
7531                     selectFlag = 0; lastX = xPix; lastY = yPix;
7532                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7533                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7534                 }
7535                 if (appData.highlightDragging) {
7536                     SetHighlights(fromX, fromY, -1, -1);
7537                 } else {
7538                     ClearHighlights();
7539                 }
7540             } else fromX = fromY = -1;
7541             return;
7542         }
7543     }
7544
7545     /* fromX != -1 */
7546     if (clickType == Press && gameMode != EditPosition) {
7547         ChessSquare fromP;
7548         ChessSquare toP;
7549         int frc;
7550
7551         // ignore off-board to clicks
7552         if(y < 0 || x < 0) return;
7553
7554         /* Check if clicking again on the same color piece */
7555         fromP = boards[currentMove][fromY][fromX];
7556         toP = boards[currentMove][y][x];
7557         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7558         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7559             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7560            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7561              WhitePawn <= toP && toP <= WhiteKing &&
7562              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7563              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7564             (BlackPawn <= fromP && fromP <= BlackKing &&
7565              BlackPawn <= toP && toP <= BlackKing &&
7566              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7567              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7568             /* Clicked again on same color piece -- changed his mind */
7569             second = (x == fromX && y == fromY);
7570             killX = killY = -1;
7571             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7572                 second = FALSE; // first double-click rather than scond click
7573                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7574             }
7575             promoDefaultAltered = FALSE;
7576             MarkTargetSquares(1);
7577            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7578             if (appData.highlightDragging) {
7579                 SetHighlights(x, y, -1, -1);
7580             } else {
7581                 ClearHighlights();
7582             }
7583             if (OKToStartUserMove(x, y)) {
7584                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7585                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7586                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7587                  gatingPiece = boards[currentMove][fromY][fromX];
7588                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7589                 fromX = x;
7590                 fromY = y; dragging = 1;
7591                 if(!second) ReportClick("lift", x, y);
7592                 MarkTargetSquares(0);
7593                 DragPieceBegin(xPix, yPix, FALSE);
7594                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7595                     promoSweep = defaultPromoChoice;
7596                     selectFlag = 0; lastX = xPix; lastY = yPix;
7597                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7598                 }
7599             }
7600            }
7601            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7602            second = FALSE;
7603         }
7604         // ignore clicks on holdings
7605         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7606     }
7607
7608     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7609         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7610         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7611         return;
7612     }
7613
7614     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7615         DragPieceEnd(xPix, yPix); dragging = 0;
7616         if(clearFlag) {
7617             // a deferred attempt to click-click move an empty square on top of a piece
7618             boards[currentMove][y][x] = EmptySquare;
7619             ClearHighlights();
7620             DrawPosition(FALSE, boards[currentMove]);
7621             fromX = fromY = -1; clearFlag = 0;
7622             return;
7623         }
7624         if (appData.animateDragging) {
7625             /* Undo animation damage if any */
7626             DrawPosition(FALSE, NULL);
7627         }
7628         if (second) {
7629             /* Second up/down in same square; just abort move */
7630             second = 0;
7631             fromX = fromY = -1;
7632             gatingPiece = EmptySquare;
7633             MarkTargetSquares(1);
7634             ClearHighlights();
7635             gotPremove = 0;
7636             ClearPremoveHighlights();
7637         } else {
7638             /* First upclick in same square; start click-click mode */
7639             SetHighlights(x, y, -1, -1);
7640         }
7641         return;
7642     }
7643
7644     clearFlag = 0;
7645
7646     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7647        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7648         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7649         DisplayMessage(_("only marked squares are legal"),"");
7650         DrawPosition(TRUE, NULL);
7651         return; // ignore to-click
7652     }
7653
7654     /* we now have a different from- and (possibly off-board) to-square */
7655     /* Completed move */
7656     if(!sweepSelecting) {
7657         toX = x;
7658         toY = y;
7659     }
7660
7661     piece = boards[currentMove][fromY][fromX];
7662
7663     saveAnimate = appData.animate;
7664     if (clickType == Press) {
7665         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7666         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7667             // must be Edit Position mode with empty-square selected
7668             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7669             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7670             return;
7671         }
7672         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7673             return;
7674         }
7675         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7676             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7677         } else
7678         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7679         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7680           if(appData.sweepSelect) {
7681             promoSweep = defaultPromoChoice;
7682             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7683             selectFlag = 0; lastX = xPix; lastY = yPix;
7684             Sweep(0); // Pawn that is going to promote: preview promotion piece
7685             sweepSelecting = 1;
7686             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7687             MarkTargetSquares(1);
7688           }
7689           return; // promo popup appears on up-click
7690         }
7691         /* Finish clickclick move */
7692         if (appData.animate || appData.highlightLastMove) {
7693             SetHighlights(fromX, fromY, toX, toY);
7694         } else {
7695             ClearHighlights();
7696         }
7697     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7698         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7699         if (appData.animate || appData.highlightLastMove) {
7700             SetHighlights(fromX, fromY, toX, toY);
7701         } else {
7702             ClearHighlights();
7703         }
7704     } else {
7705 #if 0
7706 // [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
7707         /* Finish drag move */
7708         if (appData.highlightLastMove) {
7709             SetHighlights(fromX, fromY, toX, toY);
7710         } else {
7711             ClearHighlights();
7712         }
7713 #endif
7714         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7715         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7716           dragging *= 2;            // flag button-less dragging if we are dragging
7717           MarkTargetSquares(1);
7718           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7719           else {
7720             kill2X = killX; kill2Y = killY;
7721             killX = x; killY = y;     //remeber this square as intermediate
7722             ReportClick("put", x, y); // and inform engine
7723             ReportClick("lift", x, y);
7724             MarkTargetSquares(0);
7725             return;
7726           }
7727         }
7728         DragPieceEnd(xPix, yPix); dragging = 0;
7729         /* Don't animate move and drag both */
7730         appData.animate = FALSE;
7731     }
7732
7733     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7734     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7735         ChessSquare piece = boards[currentMove][fromY][fromX];
7736         if(gameMode == EditPosition && piece != EmptySquare &&
7737            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7738             int n;
7739
7740             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7741                 n = PieceToNumber(piece - (int)BlackPawn);
7742                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7743                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7744                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7745             } else
7746             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7747                 n = PieceToNumber(piece);
7748                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7749                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7750                 boards[currentMove][n][BOARD_WIDTH-2]++;
7751             }
7752             boards[currentMove][fromY][fromX] = EmptySquare;
7753         }
7754         ClearHighlights();
7755         fromX = fromY = -1;
7756         MarkTargetSquares(1);
7757         DrawPosition(TRUE, boards[currentMove]);
7758         return;
7759     }
7760
7761     // off-board moves should not be highlighted
7762     if(x < 0 || y < 0) ClearHighlights();
7763     else ReportClick("put", x, y);
7764
7765     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7766
7767     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7768
7769     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7770         SetHighlights(fromX, fromY, toX, toY);
7771         MarkTargetSquares(1);
7772         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7773             // [HGM] super: promotion to captured piece selected from holdings
7774             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7775             promotionChoice = TRUE;
7776             // kludge follows to temporarily execute move on display, without promoting yet
7777             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7778             boards[currentMove][toY][toX] = p;
7779             DrawPosition(FALSE, boards[currentMove]);
7780             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7781             boards[currentMove][toY][toX] = q;
7782             DisplayMessage("Click in holdings to choose piece", "");
7783             return;
7784         }
7785         PromotionPopUp(promoChoice);
7786     } else {
7787         int oldMove = currentMove;
7788         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7789         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7790         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7791         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7792            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7793             DrawPosition(TRUE, boards[currentMove]);
7794         MarkTargetSquares(1);
7795         fromX = fromY = -1;
7796     }
7797     appData.animate = saveAnimate;
7798     if (appData.animate || appData.animateDragging) {
7799         /* Undo animation damage if needed */
7800         DrawPosition(FALSE, NULL);
7801     }
7802 }
7803
7804 int
7805 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7806 {   // front-end-free part taken out of PieceMenuPopup
7807     int whichMenu; int xSqr, ySqr;
7808
7809     if(seekGraphUp) { // [HGM] seekgraph
7810         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7811         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7812         return -2;
7813     }
7814
7815     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7816          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7817         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7818         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7819         if(action == Press)   {
7820             originalFlip = flipView;
7821             flipView = !flipView; // temporarily flip board to see game from partners perspective
7822             DrawPosition(TRUE, partnerBoard);
7823             DisplayMessage(partnerStatus, "");
7824             partnerUp = TRUE;
7825         } else if(action == Release) {
7826             flipView = originalFlip;
7827             DrawPosition(TRUE, boards[currentMove]);
7828             partnerUp = FALSE;
7829         }
7830         return -2;
7831     }
7832
7833     xSqr = EventToSquare(x, BOARD_WIDTH);
7834     ySqr = EventToSquare(y, BOARD_HEIGHT);
7835     if (action == Release) {
7836         if(pieceSweep != EmptySquare) {
7837             EditPositionMenuEvent(pieceSweep, toX, toY);
7838             pieceSweep = EmptySquare;
7839         } else UnLoadPV(); // [HGM] pv
7840     }
7841     if (action != Press) return -2; // return code to be ignored
7842     switch (gameMode) {
7843       case IcsExamining:
7844         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7845       case EditPosition:
7846         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7847         if (xSqr < 0 || ySqr < 0) return -1;
7848         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7849         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7850         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7851         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7852         NextPiece(0);
7853         return 2; // grab
7854       case IcsObserving:
7855         if(!appData.icsEngineAnalyze) return -1;
7856       case IcsPlayingWhite:
7857       case IcsPlayingBlack:
7858         if(!appData.zippyPlay) goto noZip;
7859       case AnalyzeMode:
7860       case AnalyzeFile:
7861       case MachinePlaysWhite:
7862       case MachinePlaysBlack:
7863       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7864         if (!appData.dropMenu) {
7865           LoadPV(x, y);
7866           return 2; // flag front-end to grab mouse events
7867         }
7868         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7869            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7870       case EditGame:
7871       noZip:
7872         if (xSqr < 0 || ySqr < 0) return -1;
7873         if (!appData.dropMenu || appData.testLegality &&
7874             gameInfo.variant != VariantBughouse &&
7875             gameInfo.variant != VariantCrazyhouse) return -1;
7876         whichMenu = 1; // drop menu
7877         break;
7878       default:
7879         return -1;
7880     }
7881
7882     if (((*fromX = xSqr) < 0) ||
7883         ((*fromY = ySqr) < 0)) {
7884         *fromX = *fromY = -1;
7885         return -1;
7886     }
7887     if (flipView)
7888       *fromX = BOARD_WIDTH - 1 - *fromX;
7889     else
7890       *fromY = BOARD_HEIGHT - 1 - *fromY;
7891
7892     return whichMenu;
7893 }
7894
7895 void
7896 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7897 {
7898 //    char * hint = lastHint;
7899     FrontEndProgramStats stats;
7900
7901     stats.which = cps == &first ? 0 : 1;
7902     stats.depth = cpstats->depth;
7903     stats.nodes = cpstats->nodes;
7904     stats.score = cpstats->score;
7905     stats.time = cpstats->time;
7906     stats.pv = cpstats->movelist;
7907     stats.hint = lastHint;
7908     stats.an_move_index = 0;
7909     stats.an_move_count = 0;
7910
7911     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7912         stats.hint = cpstats->move_name;
7913         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7914         stats.an_move_count = cpstats->nr_moves;
7915     }
7916
7917     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
7918
7919     SetProgramStats( &stats );
7920 }
7921
7922 void
7923 ClearEngineOutputPane (int which)
7924 {
7925     static FrontEndProgramStats dummyStats;
7926     dummyStats.which = which;
7927     dummyStats.pv = "#";
7928     SetProgramStats( &dummyStats );
7929 }
7930
7931 #define MAXPLAYERS 500
7932
7933 char *
7934 TourneyStandings (int display)
7935 {
7936     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7937     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7938     char result, *p, *names[MAXPLAYERS];
7939
7940     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7941         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7942     names[0] = p = strdup(appData.participants);
7943     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7944
7945     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7946
7947     while(result = appData.results[nr]) {
7948         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7949         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7950         wScore = bScore = 0;
7951         switch(result) {
7952           case '+': wScore = 2; break;
7953           case '-': bScore = 2; break;
7954           case '=': wScore = bScore = 1; break;
7955           case ' ':
7956           case '*': return strdup("busy"); // tourney not finished
7957         }
7958         score[w] += wScore;
7959         score[b] += bScore;
7960         games[w]++;
7961         games[b]++;
7962         nr++;
7963     }
7964     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7965     for(w=0; w<nPlayers; w++) {
7966         bScore = -1;
7967         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7968         ranking[w] = b; points[w] = bScore; score[b] = -2;
7969     }
7970     p = malloc(nPlayers*34+1);
7971     for(w=0; w<nPlayers && w<display; w++)
7972         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7973     free(names[0]);
7974     return p;
7975 }
7976
7977 void
7978 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7979 {       // count all piece types
7980         int p, f, r;
7981         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7982         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7983         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7984                 p = board[r][f];
7985                 pCnt[p]++;
7986                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7987                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7988                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7989                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7990                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7991                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7992         }
7993 }
7994
7995 int
7996 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7997 {
7998         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7999         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8000
8001         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8002         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8003         if(myPawns == 2 && nMine == 3) // KPP
8004             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8005         if(myPawns == 1 && nMine == 2) // KP
8006             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8007         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8008             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8009         if(myPawns) return FALSE;
8010         if(pCnt[WhiteRook+side])
8011             return pCnt[BlackRook-side] ||
8012                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8013                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8014                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8015         if(pCnt[WhiteCannon+side]) {
8016             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8017             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8018         }
8019         if(pCnt[WhiteKnight+side])
8020             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8021         return FALSE;
8022 }
8023
8024 int
8025 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8026 {
8027         VariantClass v = gameInfo.variant;
8028
8029         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8030         if(v == VariantShatranj) return TRUE; // always winnable through baring
8031         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8032         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8033
8034         if(v == VariantXiangqi) {
8035                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8036
8037                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8038                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8039                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8040                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8041                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8042                 if(stale) // we have at least one last-rank P plus perhaps C
8043                     return majors // KPKX
8044                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8045                 else // KCA*E*
8046                     return pCnt[WhiteFerz+side] // KCAK
8047                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8048                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8049                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8050
8051         } else if(v == VariantKnightmate) {
8052                 if(nMine == 1) return FALSE;
8053                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8054         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8055                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8056
8057                 if(nMine == 1) return FALSE; // bare King
8058                 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
8059                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8060                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8061                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8062                 if(pCnt[WhiteKnight+side])
8063                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8064                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8065                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8066                 if(nBishops)
8067                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8068                 if(pCnt[WhiteAlfil+side])
8069                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8070                 if(pCnt[WhiteWazir+side])
8071                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8072         }
8073
8074         return TRUE;
8075 }
8076
8077 int
8078 CompareWithRights (Board b1, Board b2)
8079 {
8080     int rights = 0;
8081     if(!CompareBoards(b1, b2)) return FALSE;
8082     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8083     /* compare castling rights */
8084     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8085            rights++; /* King lost rights, while rook still had them */
8086     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8087         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8088            rights++; /* but at least one rook lost them */
8089     }
8090     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8091            rights++;
8092     if( b1[CASTLING][5] != NoRights ) {
8093         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8094            rights++;
8095     }
8096     return rights == 0;
8097 }
8098
8099 int
8100 Adjudicate (ChessProgramState *cps)
8101 {       // [HGM] some adjudications useful with buggy engines
8102         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8103         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8104         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8105         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8106         int k, drop, count = 0; static int bare = 1;
8107         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8108         Boolean canAdjudicate = !appData.icsActive;
8109
8110         // most tests only when we understand the game, i.e. legality-checking on
8111             if( appData.testLegality )
8112             {   /* [HGM] Some more adjudications for obstinate engines */
8113                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8114                 static int moveCount = 6;
8115                 ChessMove result;
8116                 char *reason = NULL;
8117
8118                 /* Count what is on board. */
8119                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8120
8121                 /* Some material-based adjudications that have to be made before stalemate test */
8122                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8123                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8124                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8125                      if(canAdjudicate && appData.checkMates) {
8126                          if(engineOpponent)
8127                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8128                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8129                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8130                          return 1;
8131                      }
8132                 }
8133
8134                 /* Bare King in Shatranj (loses) or Losers (wins) */
8135                 if( nrW == 1 || nrB == 1) {
8136                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8137                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8138                      if(canAdjudicate && appData.checkMates) {
8139                          if(engineOpponent)
8140                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8141                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8142                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8143                          return 1;
8144                      }
8145                   } else
8146                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8147                   {    /* bare King */
8148                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8149                         if(canAdjudicate && appData.checkMates) {
8150                             /* but only adjudicate if adjudication enabled */
8151                             if(engineOpponent)
8152                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8153                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8154                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8155                             return 1;
8156                         }
8157                   }
8158                 } else bare = 1;
8159
8160
8161             // don't wait for engine to announce game end if we can judge ourselves
8162             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8163               case MT_CHECK:
8164                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8165                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8166                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8167                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8168                             checkCnt++;
8169                         if(checkCnt >= 2) {
8170                             reason = "Xboard adjudication: 3rd check";
8171                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8172                             break;
8173                         }
8174                     }
8175                 }
8176               case MT_NONE:
8177               default:
8178                 break;
8179               case MT_STEALMATE:
8180               case MT_STALEMATE:
8181               case MT_STAINMATE:
8182                 reason = "Xboard adjudication: Stalemate";
8183                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8184                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8185                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8186                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8187                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8188                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8189                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8190                                                                         EP_CHECKMATE : EP_WINS);
8191                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8192                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8193                 }
8194                 break;
8195               case MT_CHECKMATE:
8196                 reason = "Xboard adjudication: Checkmate";
8197                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8198                 if(gameInfo.variant == VariantShogi) {
8199                     if(forwardMostMove > backwardMostMove
8200                        && moveList[forwardMostMove-1][1] == '@'
8201                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8202                         reason = "XBoard adjudication: pawn-drop mate";
8203                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8204                     }
8205                 }
8206                 break;
8207             }
8208
8209                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8210                     case EP_STALEMATE:
8211                         result = GameIsDrawn; break;
8212                     case EP_CHECKMATE:
8213                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8214                     case EP_WINS:
8215                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8216                     default:
8217                         result = EndOfFile;
8218                 }
8219                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8220                     if(engineOpponent)
8221                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8222                     GameEnds( result, reason, GE_XBOARD );
8223                     return 1;
8224                 }
8225
8226                 /* Next absolutely insufficient mating material. */
8227                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8228                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8229                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8230
8231                      /* always flag draws, for judging claims */
8232                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8233
8234                      if(canAdjudicate && appData.materialDraws) {
8235                          /* but only adjudicate them if adjudication enabled */
8236                          if(engineOpponent) {
8237                            SendToProgram("force\n", engineOpponent); // suppress reply
8238                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8239                          }
8240                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8241                          return 1;
8242                      }
8243                 }
8244
8245                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8246                 if(gameInfo.variant == VariantXiangqi ?
8247                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8248                  : nrW + nrB == 4 &&
8249                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8250                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8251                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8252                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8253                    ) ) {
8254                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8255                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8256                           if(engineOpponent) {
8257                             SendToProgram("force\n", engineOpponent); // suppress reply
8258                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8259                           }
8260                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8261                           return 1;
8262                      }
8263                 } else moveCount = 6;
8264             }
8265
8266         // Repetition draws and 50-move rule can be applied independently of legality testing
8267
8268                 /* Check for rep-draws */
8269                 count = 0;
8270                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8271                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8272                 for(k = forwardMostMove-2;
8273                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8274                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8275                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8276                     k-=2)
8277                 {   int rights=0;
8278                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8279                         /* compare castling rights */
8280                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8281                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8282                                 rights++; /* King lost rights, while rook still had them */
8283                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8284                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8285                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8286                                    rights++; /* but at least one rook lost them */
8287                         }
8288                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8289                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8290                                 rights++;
8291                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8292                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8293                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8294                                    rights++;
8295                         }
8296                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8297                             && appData.drawRepeats > 1) {
8298                              /* adjudicate after user-specified nr of repeats */
8299                              int result = GameIsDrawn;
8300                              char *details = "XBoard adjudication: repetition draw";
8301                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8302                                 // [HGM] xiangqi: check for forbidden perpetuals
8303                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8304                                 for(m=forwardMostMove; m>k; m-=2) {
8305                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8306                                         ourPerpetual = 0; // the current mover did not always check
8307                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8308                                         hisPerpetual = 0; // the opponent did not always check
8309                                 }
8310                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8311                                                                         ourPerpetual, hisPerpetual);
8312                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8313                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8314                                     details = "Xboard adjudication: perpetual checking";
8315                                 } else
8316                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8317                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8318                                 } else
8319                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8320                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8321                                         result = BlackWins;
8322                                         details = "Xboard adjudication: repetition";
8323                                     }
8324                                 } else // it must be XQ
8325                                 // Now check for perpetual chases
8326                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8327                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8328                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8329                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8330                                         static char resdet[MSG_SIZ];
8331                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8332                                         details = resdet;
8333                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8334                                     } else
8335                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8336                                         break; // Abort repetition-checking loop.
8337                                 }
8338                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8339                              }
8340                              if(engineOpponent) {
8341                                SendToProgram("force\n", engineOpponent); // suppress reply
8342                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8343                              }
8344                              GameEnds( result, details, GE_XBOARD );
8345                              return 1;
8346                         }
8347                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8348                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8349                     }
8350                 }
8351
8352                 /* Now we test for 50-move draws. Determine ply count */
8353                 count = forwardMostMove;
8354                 /* look for last irreversble move */
8355                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8356                     count--;
8357                 /* if we hit starting position, add initial plies */
8358                 if( count == backwardMostMove )
8359                     count -= initialRulePlies;
8360                 count = forwardMostMove - count;
8361                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8362                         // adjust reversible move counter for checks in Xiangqi
8363                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8364                         if(i < backwardMostMove) i = backwardMostMove;
8365                         while(i <= forwardMostMove) {
8366                                 lastCheck = inCheck; // check evasion does not count
8367                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8368                                 if(inCheck || lastCheck) count--; // check does not count
8369                                 i++;
8370                         }
8371                 }
8372                 if( count >= 100)
8373                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8374                          /* this is used to judge if draw claims are legal */
8375                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8376                          if(engineOpponent) {
8377                            SendToProgram("force\n", engineOpponent); // suppress reply
8378                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8379                          }
8380                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8381                          return 1;
8382                 }
8383
8384                 /* if draw offer is pending, treat it as a draw claim
8385                  * when draw condition present, to allow engines a way to
8386                  * claim draws before making their move to avoid a race
8387                  * condition occurring after their move
8388                  */
8389                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8390                          char *p = NULL;
8391                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8392                              p = "Draw claim: 50-move rule";
8393                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8394                              p = "Draw claim: 3-fold repetition";
8395                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8396                              p = "Draw claim: insufficient mating material";
8397                          if( p != NULL && canAdjudicate) {
8398                              if(engineOpponent) {
8399                                SendToProgram("force\n", engineOpponent); // suppress reply
8400                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8401                              }
8402                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8403                              return 1;
8404                          }
8405                 }
8406
8407                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8408                     if(engineOpponent) {
8409                       SendToProgram("force\n", engineOpponent); // suppress reply
8410                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8411                     }
8412                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8413                     return 1;
8414                 }
8415         return 0;
8416 }
8417
8418 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8419 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8420 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8421
8422 static int
8423 BitbaseProbe ()
8424 {
8425     int pieces[10], squares[10], cnt=0, r, f, res;
8426     static int loaded;
8427     static PPROBE_EGBB probeBB;
8428     if(!appData.testLegality) return 10;
8429     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8430     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8431     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8432     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8433         ChessSquare piece = boards[forwardMostMove][r][f];
8434         int black = (piece >= BlackPawn);
8435         int type = piece - black*BlackPawn;
8436         if(piece == EmptySquare) continue;
8437         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8438         if(type == WhiteKing) type = WhiteQueen + 1;
8439         type = egbbCode[type];
8440         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8441         pieces[cnt] = type + black*6;
8442         if(++cnt > 5) return 11;
8443     }
8444     pieces[cnt] = squares[cnt] = 0;
8445     // probe EGBB
8446     if(loaded == 2) return 13; // loading failed before
8447     if(loaded == 0) {
8448         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8449         HMODULE lib;
8450         PLOAD_EGBB loadBB;
8451         loaded = 2; // prepare for failure
8452         if(!path) return 13; // no egbb installed
8453         strncpy(buf, path + 8, MSG_SIZ);
8454         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8455         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8456         lib = LoadLibrary(buf);
8457         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8458         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8459         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8460         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8461         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8462         loaded = 1; // success!
8463     }
8464     res = probeBB(forwardMostMove & 1, pieces, squares);
8465     return res > 0 ? 1 : res < 0 ? -1 : 0;
8466 }
8467
8468 char *
8469 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8470 {   // [HGM] book: this routine intercepts moves to simulate book replies
8471     char *bookHit = NULL;
8472
8473     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8474         char buf[MSG_SIZ];
8475         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8476         SendToProgram(buf, cps);
8477     }
8478     //first determine if the incoming move brings opponent into his book
8479     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8480         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8481     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8482     if(bookHit != NULL && !cps->bookSuspend) {
8483         // make sure opponent is not going to reply after receiving move to book position
8484         SendToProgram("force\n", cps);
8485         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8486     }
8487     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8488     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8489     // now arrange restart after book miss
8490     if(bookHit) {
8491         // after a book hit we never send 'go', and the code after the call to this routine
8492         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8493         char buf[MSG_SIZ], *move = bookHit;
8494         if(cps->useSAN) {
8495             int fromX, fromY, toX, toY;
8496             char promoChar;
8497             ChessMove moveType;
8498             move = buf + 30;
8499             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8500                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8501                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8502                                     PosFlags(forwardMostMove),
8503                                     fromY, fromX, toY, toX, promoChar, move);
8504             } else {
8505                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8506                 bookHit = NULL;
8507             }
8508         }
8509         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8510         SendToProgram(buf, cps);
8511         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8512     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8513         SendToProgram("go\n", cps);
8514         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8515     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8516         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8517             SendToProgram("go\n", cps);
8518         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8519     }
8520     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8521 }
8522
8523 int
8524 LoadError (char *errmess, ChessProgramState *cps)
8525 {   // unloads engine and switches back to -ncp mode if it was first
8526     if(cps->initDone) return FALSE;
8527     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8528     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8529     cps->pr = NoProc;
8530     if(cps == &first) {
8531         appData.noChessProgram = TRUE;
8532         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8533         gameMode = BeginningOfGame; ModeHighlight();
8534         SetNCPMode();
8535     }
8536     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8537     DisplayMessage("", ""); // erase waiting message
8538     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8539     return TRUE;
8540 }
8541
8542 char *savedMessage;
8543 ChessProgramState *savedState;
8544 void
8545 DeferredBookMove (void)
8546 {
8547         if(savedState->lastPing != savedState->lastPong)
8548                     ScheduleDelayedEvent(DeferredBookMove, 10);
8549         else
8550         HandleMachineMove(savedMessage, savedState);
8551 }
8552
8553 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8554 static ChessProgramState *stalledEngine;
8555 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8556
8557 void
8558 HandleMachineMove (char *message, ChessProgramState *cps)
8559 {
8560     static char firstLeg[20];
8561     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8562     char realname[MSG_SIZ];
8563     int fromX, fromY, toX, toY;
8564     ChessMove moveType;
8565     char promoChar, roar;
8566     char *p, *pv=buf1;
8567     int machineWhite, oldError;
8568     char *bookHit;
8569
8570     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8571         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8572         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8573             DisplayError(_("Invalid pairing from pairing engine"), 0);
8574             return;
8575         }
8576         pairingReceived = 1;
8577         NextMatchGame();
8578         return; // Skim the pairing messages here.
8579     }
8580
8581     oldError = cps->userError; cps->userError = 0;
8582
8583 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8584     /*
8585      * Kludge to ignore BEL characters
8586      */
8587     while (*message == '\007') message++;
8588
8589     /*
8590      * [HGM] engine debug message: ignore lines starting with '#' character
8591      */
8592     if(cps->debug && *message == '#') return;
8593
8594     /*
8595      * Look for book output
8596      */
8597     if (cps == &first && bookRequested) {
8598         if (message[0] == '\t' || message[0] == ' ') {
8599             /* Part of the book output is here; append it */
8600             strcat(bookOutput, message);
8601             strcat(bookOutput, "  \n");
8602             return;
8603         } else if (bookOutput[0] != NULLCHAR) {
8604             /* All of book output has arrived; display it */
8605             char *p = bookOutput;
8606             while (*p != NULLCHAR) {
8607                 if (*p == '\t') *p = ' ';
8608                 p++;
8609             }
8610             DisplayInformation(bookOutput);
8611             bookRequested = FALSE;
8612             /* Fall through to parse the current output */
8613         }
8614     }
8615
8616     /*
8617      * Look for machine move.
8618      */
8619     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8620         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8621     {
8622         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8623             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8624             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8625             stalledEngine = cps;
8626             if(appData.ponderNextMove) { // bring opponent out of ponder
8627                 if(gameMode == TwoMachinesPlay) {
8628                     if(cps->other->pause)
8629                         PauseEngine(cps->other);
8630                     else
8631                         SendToProgram("easy\n", cps->other);
8632                 }
8633             }
8634             StopClocks();
8635             return;
8636         }
8637
8638       if(cps->usePing) {
8639
8640         /* This method is only useful on engines that support ping */
8641         if(abortEngineThink) {
8642             if (appData.debugMode) {
8643                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8644             }
8645             SendToProgram("undo\n", cps);
8646             return;
8647         }
8648
8649         if (cps->lastPing != cps->lastPong) {
8650             /* Extra move from before last new; ignore */
8651             if (appData.debugMode) {
8652                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8653             }
8654           return;
8655         }
8656
8657       } else {
8658
8659         switch (gameMode) {
8660           case BeginningOfGame:
8661             /* Extra move from before last reset; ignore */
8662             if (appData.debugMode) {
8663                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8664             }
8665             return;
8666
8667           case EndOfGame:
8668           case IcsIdle:
8669           default:
8670             /* Extra move after we tried to stop.  The mode test is
8671                not a reliable way of detecting this problem, but it's
8672                the best we can do on engines that don't support ping.
8673             */
8674             if (appData.debugMode) {
8675                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8676                         cps->which, gameMode);
8677             }
8678             SendToProgram("undo\n", cps);
8679             return;
8680
8681           case MachinePlaysWhite:
8682           case IcsPlayingWhite:
8683             machineWhite = TRUE;
8684             break;
8685
8686           case MachinePlaysBlack:
8687           case IcsPlayingBlack:
8688             machineWhite = FALSE;
8689             break;
8690
8691           case TwoMachinesPlay:
8692             machineWhite = (cps->twoMachinesColor[0] == 'w');
8693             break;
8694         }
8695         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8696             if (appData.debugMode) {
8697                 fprintf(debugFP,
8698                         "Ignoring move out of turn by %s, gameMode %d"
8699                         ", forwardMost %d\n",
8700                         cps->which, gameMode, forwardMostMove);
8701             }
8702             return;
8703         }
8704       }
8705
8706         if(cps->alphaRank) AlphaRank(machineMove, 4);
8707
8708         // [HGM] lion: (some very limited) support for Alien protocol
8709         killX = killY = kill2X = kill2Y = -1;
8710         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8711             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8712             return;
8713         }
8714         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8715             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8716             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8717         }
8718         if(firstLeg[0]) { // there was a previous leg;
8719             // only support case where same piece makes two step
8720             char buf[20], *p = machineMove+1, *q = buf+1, f;
8721             safeStrCpy(buf, machineMove, 20);
8722             while(isdigit(*q)) q++; // find start of to-square
8723             safeStrCpy(machineMove, firstLeg, 20);
8724             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8725             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8726             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8727             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8728             firstLeg[0] = NULLCHAR;
8729         }
8730
8731         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8732                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8733             /* Machine move could not be parsed; ignore it. */
8734           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8735                     machineMove, _(cps->which));
8736             DisplayMoveError(buf1);
8737             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8738                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8739             if (gameMode == TwoMachinesPlay) {
8740               GameEnds(machineWhite ? BlackWins : WhiteWins,
8741                        buf1, GE_XBOARD);
8742             }
8743             return;
8744         }
8745
8746         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8747         /* So we have to redo legality test with true e.p. status here,  */
8748         /* to make sure an illegal e.p. capture does not slip through,   */
8749         /* to cause a forfeit on a justified illegal-move complaint      */
8750         /* of the opponent.                                              */
8751         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8752            ChessMove moveType;
8753            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8754                              fromY, fromX, toY, toX, promoChar);
8755             if(moveType == IllegalMove) {
8756               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8757                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8758                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8759                            buf1, GE_XBOARD);
8760                 return;
8761            } else if(!appData.fischerCastling)
8762            /* [HGM] Kludge to handle engines that send FRC-style castling
8763               when they shouldn't (like TSCP-Gothic) */
8764            switch(moveType) {
8765              case WhiteASideCastleFR:
8766              case BlackASideCastleFR:
8767                toX+=2;
8768                currentMoveString[2]++;
8769                break;
8770              case WhiteHSideCastleFR:
8771              case BlackHSideCastleFR:
8772                toX--;
8773                currentMoveString[2]--;
8774                break;
8775              default: ; // nothing to do, but suppresses warning of pedantic compilers
8776            }
8777         }
8778         hintRequested = FALSE;
8779         lastHint[0] = NULLCHAR;
8780         bookRequested = FALSE;
8781         /* Program may be pondering now */
8782         cps->maybeThinking = TRUE;
8783         if (cps->sendTime == 2) cps->sendTime = 1;
8784         if (cps->offeredDraw) cps->offeredDraw--;
8785
8786         /* [AS] Save move info*/
8787         pvInfoList[ forwardMostMove ].score = programStats.score;
8788         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8789         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8790
8791         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8792
8793         /* Test suites abort the 'game' after one move */
8794         if(*appData.finger) {
8795            static FILE *f;
8796            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8797            if(!f) f = fopen(appData.finger, "w");
8798            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8799            else { DisplayFatalError("Bad output file", errno, 0); return; }
8800            free(fen);
8801            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8802         }
8803         if(appData.epd) {
8804            if(solvingTime >= 0) {
8805               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8806               totalTime += solvingTime; first.matchWins++;
8807            } else {
8808               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8809               second.matchWins++;
8810            }
8811            OutputKibitz(2, buf1);
8812            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8813         }
8814
8815         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8816         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8817             int count = 0;
8818
8819             while( count < adjudicateLossPlies ) {
8820                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8821
8822                 if( count & 1 ) {
8823                     score = -score; /* Flip score for winning side */
8824                 }
8825
8826                 if( score > appData.adjudicateLossThreshold ) {
8827                     break;
8828                 }
8829
8830                 count++;
8831             }
8832
8833             if( count >= adjudicateLossPlies ) {
8834                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8835
8836                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8837                     "Xboard adjudication",
8838                     GE_XBOARD );
8839
8840                 return;
8841             }
8842         }
8843
8844         if(Adjudicate(cps)) {
8845             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8846             return; // [HGM] adjudicate: for all automatic game ends
8847         }
8848
8849 #if ZIPPY
8850         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8851             first.initDone) {
8852           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8853                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8854                 SendToICS("draw ");
8855                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8856           }
8857           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8858           ics_user_moved = 1;
8859           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8860                 char buf[3*MSG_SIZ];
8861
8862                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8863                         programStats.score / 100.,
8864                         programStats.depth,
8865                         programStats.time / 100.,
8866                         (unsigned int)programStats.nodes,
8867                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8868                         programStats.movelist);
8869                 SendToICS(buf);
8870           }
8871         }
8872 #endif
8873
8874         /* [AS] Clear stats for next move */
8875         ClearProgramStats();
8876         thinkOutput[0] = NULLCHAR;
8877         hiddenThinkOutputState = 0;
8878
8879         bookHit = NULL;
8880         if (gameMode == TwoMachinesPlay) {
8881             /* [HGM] relaying draw offers moved to after reception of move */
8882             /* and interpreting offer as claim if it brings draw condition */
8883             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8884                 SendToProgram("draw\n", cps->other);
8885             }
8886             if (cps->other->sendTime) {
8887                 SendTimeRemaining(cps->other,
8888                                   cps->other->twoMachinesColor[0] == 'w');
8889             }
8890             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8891             if (firstMove && !bookHit) {
8892                 firstMove = FALSE;
8893                 if (cps->other->useColors) {
8894                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8895                 }
8896                 SendToProgram("go\n", cps->other);
8897             }
8898             cps->other->maybeThinking = TRUE;
8899         }
8900
8901         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8902
8903         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8904
8905         if (!pausing && appData.ringBellAfterMoves) {
8906             if(!roar) RingBell();
8907         }
8908
8909         /*
8910          * Reenable menu items that were disabled while
8911          * machine was thinking
8912          */
8913         if (gameMode != TwoMachinesPlay)
8914             SetUserThinkingEnables();
8915
8916         // [HGM] book: after book hit opponent has received move and is now in force mode
8917         // force the book reply into it, and then fake that it outputted this move by jumping
8918         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8919         if(bookHit) {
8920                 static char bookMove[MSG_SIZ]; // a bit generous?
8921
8922                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8923                 strcat(bookMove, bookHit);
8924                 message = bookMove;
8925                 cps = cps->other;
8926                 programStats.nodes = programStats.depth = programStats.time =
8927                 programStats.score = programStats.got_only_move = 0;
8928                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8929
8930                 if(cps->lastPing != cps->lastPong) {
8931                     savedMessage = message; // args for deferred call
8932                     savedState = cps;
8933                     ScheduleDelayedEvent(DeferredBookMove, 10);
8934                     return;
8935                 }
8936                 goto FakeBookMove;
8937         }
8938
8939         return;
8940     }
8941
8942     /* Set special modes for chess engines.  Later something general
8943      *  could be added here; for now there is just one kludge feature,
8944      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8945      *  when "xboard" is given as an interactive command.
8946      */
8947     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8948         cps->useSigint = FALSE;
8949         cps->useSigterm = FALSE;
8950     }
8951     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8952       ParseFeatures(message+8, cps);
8953       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8954     }
8955
8956     if (!strncmp(message, "setup ", 6) && 
8957         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8958           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8959                                         ) { // [HGM] allow first engine to define opening position
8960       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8961       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8962       *buf = NULLCHAR;
8963       if(sscanf(message, "setup (%s", buf) == 1) {
8964         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8965         ASSIGN(appData.pieceToCharTable, buf);
8966       }
8967       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8968       if(dummy >= 3) {
8969         while(message[s] && message[s++] != ' ');
8970         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8971            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8972             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8973             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8974           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8975           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8976           startedFromSetupPosition = FALSE;
8977         }
8978       }
8979       if(startedFromSetupPosition) return;
8980       ParseFEN(boards[0], &dummy, message+s, FALSE);
8981       DrawPosition(TRUE, boards[0]);
8982       CopyBoard(initialPosition, boards[0]);
8983       startedFromSetupPosition = TRUE;
8984       return;
8985     }
8986     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8987       ChessSquare piece = WhitePawn;
8988       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
8989       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8990       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8991       piece += CharToPiece(ID & 255) - WhitePawn;
8992       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8993       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8994       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8995       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8996       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8997       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8998                                                && gameInfo.variant != VariantGreat
8999                                                && gameInfo.variant != VariantFairy    ) return;
9000       if(piece < EmptySquare) {
9001         pieceDefs = TRUE;
9002         ASSIGN(pieceDesc[piece], buf1);
9003         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9004       }
9005       return;
9006     }
9007     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9008      * want this, I was asked to put it in, and obliged.
9009      */
9010     if (!strncmp(message, "setboard ", 9)) {
9011         Board initial_position;
9012
9013         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9014
9015         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9016             DisplayError(_("Bad FEN received from engine"), 0);
9017             return ;
9018         } else {
9019            Reset(TRUE, FALSE);
9020            CopyBoard(boards[0], initial_position);
9021            initialRulePlies = FENrulePlies;
9022            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9023            else gameMode = MachinePlaysBlack;
9024            DrawPosition(FALSE, boards[currentMove]);
9025         }
9026         return;
9027     }
9028
9029     /*
9030      * Look for communication commands
9031      */
9032     if (!strncmp(message, "telluser ", 9)) {
9033         if(message[9] == '\\' && message[10] == '\\')
9034             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9035         PlayTellSound();
9036         DisplayNote(message + 9);
9037         return;
9038     }
9039     if (!strncmp(message, "tellusererror ", 14)) {
9040         cps->userError = 1;
9041         if(message[14] == '\\' && message[15] == '\\')
9042             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9043         PlayTellSound();
9044         DisplayError(message + 14, 0);
9045         return;
9046     }
9047     if (!strncmp(message, "tellopponent ", 13)) {
9048       if (appData.icsActive) {
9049         if (loggedOn) {
9050           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9051           SendToICS(buf1);
9052         }
9053       } else {
9054         DisplayNote(message + 13);
9055       }
9056       return;
9057     }
9058     if (!strncmp(message, "tellothers ", 11)) {
9059       if (appData.icsActive) {
9060         if (loggedOn) {
9061           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9062           SendToICS(buf1);
9063         }
9064       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9065       return;
9066     }
9067     if (!strncmp(message, "tellall ", 8)) {
9068       if (appData.icsActive) {
9069         if (loggedOn) {
9070           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9071           SendToICS(buf1);
9072         }
9073       } else {
9074         DisplayNote(message + 8);
9075       }
9076       return;
9077     }
9078     if (strncmp(message, "warning", 7) == 0) {
9079         /* Undocumented feature, use tellusererror in new code */
9080         DisplayError(message, 0);
9081         return;
9082     }
9083     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9084         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9085         strcat(realname, " query");
9086         AskQuestion(realname, buf2, buf1, cps->pr);
9087         return;
9088     }
9089     /* Commands from the engine directly to ICS.  We don't allow these to be
9090      *  sent until we are logged on. Crafty kibitzes have been known to
9091      *  interfere with the login process.
9092      */
9093     if (loggedOn) {
9094         if (!strncmp(message, "tellics ", 8)) {
9095             SendToICS(message + 8);
9096             SendToICS("\n");
9097             return;
9098         }
9099         if (!strncmp(message, "tellicsnoalias ", 15)) {
9100             SendToICS(ics_prefix);
9101             SendToICS(message + 15);
9102             SendToICS("\n");
9103             return;
9104         }
9105         /* The following are for backward compatibility only */
9106         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9107             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9108             SendToICS(ics_prefix);
9109             SendToICS(message);
9110             SendToICS("\n");
9111             return;
9112         }
9113     }
9114     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9115         if(initPing == cps->lastPong) {
9116             if(gameInfo.variant == VariantUnknown) {
9117                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9118                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9119                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9120             }
9121             initPing = -1;
9122         }
9123         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9124             abortEngineThink = FALSE;
9125             DisplayMessage("", "");
9126             ThawUI();
9127         }
9128         return;
9129     }
9130     if(!strncmp(message, "highlight ", 10)) {
9131         if(appData.testLegality && !*engineVariant && appData.markers) return;
9132         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9133         return;
9134     }
9135     if(!strncmp(message, "click ", 6)) {
9136         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9137         if(appData.testLegality || !appData.oneClick) return;
9138         sscanf(message+6, "%c%d%c", &f, &y, &c);
9139         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9140         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9141         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9142         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9143         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9144         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9145             LeftClick(Release, lastLeftX, lastLeftY);
9146         controlKey  = (c == ',');
9147         LeftClick(Press, x, y);
9148         LeftClick(Release, x, y);
9149         first.highlight = f;
9150         return;
9151     }
9152     /*
9153      * If the move is illegal, cancel it and redraw the board.
9154      * Also deal with other error cases.  Matching is rather loose
9155      * here to accommodate engines written before the spec.
9156      */
9157     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9158         strncmp(message, "Error", 5) == 0) {
9159         if (StrStr(message, "name") ||
9160             StrStr(message, "rating") || StrStr(message, "?") ||
9161             StrStr(message, "result") || StrStr(message, "board") ||
9162             StrStr(message, "bk") || StrStr(message, "computer") ||
9163             StrStr(message, "variant") || StrStr(message, "hint") ||
9164             StrStr(message, "random") || StrStr(message, "depth") ||
9165             StrStr(message, "accepted")) {
9166             return;
9167         }
9168         if (StrStr(message, "protover")) {
9169           /* Program is responding to input, so it's apparently done
9170              initializing, and this error message indicates it is
9171              protocol version 1.  So we don't need to wait any longer
9172              for it to initialize and send feature commands. */
9173           FeatureDone(cps, 1);
9174           cps->protocolVersion = 1;
9175           return;
9176         }
9177         cps->maybeThinking = FALSE;
9178
9179         if (StrStr(message, "draw")) {
9180             /* Program doesn't have "draw" command */
9181             cps->sendDrawOffers = 0;
9182             return;
9183         }
9184         if (cps->sendTime != 1 &&
9185             (StrStr(message, "time") || StrStr(message, "otim"))) {
9186           /* Program apparently doesn't have "time" or "otim" command */
9187           cps->sendTime = 0;
9188           return;
9189         }
9190         if (StrStr(message, "analyze")) {
9191             cps->analysisSupport = FALSE;
9192             cps->analyzing = FALSE;
9193 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9194             EditGameEvent(); // [HGM] try to preserve loaded game
9195             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9196             DisplayError(buf2, 0);
9197             return;
9198         }
9199         if (StrStr(message, "(no matching move)st")) {
9200           /* Special kludge for GNU Chess 4 only */
9201           cps->stKludge = TRUE;
9202           SendTimeControl(cps, movesPerSession, timeControl,
9203                           timeIncrement, appData.searchDepth,
9204                           searchTime);
9205           return;
9206         }
9207         if (StrStr(message, "(no matching move)sd")) {
9208           /* Special kludge for GNU Chess 4 only */
9209           cps->sdKludge = TRUE;
9210           SendTimeControl(cps, movesPerSession, timeControl,
9211                           timeIncrement, appData.searchDepth,
9212                           searchTime);
9213           return;
9214         }
9215         if (!StrStr(message, "llegal")) {
9216             return;
9217         }
9218         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9219             gameMode == IcsIdle) return;
9220         if (forwardMostMove <= backwardMostMove) return;
9221         if (pausing) PauseEvent();
9222       if(appData.forceIllegal) {
9223             // [HGM] illegal: machine refused move; force position after move into it
9224           SendToProgram("force\n", cps);
9225           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9226                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9227                 // when black is to move, while there might be nothing on a2 or black
9228                 // might already have the move. So send the board as if white has the move.
9229                 // But first we must change the stm of the engine, as it refused the last move
9230                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9231                 if(WhiteOnMove(forwardMostMove)) {
9232                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9233                     SendBoard(cps, forwardMostMove); // kludgeless board
9234                 } else {
9235                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9236                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9237                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9238                 }
9239           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9240             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9241                  gameMode == TwoMachinesPlay)
9242               SendToProgram("go\n", cps);
9243             return;
9244       } else
9245         if (gameMode == PlayFromGameFile) {
9246             /* Stop reading this game file */
9247             gameMode = EditGame;
9248             ModeHighlight();
9249         }
9250         /* [HGM] illegal-move claim should forfeit game when Xboard */
9251         /* only passes fully legal moves                            */
9252         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9253             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9254                                 "False illegal-move claim", GE_XBOARD );
9255             return; // do not take back move we tested as valid
9256         }
9257         currentMove = forwardMostMove-1;
9258         DisplayMove(currentMove-1); /* before DisplayMoveError */
9259         SwitchClocks(forwardMostMove-1); // [HGM] race
9260         DisplayBothClocks();
9261         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9262                 parseList[currentMove], _(cps->which));
9263         DisplayMoveError(buf1);
9264         DrawPosition(FALSE, boards[currentMove]);
9265
9266         SetUserThinkingEnables();
9267         return;
9268     }
9269     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9270         /* Program has a broken "time" command that
9271            outputs a string not ending in newline.
9272            Don't use it. */
9273         cps->sendTime = 0;
9274     }
9275     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9276         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9277             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9278     }
9279
9280     /*
9281      * If chess program startup fails, exit with an error message.
9282      * Attempts to recover here are futile. [HGM] Well, we try anyway
9283      */
9284     if ((StrStr(message, "unknown host") != NULL)
9285         || (StrStr(message, "No remote directory") != NULL)
9286         || (StrStr(message, "not found") != NULL)
9287         || (StrStr(message, "No such file") != NULL)
9288         || (StrStr(message, "can't alloc") != NULL)
9289         || (StrStr(message, "Permission denied") != NULL)) {
9290
9291         cps->maybeThinking = FALSE;
9292         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9293                 _(cps->which), cps->program, cps->host, message);
9294         RemoveInputSource(cps->isr);
9295         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9296             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9297             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9298         }
9299         return;
9300     }
9301
9302     /*
9303      * Look for hint output
9304      */
9305     if (sscanf(message, "Hint: %s", buf1) == 1) {
9306         if (cps == &first && hintRequested) {
9307             hintRequested = FALSE;
9308             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9309                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9310                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9311                                     PosFlags(forwardMostMove),
9312                                     fromY, fromX, toY, toX, promoChar, buf1);
9313                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9314                 DisplayInformation(buf2);
9315             } else {
9316                 /* Hint move could not be parsed!? */
9317               snprintf(buf2, sizeof(buf2),
9318                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9319                         buf1, _(cps->which));
9320                 DisplayError(buf2, 0);
9321             }
9322         } else {
9323           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9324         }
9325         return;
9326     }
9327
9328     /*
9329      * Ignore other messages if game is not in progress
9330      */
9331     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9332         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9333
9334     /*
9335      * look for win, lose, draw, or draw offer
9336      */
9337     if (strncmp(message, "1-0", 3) == 0) {
9338         char *p, *q, *r = "";
9339         p = strchr(message, '{');
9340         if (p) {
9341             q = strchr(p, '}');
9342             if (q) {
9343                 *q = NULLCHAR;
9344                 r = p + 1;
9345             }
9346         }
9347         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9348         return;
9349     } else if (strncmp(message, "0-1", 3) == 0) {
9350         char *p, *q, *r = "";
9351         p = strchr(message, '{');
9352         if (p) {
9353             q = strchr(p, '}');
9354             if (q) {
9355                 *q = NULLCHAR;
9356                 r = p + 1;
9357             }
9358         }
9359         /* Kludge for Arasan 4.1 bug */
9360         if (strcmp(r, "Black resigns") == 0) {
9361             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9362             return;
9363         }
9364         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9365         return;
9366     } else if (strncmp(message, "1/2", 3) == 0) {
9367         char *p, *q, *r = "";
9368         p = strchr(message, '{');
9369         if (p) {
9370             q = strchr(p, '}');
9371             if (q) {
9372                 *q = NULLCHAR;
9373                 r = p + 1;
9374             }
9375         }
9376
9377         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9378         return;
9379
9380     } else if (strncmp(message, "White resign", 12) == 0) {
9381         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9382         return;
9383     } else if (strncmp(message, "Black resign", 12) == 0) {
9384         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9385         return;
9386     } else if (strncmp(message, "White matches", 13) == 0 ||
9387                strncmp(message, "Black matches", 13) == 0   ) {
9388         /* [HGM] ignore GNUShogi noises */
9389         return;
9390     } else if (strncmp(message, "White", 5) == 0 &&
9391                message[5] != '(' &&
9392                StrStr(message, "Black") == NULL) {
9393         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9394         return;
9395     } else if (strncmp(message, "Black", 5) == 0 &&
9396                message[5] != '(') {
9397         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9398         return;
9399     } else if (strcmp(message, "resign") == 0 ||
9400                strcmp(message, "computer resigns") == 0) {
9401         switch (gameMode) {
9402           case MachinePlaysBlack:
9403           case IcsPlayingBlack:
9404             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9405             break;
9406           case MachinePlaysWhite:
9407           case IcsPlayingWhite:
9408             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9409             break;
9410           case TwoMachinesPlay:
9411             if (cps->twoMachinesColor[0] == 'w')
9412               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9413             else
9414               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9415             break;
9416           default:
9417             /* can't happen */
9418             break;
9419         }
9420         return;
9421     } else if (strncmp(message, "opponent mates", 14) == 0) {
9422         switch (gameMode) {
9423           case MachinePlaysBlack:
9424           case IcsPlayingBlack:
9425             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9426             break;
9427           case MachinePlaysWhite:
9428           case IcsPlayingWhite:
9429             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9430             break;
9431           case TwoMachinesPlay:
9432             if (cps->twoMachinesColor[0] == 'w')
9433               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9434             else
9435               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9436             break;
9437           default:
9438             /* can't happen */
9439             break;
9440         }
9441         return;
9442     } else if (strncmp(message, "computer mates", 14) == 0) {
9443         switch (gameMode) {
9444           case MachinePlaysBlack:
9445           case IcsPlayingBlack:
9446             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9447             break;
9448           case MachinePlaysWhite:
9449           case IcsPlayingWhite:
9450             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9451             break;
9452           case TwoMachinesPlay:
9453             if (cps->twoMachinesColor[0] == 'w')
9454               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9455             else
9456               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9457             break;
9458           default:
9459             /* can't happen */
9460             break;
9461         }
9462         return;
9463     } else if (strncmp(message, "checkmate", 9) == 0) {
9464         if (WhiteOnMove(forwardMostMove)) {
9465             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9466         } else {
9467             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9468         }
9469         return;
9470     } else if (strstr(message, "Draw") != NULL ||
9471                strstr(message, "game is a draw") != NULL) {
9472         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9473         return;
9474     } else if (strstr(message, "offer") != NULL &&
9475                strstr(message, "draw") != NULL) {
9476 #if ZIPPY
9477         if (appData.zippyPlay && first.initDone) {
9478             /* Relay offer to ICS */
9479             SendToICS(ics_prefix);
9480             SendToICS("draw\n");
9481         }
9482 #endif
9483         cps->offeredDraw = 2; /* valid until this engine moves twice */
9484         if (gameMode == TwoMachinesPlay) {
9485             if (cps->other->offeredDraw) {
9486                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9487             /* [HGM] in two-machine mode we delay relaying draw offer      */
9488             /* until after we also have move, to see if it is really claim */
9489             }
9490         } else if (gameMode == MachinePlaysWhite ||
9491                    gameMode == MachinePlaysBlack) {
9492           if (userOfferedDraw) {
9493             DisplayInformation(_("Machine accepts your draw offer"));
9494             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9495           } else {
9496             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9497           }
9498         }
9499     }
9500
9501
9502     /*
9503      * Look for thinking output
9504      */
9505     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9506           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9507                                 ) {
9508         int plylev, mvleft, mvtot, curscore, time;
9509         char mvname[MOVE_LEN];
9510         u64 nodes; // [DM]
9511         char plyext;
9512         int ignore = FALSE;
9513         int prefixHint = FALSE;
9514         mvname[0] = NULLCHAR;
9515
9516         switch (gameMode) {
9517           case MachinePlaysBlack:
9518           case IcsPlayingBlack:
9519             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9520             break;
9521           case MachinePlaysWhite:
9522           case IcsPlayingWhite:
9523             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9524             break;
9525           case AnalyzeMode:
9526           case AnalyzeFile:
9527             break;
9528           case IcsObserving: /* [DM] icsEngineAnalyze */
9529             if (!appData.icsEngineAnalyze) ignore = TRUE;
9530             break;
9531           case TwoMachinesPlay:
9532             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9533                 ignore = TRUE;
9534             }
9535             break;
9536           default:
9537             ignore = TRUE;
9538             break;
9539         }
9540
9541         if (!ignore) {
9542             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9543             buf1[0] = NULLCHAR;
9544             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9545                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9546                 char score_buf[MSG_SIZ];
9547
9548                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9549                     nodes += u64Const(0x100000000);
9550
9551                 if (plyext != ' ' && plyext != '\t') {
9552                     time *= 100;
9553                 }
9554
9555                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9556                 if( cps->scoreIsAbsolute &&
9557                     ( gameMode == MachinePlaysBlack ||
9558                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9559                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9560                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9561                      !WhiteOnMove(currentMove)
9562                     ) )
9563                 {
9564                     curscore = -curscore;
9565                 }
9566
9567                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9568
9569                 if(*bestMove) { // rememer time best EPD move was first found
9570                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9571                     ChessMove mt;
9572                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9573                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9574                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9575                 }
9576
9577                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9578                         char buf[MSG_SIZ];
9579                         FILE *f;
9580                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9581                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9582                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9583                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9584                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9585                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9586                                 fclose(f);
9587                         }
9588                         else
9589                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9590                           DisplayError(_("failed writing PV"), 0);
9591                 }
9592
9593                 tempStats.depth = plylev;
9594                 tempStats.nodes = nodes;
9595                 tempStats.time = time;
9596                 tempStats.score = curscore;
9597                 tempStats.got_only_move = 0;
9598
9599                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9600                         int ticklen;
9601
9602                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9603                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9604                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9605                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9606                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9607                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9608                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9609                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9610                 }
9611
9612                 /* Buffer overflow protection */
9613                 if (pv[0] != NULLCHAR) {
9614                     if (strlen(pv) >= sizeof(tempStats.movelist)
9615                         && appData.debugMode) {
9616                         fprintf(debugFP,
9617                                 "PV is too long; using the first %u bytes.\n",
9618                                 (unsigned) sizeof(tempStats.movelist) - 1);
9619                     }
9620
9621                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9622                 } else {
9623                     sprintf(tempStats.movelist, " no PV\n");
9624                 }
9625
9626                 if (tempStats.seen_stat) {
9627                     tempStats.ok_to_send = 1;
9628                 }
9629
9630                 if (strchr(tempStats.movelist, '(') != NULL) {
9631                     tempStats.line_is_book = 1;
9632                     tempStats.nr_moves = 0;
9633                     tempStats.moves_left = 0;
9634                 } else {
9635                     tempStats.line_is_book = 0;
9636                 }
9637
9638                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9639                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9640
9641                 SendProgramStatsToFrontend( cps, &tempStats );
9642
9643                 /*
9644                     [AS] Protect the thinkOutput buffer from overflow... this
9645                     is only useful if buf1 hasn't overflowed first!
9646                 */
9647                 if(curscore >= MATE_SCORE) 
9648                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9649                 else if(curscore <= -MATE_SCORE) 
9650                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9651                 else
9652                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9653                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9654                          plylev,
9655                          (gameMode == TwoMachinesPlay ?
9656                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9657                          score_buf,
9658                          prefixHint ? lastHint : "",
9659                          prefixHint ? " " : "" );
9660
9661                 if( buf1[0] != NULLCHAR ) {
9662                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9663
9664                     if( strlen(pv) > max_len ) {
9665                         if( appData.debugMode) {
9666                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9667                         }
9668                         pv[max_len+1] = '\0';
9669                     }
9670
9671                     strcat( thinkOutput, pv);
9672                 }
9673
9674                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9675                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9676                     DisplayMove(currentMove - 1);
9677                 }
9678                 return;
9679
9680             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9681                 /* crafty (9.25+) says "(only move) <move>"
9682                  * if there is only 1 legal move
9683                  */
9684                 sscanf(p, "(only move) %s", buf1);
9685                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9686                 sprintf(programStats.movelist, "%s (only move)", buf1);
9687                 programStats.depth = 1;
9688                 programStats.nr_moves = 1;
9689                 programStats.moves_left = 1;
9690                 programStats.nodes = 1;
9691                 programStats.time = 1;
9692                 programStats.got_only_move = 1;
9693
9694                 /* Not really, but we also use this member to
9695                    mean "line isn't going to change" (Crafty
9696                    isn't searching, so stats won't change) */
9697                 programStats.line_is_book = 1;
9698
9699                 SendProgramStatsToFrontend( cps, &programStats );
9700
9701                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9702                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9703                     DisplayMove(currentMove - 1);
9704                 }
9705                 return;
9706             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9707                               &time, &nodes, &plylev, &mvleft,
9708                               &mvtot, mvname) >= 5) {
9709                 /* The stat01: line is from Crafty (9.29+) in response
9710                    to the "." command */
9711                 programStats.seen_stat = 1;
9712                 cps->maybeThinking = TRUE;
9713
9714                 if (programStats.got_only_move || !appData.periodicUpdates)
9715                   return;
9716
9717                 programStats.depth = plylev;
9718                 programStats.time = time;
9719                 programStats.nodes = nodes;
9720                 programStats.moves_left = mvleft;
9721                 programStats.nr_moves = mvtot;
9722                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9723                 programStats.ok_to_send = 1;
9724                 programStats.movelist[0] = '\0';
9725
9726                 SendProgramStatsToFrontend( cps, &programStats );
9727
9728                 return;
9729
9730             } else if (strncmp(message,"++",2) == 0) {
9731                 /* Crafty 9.29+ outputs this */
9732                 programStats.got_fail = 2;
9733                 return;
9734
9735             } else if (strncmp(message,"--",2) == 0) {
9736                 /* Crafty 9.29+ outputs this */
9737                 programStats.got_fail = 1;
9738                 return;
9739
9740             } else if (thinkOutput[0] != NULLCHAR &&
9741                        strncmp(message, "    ", 4) == 0) {
9742                 unsigned message_len;
9743
9744                 p = message;
9745                 while (*p && *p == ' ') p++;
9746
9747                 message_len = strlen( p );
9748
9749                 /* [AS] Avoid buffer overflow */
9750                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9751                     strcat(thinkOutput, " ");
9752                     strcat(thinkOutput, p);
9753                 }
9754
9755                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9756                     strcat(programStats.movelist, " ");
9757                     strcat(programStats.movelist, p);
9758                 }
9759
9760                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9761                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9762                     DisplayMove(currentMove - 1);
9763                 }
9764                 return;
9765             }
9766         }
9767         else {
9768             buf1[0] = NULLCHAR;
9769
9770             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9771                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9772             {
9773                 ChessProgramStats cpstats;
9774
9775                 if (plyext != ' ' && plyext != '\t') {
9776                     time *= 100;
9777                 }
9778
9779                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9780                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9781                     curscore = -curscore;
9782                 }
9783
9784                 cpstats.depth = plylev;
9785                 cpstats.nodes = nodes;
9786                 cpstats.time = time;
9787                 cpstats.score = curscore;
9788                 cpstats.got_only_move = 0;
9789                 cpstats.movelist[0] = '\0';
9790
9791                 if (buf1[0] != NULLCHAR) {
9792                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9793                 }
9794
9795                 cpstats.ok_to_send = 0;
9796                 cpstats.line_is_book = 0;
9797                 cpstats.nr_moves = 0;
9798                 cpstats.moves_left = 0;
9799
9800                 SendProgramStatsToFrontend( cps, &cpstats );
9801             }
9802         }
9803     }
9804 }
9805
9806
9807 /* Parse a game score from the character string "game", and
9808    record it as the history of the current game.  The game
9809    score is NOT assumed to start from the standard position.
9810    The display is not updated in any way.
9811    */
9812 void
9813 ParseGameHistory (char *game)
9814 {
9815     ChessMove moveType;
9816     int fromX, fromY, toX, toY, boardIndex;
9817     char promoChar;
9818     char *p, *q;
9819     char buf[MSG_SIZ];
9820
9821     if (appData.debugMode)
9822       fprintf(debugFP, "Parsing game history: %s\n", game);
9823
9824     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9825     gameInfo.site = StrSave(appData.icsHost);
9826     gameInfo.date = PGNDate();
9827     gameInfo.round = StrSave("-");
9828
9829     /* Parse out names of players */
9830     while (*game == ' ') game++;
9831     p = buf;
9832     while (*game != ' ') *p++ = *game++;
9833     *p = NULLCHAR;
9834     gameInfo.white = StrSave(buf);
9835     while (*game == ' ') game++;
9836     p = buf;
9837     while (*game != ' ' && *game != '\n') *p++ = *game++;
9838     *p = NULLCHAR;
9839     gameInfo.black = StrSave(buf);
9840
9841     /* Parse moves */
9842     boardIndex = blackPlaysFirst ? 1 : 0;
9843     yynewstr(game);
9844     for (;;) {
9845         yyboardindex = boardIndex;
9846         moveType = (ChessMove) Myylex();
9847         switch (moveType) {
9848           case IllegalMove:             /* maybe suicide chess, etc. */
9849   if (appData.debugMode) {
9850     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9851     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9852     setbuf(debugFP, NULL);
9853   }
9854           case WhitePromotion:
9855           case BlackPromotion:
9856           case WhiteNonPromotion:
9857           case BlackNonPromotion:
9858           case NormalMove:
9859           case FirstLeg:
9860           case WhiteCapturesEnPassant:
9861           case BlackCapturesEnPassant:
9862           case WhiteKingSideCastle:
9863           case WhiteQueenSideCastle:
9864           case BlackKingSideCastle:
9865           case BlackQueenSideCastle:
9866           case WhiteKingSideCastleWild:
9867           case WhiteQueenSideCastleWild:
9868           case BlackKingSideCastleWild:
9869           case BlackQueenSideCastleWild:
9870           /* PUSH Fabien */
9871           case WhiteHSideCastleFR:
9872           case WhiteASideCastleFR:
9873           case BlackHSideCastleFR:
9874           case BlackASideCastleFR:
9875           /* POP Fabien */
9876             fromX = currentMoveString[0] - AAA;
9877             fromY = currentMoveString[1] - ONE;
9878             toX = currentMoveString[2] - AAA;
9879             toY = currentMoveString[3] - ONE;
9880             promoChar = currentMoveString[4];
9881             break;
9882           case WhiteDrop:
9883           case BlackDrop:
9884             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9885             fromX = moveType == WhiteDrop ?
9886               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9887             (int) CharToPiece(ToLower(currentMoveString[0]));
9888             fromY = DROP_RANK;
9889             toX = currentMoveString[2] - AAA;
9890             toY = currentMoveString[3] - ONE;
9891             promoChar = NULLCHAR;
9892             break;
9893           case AmbiguousMove:
9894             /* bug? */
9895             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9896   if (appData.debugMode) {
9897     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9898     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9899     setbuf(debugFP, NULL);
9900   }
9901             DisplayError(buf, 0);
9902             return;
9903           case ImpossibleMove:
9904             /* bug? */
9905             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9906   if (appData.debugMode) {
9907     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9908     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9909     setbuf(debugFP, NULL);
9910   }
9911             DisplayError(buf, 0);
9912             return;
9913           case EndOfFile:
9914             if (boardIndex < backwardMostMove) {
9915                 /* Oops, gap.  How did that happen? */
9916                 DisplayError(_("Gap in move list"), 0);
9917                 return;
9918             }
9919             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9920             if (boardIndex > forwardMostMove) {
9921                 forwardMostMove = boardIndex;
9922             }
9923             return;
9924           case ElapsedTime:
9925             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9926                 strcat(parseList[boardIndex-1], " ");
9927                 strcat(parseList[boardIndex-1], yy_text);
9928             }
9929             continue;
9930           case Comment:
9931           case PGNTag:
9932           case NAG:
9933           default:
9934             /* ignore */
9935             continue;
9936           case WhiteWins:
9937           case BlackWins:
9938           case GameIsDrawn:
9939           case GameUnfinished:
9940             if (gameMode == IcsExamining) {
9941                 if (boardIndex < backwardMostMove) {
9942                     /* Oops, gap.  How did that happen? */
9943                     return;
9944                 }
9945                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9946                 return;
9947             }
9948             gameInfo.result = moveType;
9949             p = strchr(yy_text, '{');
9950             if (p == NULL) p = strchr(yy_text, '(');
9951             if (p == NULL) {
9952                 p = yy_text;
9953                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9954             } else {
9955                 q = strchr(p, *p == '{' ? '}' : ')');
9956                 if (q != NULL) *q = NULLCHAR;
9957                 p++;
9958             }
9959             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9960             gameInfo.resultDetails = StrSave(p);
9961             continue;
9962         }
9963         if (boardIndex >= forwardMostMove &&
9964             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9965             backwardMostMove = blackPlaysFirst ? 1 : 0;
9966             return;
9967         }
9968         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9969                                  fromY, fromX, toY, toX, promoChar,
9970                                  parseList[boardIndex]);
9971         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9972         /* currentMoveString is set as a side-effect of yylex */
9973         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9974         strcat(moveList[boardIndex], "\n");
9975         boardIndex++;
9976         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9977         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9978           case MT_NONE:
9979           case MT_STALEMATE:
9980           default:
9981             break;
9982           case MT_CHECK:
9983             if(!IS_SHOGI(gameInfo.variant))
9984                 strcat(parseList[boardIndex - 1], "+");
9985             break;
9986           case MT_CHECKMATE:
9987           case MT_STAINMATE:
9988             strcat(parseList[boardIndex - 1], "#");
9989             break;
9990         }
9991     }
9992 }
9993
9994
9995 /* Apply a move to the given board  */
9996 void
9997 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9998 {
9999   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10000   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10001
10002     /* [HGM] compute & store e.p. status and castling rights for new position */
10003     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10004
10005       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10006       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10007       board[EP_STATUS] = EP_NONE;
10008       board[EP_FILE] = board[EP_RANK] = 100;
10009
10010   if (fromY == DROP_RANK) {
10011         /* must be first */
10012         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10013             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10014             return;
10015         }
10016         piece = board[toY][toX] = (ChessSquare) fromX;
10017   } else {
10018 //      ChessSquare victim;
10019       int i;
10020
10021       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10022 //           victim = board[killY][killX],
10023            killed = board[killY][killX],
10024            board[killY][killX] = EmptySquare,
10025            board[EP_STATUS] = EP_CAPTURE;
10026            if( kill2X >= 0 && kill2Y >= 0)
10027              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10028       }
10029
10030       if( board[toY][toX] != EmptySquare ) {
10031            board[EP_STATUS] = EP_CAPTURE;
10032            if( (fromX != toX || fromY != toY) && // not igui!
10033                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10034                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10035                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10036            }
10037       }
10038
10039       pawn = board[fromY][fromX];
10040       if( pawn == WhiteLance || pawn == BlackLance ) {
10041            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10042                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10043                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10044            }
10045       }
10046       if( pawn == WhitePawn ) {
10047            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10048                board[EP_STATUS] = EP_PAWN_MOVE;
10049            if( toY-fromY>=2) {
10050                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10051                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10052                         gameInfo.variant != VariantBerolina || toX < fromX)
10053                       board[EP_STATUS] = toX | berolina;
10054                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10055                         gameInfo.variant != VariantBerolina || toX > fromX)
10056                       board[EP_STATUS] = toX;
10057            }
10058       } else
10059       if( pawn == BlackPawn ) {
10060            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10061                board[EP_STATUS] = EP_PAWN_MOVE;
10062            if( toY-fromY<= -2) {
10063                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10064                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10065                         gameInfo.variant != VariantBerolina || toX < fromX)
10066                       board[EP_STATUS] = toX | berolina;
10067                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10068                         gameInfo.variant != VariantBerolina || toX > fromX)
10069                       board[EP_STATUS] = toX;
10070            }
10071        }
10072
10073        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10074        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10075        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10076        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10077
10078        for(i=0; i<nrCastlingRights; i++) {
10079            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10080               board[CASTLING][i] == toX   && castlingRank[i] == toY
10081              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10082        }
10083
10084        if(gameInfo.variant == VariantSChess) { // update virginity
10085            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10086            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10087            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10088            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10089        }
10090
10091      if (fromX == toX && fromY == toY) return;
10092
10093      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10094      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10095      if(gameInfo.variant == VariantKnightmate)
10096          king += (int) WhiteUnicorn - (int) WhiteKing;
10097
10098     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10099        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10100         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10101         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10102         board[EP_STATUS] = EP_NONE; // capture was fake!
10103     } else
10104     /* Code added by Tord: */
10105     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10106     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10107         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10108       board[EP_STATUS] = EP_NONE; // capture was fake!
10109       board[fromY][fromX] = EmptySquare;
10110       board[toY][toX] = EmptySquare;
10111       if((toX > fromX) != (piece == WhiteRook)) {
10112         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10113       } else {
10114         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10115       }
10116     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10117                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10118       board[EP_STATUS] = EP_NONE;
10119       board[fromY][fromX] = EmptySquare;
10120       board[toY][toX] = EmptySquare;
10121       if((toX > fromX) != (piece == BlackRook)) {
10122         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10123       } else {
10124         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10125       }
10126     /* End of code added by Tord */
10127
10128     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10129         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10130         board[toY][toX] = piece;
10131     } else if (board[fromY][fromX] == king
10132         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10133         && toY == fromY && toX > fromX+1) {
10134         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10135         board[fromY][toX-1] = board[fromY][rookX];
10136         board[fromY][rookX] = EmptySquare;
10137         board[fromY][fromX] = EmptySquare;
10138         board[toY][toX] = king;
10139     } else if (board[fromY][fromX] == king
10140         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10141                && toY == fromY && toX < fromX-1) {
10142         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10143         board[fromY][toX+1] = board[fromY][rookX];
10144         board[fromY][rookX] = EmptySquare;
10145         board[fromY][fromX] = EmptySquare;
10146         board[toY][toX] = king;
10147     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10148                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10149                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10150                ) {
10151         /* white pawn promotion */
10152         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10153         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10155         board[fromY][fromX] = EmptySquare;
10156     } else if ((fromY >= BOARD_HEIGHT>>1)
10157                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10158                && (toX != fromX)
10159                && gameInfo.variant != VariantXiangqi
10160                && gameInfo.variant != VariantBerolina
10161                && (pawn == WhitePawn)
10162                && (board[toY][toX] == EmptySquare)) {
10163         board[fromY][fromX] = EmptySquare;
10164         board[toY][toX] = piece;
10165         if(toY == epRank - 128 + 1)
10166             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10167         else
10168             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10169     } else if ((fromY == BOARD_HEIGHT-4)
10170                && (toX == fromX)
10171                && gameInfo.variant == VariantBerolina
10172                && (board[fromY][fromX] == WhitePawn)
10173                && (board[toY][toX] == EmptySquare)) {
10174         board[fromY][fromX] = EmptySquare;
10175         board[toY][toX] = WhitePawn;
10176         if(oldEP & EP_BEROLIN_A) {
10177                 captured = board[fromY][fromX-1];
10178                 board[fromY][fromX-1] = EmptySquare;
10179         }else{  captured = board[fromY][fromX+1];
10180                 board[fromY][fromX+1] = EmptySquare;
10181         }
10182     } else if (board[fromY][fromX] == king
10183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10184                && toY == fromY && toX > fromX+1) {
10185         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10186         board[fromY][toX-1] = board[fromY][rookX];
10187         board[fromY][rookX] = EmptySquare;
10188         board[fromY][fromX] = EmptySquare;
10189         board[toY][toX] = king;
10190     } else if (board[fromY][fromX] == king
10191         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10192                && toY == fromY && toX < fromX-1) {
10193         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10194         board[fromY][toX+1] = board[fromY][rookX];
10195         board[fromY][rookX] = EmptySquare;
10196         board[fromY][fromX] = EmptySquare;
10197         board[toY][toX] = king;
10198     } else if (fromY == 7 && fromX == 3
10199                && board[fromY][fromX] == BlackKing
10200                && toY == 7 && toX == 5) {
10201         board[fromY][fromX] = EmptySquare;
10202         board[toY][toX] = BlackKing;
10203         board[fromY][7] = EmptySquare;
10204         board[toY][4] = BlackRook;
10205     } else if (fromY == 7 && fromX == 3
10206                && board[fromY][fromX] == BlackKing
10207                && toY == 7 && toX == 1) {
10208         board[fromY][fromX] = EmptySquare;
10209         board[toY][toX] = BlackKing;
10210         board[fromY][0] = EmptySquare;
10211         board[toY][2] = BlackRook;
10212     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10213                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10214                && toY < promoRank && promoChar
10215                ) {
10216         /* black pawn promotion */
10217         board[toY][toX] = CharToPiece(ToLower(promoChar));
10218         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10219             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10220         board[fromY][fromX] = EmptySquare;
10221     } else if ((fromY < BOARD_HEIGHT>>1)
10222                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10223                && (toX != fromX)
10224                && gameInfo.variant != VariantXiangqi
10225                && gameInfo.variant != VariantBerolina
10226                && (pawn == BlackPawn)
10227                && (board[toY][toX] == EmptySquare)) {
10228         board[fromY][fromX] = EmptySquare;
10229         board[toY][toX] = piece;
10230         if(toY == epRank - 128 - 1)
10231             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10232         else
10233             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10234     } else if ((fromY == 3)
10235                && (toX == fromX)
10236                && gameInfo.variant == VariantBerolina
10237                && (board[fromY][fromX] == BlackPawn)
10238                && (board[toY][toX] == EmptySquare)) {
10239         board[fromY][fromX] = EmptySquare;
10240         board[toY][toX] = BlackPawn;
10241         if(oldEP & EP_BEROLIN_A) {
10242                 captured = board[fromY][fromX-1];
10243                 board[fromY][fromX-1] = EmptySquare;
10244         }else{  captured = board[fromY][fromX+1];
10245                 board[fromY][fromX+1] = EmptySquare;
10246         }
10247     } else {
10248         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10249         board[fromY][fromX] = EmptySquare;
10250         board[toY][toX] = piece;
10251     }
10252   }
10253
10254     if (gameInfo.holdingsWidth != 0) {
10255
10256       /* !!A lot more code needs to be written to support holdings  */
10257       /* [HGM] OK, so I have written it. Holdings are stored in the */
10258       /* penultimate board files, so they are automaticlly stored   */
10259       /* in the game history.                                       */
10260       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10261                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10262         /* Delete from holdings, by decreasing count */
10263         /* and erasing image if necessary            */
10264         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10265         if(p < (int) BlackPawn) { /* white drop */
10266              p -= (int)WhitePawn;
10267                  p = PieceToNumber((ChessSquare)p);
10268              if(p >= gameInfo.holdingsSize) p = 0;
10269              if(--board[p][BOARD_WIDTH-2] <= 0)
10270                   board[p][BOARD_WIDTH-1] = EmptySquare;
10271              if((int)board[p][BOARD_WIDTH-2] < 0)
10272                         board[p][BOARD_WIDTH-2] = 0;
10273         } else {                  /* black drop */
10274              p -= (int)BlackPawn;
10275                  p = PieceToNumber((ChessSquare)p);
10276              if(p >= gameInfo.holdingsSize) p = 0;
10277              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10278                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10279              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10280                         board[BOARD_HEIGHT-1-p][1] = 0;
10281         }
10282       }
10283       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10284           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10285         /* [HGM] holdings: Add to holdings, if holdings exist */
10286         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10287                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10288                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10289         }
10290         p = (int) captured;
10291         if (p >= (int) BlackPawn) {
10292           p -= (int)BlackPawn;
10293           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10294                   /* Restore shogi-promoted piece to its original  first */
10295                   captured = (ChessSquare) (DEMOTED captured);
10296                   p = DEMOTED p;
10297           }
10298           p = PieceToNumber((ChessSquare)p);
10299           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10300           board[p][BOARD_WIDTH-2]++;
10301           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10302         } else {
10303           p -= (int)WhitePawn;
10304           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10305                   captured = (ChessSquare) (DEMOTED captured);
10306                   p = DEMOTED p;
10307           }
10308           p = PieceToNumber((ChessSquare)p);
10309           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10310           board[BOARD_HEIGHT-1-p][1]++;
10311           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10312         }
10313       }
10314     } else if (gameInfo.variant == VariantAtomic) {
10315       if (captured != EmptySquare) {
10316         int y, x;
10317         for (y = toY-1; y <= toY+1; y++) {
10318           for (x = toX-1; x <= toX+1; x++) {
10319             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10320                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10321               board[y][x] = EmptySquare;
10322             }
10323           }
10324         }
10325         board[toY][toX] = EmptySquare;
10326       }
10327     }
10328
10329     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10330         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10331     } else
10332     if(promoChar == '+') {
10333         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10334         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10335         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10336           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10337     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10338         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10339         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10340            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10341         board[toY][toX] = newPiece;
10342     }
10343     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10344                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10345         // [HGM] superchess: take promotion piece out of holdings
10346         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10347         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10348             if(!--board[k][BOARD_WIDTH-2])
10349                 board[k][BOARD_WIDTH-1] = EmptySquare;
10350         } else {
10351             if(!--board[BOARD_HEIGHT-1-k][1])
10352                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10353         }
10354     }
10355 }
10356
10357 /* Updates forwardMostMove */
10358 void
10359 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10360 {
10361     int x = toX, y = toY;
10362     char *s = parseList[forwardMostMove];
10363     ChessSquare p = boards[forwardMostMove][toY][toX];
10364 //    forwardMostMove++; // [HGM] bare: moved downstream
10365
10366     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10367     (void) CoordsToAlgebraic(boards[forwardMostMove],
10368                              PosFlags(forwardMostMove),
10369                              fromY, fromX, y, x, promoChar,
10370                              s);
10371     if(killX >= 0 && killY >= 0)
10372         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10373
10374     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10375         int timeLeft; static int lastLoadFlag=0; int king, piece;
10376         piece = boards[forwardMostMove][fromY][fromX];
10377         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10378         if(gameInfo.variant == VariantKnightmate)
10379             king += (int) WhiteUnicorn - (int) WhiteKing;
10380         if(forwardMostMove == 0) {
10381             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10382                 fprintf(serverMoves, "%s;", UserName());
10383             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10384                 fprintf(serverMoves, "%s;", second.tidy);
10385             fprintf(serverMoves, "%s;", first.tidy);
10386             if(gameMode == MachinePlaysWhite)
10387                 fprintf(serverMoves, "%s;", UserName());
10388             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10389                 fprintf(serverMoves, "%s;", second.tidy);
10390         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10391         lastLoadFlag = loadFlag;
10392         // print base move
10393         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10394         // print castling suffix
10395         if( toY == fromY && piece == king ) {
10396             if(toX-fromX > 1)
10397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10398             if(fromX-toX >1)
10399                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10400         }
10401         // e.p. suffix
10402         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10403              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10404              boards[forwardMostMove][toY][toX] == EmptySquare
10405              && fromX != toX && fromY != toY)
10406                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10407         // promotion suffix
10408         if(promoChar != NULLCHAR) {
10409             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10410                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10411                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10412             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10413         }
10414         if(!loadFlag) {
10415                 char buf[MOVE_LEN*2], *p; int len;
10416             fprintf(serverMoves, "/%d/%d",
10417                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10418             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10419             else                      timeLeft = blackTimeRemaining/1000;
10420             fprintf(serverMoves, "/%d", timeLeft);
10421                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10422                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10423                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10424                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10425             fprintf(serverMoves, "/%s", buf);
10426         }
10427         fflush(serverMoves);
10428     }
10429
10430     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10431         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10432       return;
10433     }
10434     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10435     if (commentList[forwardMostMove+1] != NULL) {
10436         free(commentList[forwardMostMove+1]);
10437         commentList[forwardMostMove+1] = NULL;
10438     }
10439     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10441     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10442     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10443     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10444     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10445     adjustedClock = FALSE;
10446     gameInfo.result = GameUnfinished;
10447     if (gameInfo.resultDetails != NULL) {
10448         free(gameInfo.resultDetails);
10449         gameInfo.resultDetails = NULL;
10450     }
10451     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10452                               moveList[forwardMostMove - 1]);
10453     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10454       case MT_NONE:
10455       case MT_STALEMATE:
10456       default:
10457         break;
10458       case MT_CHECK:
10459         if(!IS_SHOGI(gameInfo.variant))
10460             strcat(parseList[forwardMostMove - 1], "+");
10461         break;
10462       case MT_CHECKMATE:
10463       case MT_STAINMATE:
10464         strcat(parseList[forwardMostMove - 1], "#");
10465         break;
10466     }
10467 }
10468
10469 /* Updates currentMove if not pausing */
10470 void
10471 ShowMove (int fromX, int fromY, int toX, int toY)
10472 {
10473     int instant = (gameMode == PlayFromGameFile) ?
10474         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10475     if(appData.noGUI) return;
10476     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10477         if (!instant) {
10478             if (forwardMostMove == currentMove + 1) {
10479                 AnimateMove(boards[forwardMostMove - 1],
10480                             fromX, fromY, toX, toY);
10481             }
10482         }
10483         currentMove = forwardMostMove;
10484     }
10485
10486     killX = killY = -1; // [HGM] lion: used up
10487
10488     if (instant) return;
10489
10490     DisplayMove(currentMove - 1);
10491     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10492             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10493                 SetHighlights(fromX, fromY, toX, toY);
10494             }
10495     }
10496     DrawPosition(FALSE, boards[currentMove]);
10497     DisplayBothClocks();
10498     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10499 }
10500
10501 void
10502 SendEgtPath (ChessProgramState *cps)
10503 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10504         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10505
10506         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10507
10508         while(*p) {
10509             char c, *q = name+1, *r, *s;
10510
10511             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10512             while(*p && *p != ',') *q++ = *p++;
10513             *q++ = ':'; *q = 0;
10514             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10515                 strcmp(name, ",nalimov:") == 0 ) {
10516                 // take nalimov path from the menu-changeable option first, if it is defined
10517               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10518                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10519             } else
10520             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10521                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10522                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10523                 s = r = StrStr(s, ":") + 1; // beginning of path info
10524                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10525                 c = *r; *r = 0;             // temporarily null-terminate path info
10526                     *--q = 0;               // strip of trailig ':' from name
10527                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10528                 *r = c;
10529                 SendToProgram(buf,cps);     // send egtbpath command for this format
10530             }
10531             if(*p == ',') p++; // read away comma to position for next format name
10532         }
10533 }
10534
10535 static int
10536 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10537 {
10538       int width = 8, height = 8, holdings = 0;             // most common sizes
10539       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10540       // correct the deviations default for each variant
10541       if( v == VariantXiangqi ) width = 9,  height = 10;
10542       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10543       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10544       if( v == VariantCapablanca || v == VariantCapaRandom ||
10545           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10546                                 width = 10;
10547       if( v == VariantCourier ) width = 12;
10548       if( v == VariantSuper )                            holdings = 8;
10549       if( v == VariantGreat )   width = 10,              holdings = 8;
10550       if( v == VariantSChess )                           holdings = 7;
10551       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10552       if( v == VariantChuChess) width = 10, height = 10;
10553       if( v == VariantChu )     width = 12, height = 12;
10554       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10555              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10556              holdingsSize >= 0 && holdingsSize != holdings;
10557 }
10558
10559 char variantError[MSG_SIZ];
10560
10561 char *
10562 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10563 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10564       char *p, *variant = VariantName(v);
10565       static char b[MSG_SIZ];
10566       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10567            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10568                                                holdingsSize, variant); // cook up sized variant name
10569            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10570            if(StrStr(list, b) == NULL) {
10571                // specific sized variant not known, check if general sizing allowed
10572                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10573                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10574                             boardWidth, boardHeight, holdingsSize, engine);
10575                    return NULL;
10576                }
10577                /* [HGM] here we really should compare with the maximum supported board size */
10578            }
10579       } else snprintf(b, MSG_SIZ,"%s", variant);
10580       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10581       p = StrStr(list, b);
10582       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10583       if(p == NULL) {
10584           // occurs not at all in list, or only as sub-string
10585           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10586           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10587               int l = strlen(variantError);
10588               char *q;
10589               while(p != list && p[-1] != ',') p--;
10590               q = strchr(p, ',');
10591               if(q) *q = NULLCHAR;
10592               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10593               if(q) *q= ',';
10594           }
10595           return NULL;
10596       }
10597       return b;
10598 }
10599
10600 void
10601 InitChessProgram (ChessProgramState *cps, int setup)
10602 /* setup needed to setup FRC opening position */
10603 {
10604     char buf[MSG_SIZ], *b;
10605     if (appData.noChessProgram) return;
10606     hintRequested = FALSE;
10607     bookRequested = FALSE;
10608
10609     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10610     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10611     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10612     if(cps->memSize) { /* [HGM] memory */
10613       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10614         SendToProgram(buf, cps);
10615     }
10616     SendEgtPath(cps); /* [HGM] EGT */
10617     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10618       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10619         SendToProgram(buf, cps);
10620     }
10621
10622     setboardSpoiledMachineBlack = FALSE;
10623     SendToProgram(cps->initString, cps);
10624     if (gameInfo.variant != VariantNormal &&
10625         gameInfo.variant != VariantLoadable
10626         /* [HGM] also send variant if board size non-standard */
10627         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10628
10629       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10630                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10631       if (b == NULL) {
10632         VariantClass v;
10633         char c, *q = cps->variants, *p = strchr(q, ',');
10634         if(p) *p = NULLCHAR;
10635         v = StringToVariant(q);
10636         DisplayError(variantError, 0);
10637         if(v != VariantUnknown && cps == &first) {
10638             int w, h, s;
10639             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10640                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10641             ASSIGN(appData.variant, q);
10642             Reset(TRUE, FALSE);
10643         }
10644         if(p) *p = ',';
10645         return;
10646       }
10647
10648       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10649       SendToProgram(buf, cps);
10650     }
10651     currentlyInitializedVariant = gameInfo.variant;
10652
10653     /* [HGM] send opening position in FRC to first engine */
10654     if(setup) {
10655           SendToProgram("force\n", cps);
10656           SendBoard(cps, 0);
10657           /* engine is now in force mode! Set flag to wake it up after first move. */
10658           setboardSpoiledMachineBlack = 1;
10659     }
10660
10661     if (cps->sendICS) {
10662       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10663       SendToProgram(buf, cps);
10664     }
10665     cps->maybeThinking = FALSE;
10666     cps->offeredDraw = 0;
10667     if (!appData.icsActive) {
10668         SendTimeControl(cps, movesPerSession, timeControl,
10669                         timeIncrement, appData.searchDepth,
10670                         searchTime);
10671     }
10672     if (appData.showThinking
10673         // [HGM] thinking: four options require thinking output to be sent
10674         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10675                                 ) {
10676         SendToProgram("post\n", cps);
10677     }
10678     SendToProgram("hard\n", cps);
10679     if (!appData.ponderNextMove) {
10680         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10681            it without being sure what state we are in first.  "hard"
10682            is not a toggle, so that one is OK.
10683          */
10684         SendToProgram("easy\n", cps);
10685     }
10686     if (cps->usePing) {
10687       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10688       SendToProgram(buf, cps);
10689     }
10690     cps->initDone = TRUE;
10691     ClearEngineOutputPane(cps == &second);
10692 }
10693
10694
10695 void
10696 ResendOptions (ChessProgramState *cps)
10697 { // send the stored value of the options
10698   int i;
10699   char buf[MSG_SIZ];
10700   Option *opt = cps->option;
10701   for(i=0; i<cps->nrOptions; i++, opt++) {
10702       switch(opt->type) {
10703         case Spin:
10704         case Slider:
10705         case CheckBox:
10706             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10707           break;
10708         case ComboBox:
10709           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10710           break;
10711         default:
10712             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10713           break;
10714         case Button:
10715         case SaveButton:
10716           continue;
10717       }
10718       SendToProgram(buf, cps);
10719   }
10720 }
10721
10722 void
10723 StartChessProgram (ChessProgramState *cps)
10724 {
10725     char buf[MSG_SIZ];
10726     int err;
10727
10728     if (appData.noChessProgram) return;
10729     cps->initDone = FALSE;
10730
10731     if (strcmp(cps->host, "localhost") == 0) {
10732         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10733     } else if (*appData.remoteShell == NULLCHAR) {
10734         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10735     } else {
10736         if (*appData.remoteUser == NULLCHAR) {
10737           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10738                     cps->program);
10739         } else {
10740           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10741                     cps->host, appData.remoteUser, cps->program);
10742         }
10743         err = StartChildProcess(buf, "", &cps->pr);
10744     }
10745
10746     if (err != 0) {
10747       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10748         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10749         if(cps != &first) return;
10750         appData.noChessProgram = TRUE;
10751         ThawUI();
10752         SetNCPMode();
10753 //      DisplayFatalError(buf, err, 1);
10754 //      cps->pr = NoProc;
10755 //      cps->isr = NULL;
10756         return;
10757     }
10758
10759     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10760     if (cps->protocolVersion > 1) {
10761       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10762       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10763         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10764         cps->comboCnt = 0;  //                and values of combo boxes
10765       }
10766       SendToProgram(buf, cps);
10767       if(cps->reload) ResendOptions(cps);
10768     } else {
10769       SendToProgram("xboard\n", cps);
10770     }
10771 }
10772
10773 void
10774 TwoMachinesEventIfReady P((void))
10775 {
10776   static int curMess = 0;
10777   if (first.lastPing != first.lastPong) {
10778     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10779     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10780     return;
10781   }
10782   if (second.lastPing != second.lastPong) {
10783     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10784     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10785     return;
10786   }
10787   DisplayMessage("", ""); curMess = 0;
10788   TwoMachinesEvent();
10789 }
10790
10791 char *
10792 MakeName (char *template)
10793 {
10794     time_t clock;
10795     struct tm *tm;
10796     static char buf[MSG_SIZ];
10797     char *p = buf;
10798     int i;
10799
10800     clock = time((time_t *)NULL);
10801     tm = localtime(&clock);
10802
10803     while(*p++ = *template++) if(p[-1] == '%') {
10804         switch(*template++) {
10805           case 0:   *p = 0; return buf;
10806           case 'Y': i = tm->tm_year+1900; break;
10807           case 'y': i = tm->tm_year-100; break;
10808           case 'M': i = tm->tm_mon+1; break;
10809           case 'd': i = tm->tm_mday; break;
10810           case 'h': i = tm->tm_hour; break;
10811           case 'm': i = tm->tm_min; break;
10812           case 's': i = tm->tm_sec; break;
10813           default:  i = 0;
10814         }
10815         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10816     }
10817     return buf;
10818 }
10819
10820 int
10821 CountPlayers (char *p)
10822 {
10823     int n = 0;
10824     while(p = strchr(p, '\n')) p++, n++; // count participants
10825     return n;
10826 }
10827
10828 FILE *
10829 WriteTourneyFile (char *results, FILE *f)
10830 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10831     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10832     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10833         // create a file with tournament description
10834         fprintf(f, "-participants {%s}\n", appData.participants);
10835         fprintf(f, "-seedBase %d\n", appData.seedBase);
10836         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10837         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10838         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10839         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10840         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10841         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10842         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10843         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10844         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10845         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10846         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10847         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10848         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10849         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10850         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10851         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10852         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10853         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10854         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10855         fprintf(f, "-smpCores %d\n", appData.smpCores);
10856         if(searchTime > 0)
10857                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10858         else {
10859                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10860                 fprintf(f, "-tc %s\n", appData.timeControl);
10861                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10862         }
10863         fprintf(f, "-results \"%s\"\n", results);
10864     }
10865     return f;
10866 }
10867
10868 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10869
10870 void
10871 Substitute (char *participants, int expunge)
10872 {
10873     int i, changed, changes=0, nPlayers=0;
10874     char *p, *q, *r, buf[MSG_SIZ];
10875     if(participants == NULL) return;
10876     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10877     r = p = participants; q = appData.participants;
10878     while(*p && *p == *q) {
10879         if(*p == '\n') r = p+1, nPlayers++;
10880         p++; q++;
10881     }
10882     if(*p) { // difference
10883         while(*p && *p++ != '\n');
10884         while(*q && *q++ != '\n');
10885       changed = nPlayers;
10886         changes = 1 + (strcmp(p, q) != 0);
10887     }
10888     if(changes == 1) { // a single engine mnemonic was changed
10889         q = r; while(*q) nPlayers += (*q++ == '\n');
10890         p = buf; while(*r && (*p = *r++) != '\n') p++;
10891         *p = NULLCHAR;
10892         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10893         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10894         if(mnemonic[i]) { // The substitute is valid
10895             FILE *f;
10896             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10897                 flock(fileno(f), LOCK_EX);
10898                 ParseArgsFromFile(f);
10899                 fseek(f, 0, SEEK_SET);
10900                 FREE(appData.participants); appData.participants = participants;
10901                 if(expunge) { // erase results of replaced engine
10902                     int len = strlen(appData.results), w, b, dummy;
10903                     for(i=0; i<len; i++) {
10904                         Pairing(i, nPlayers, &w, &b, &dummy);
10905                         if((w == changed || b == changed) && appData.results[i] == '*') {
10906                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10907                             fclose(f);
10908                             return;
10909                         }
10910                     }
10911                     for(i=0; i<len; i++) {
10912                         Pairing(i, nPlayers, &w, &b, &dummy);
10913                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10914                     }
10915                 }
10916                 WriteTourneyFile(appData.results, f);
10917                 fclose(f); // release lock
10918                 return;
10919             }
10920         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10921     }
10922     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10923     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10924     free(participants);
10925     return;
10926 }
10927
10928 int
10929 CheckPlayers (char *participants)
10930 {
10931         int i;
10932         char buf[MSG_SIZ], *p;
10933         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10934         while(p = strchr(participants, '\n')) {
10935             *p = NULLCHAR;
10936             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10937             if(!mnemonic[i]) {
10938                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10939                 *p = '\n';
10940                 DisplayError(buf, 0);
10941                 return 1;
10942             }
10943             *p = '\n';
10944             participants = p + 1;
10945         }
10946         return 0;
10947 }
10948
10949 int
10950 CreateTourney (char *name)
10951 {
10952         FILE *f;
10953         if(matchMode && strcmp(name, appData.tourneyFile)) {
10954              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10955         }
10956         if(name[0] == NULLCHAR) {
10957             if(appData.participants[0])
10958                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10959             return 0;
10960         }
10961         f = fopen(name, "r");
10962         if(f) { // file exists
10963             ASSIGN(appData.tourneyFile, name);
10964             ParseArgsFromFile(f); // parse it
10965         } else {
10966             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10967             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10968                 DisplayError(_("Not enough participants"), 0);
10969                 return 0;
10970             }
10971             if(CheckPlayers(appData.participants)) return 0;
10972             ASSIGN(appData.tourneyFile, name);
10973             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10974             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10975         }
10976         fclose(f);
10977         appData.noChessProgram = FALSE;
10978         appData.clockMode = TRUE;
10979         SetGNUMode();
10980         return 1;
10981 }
10982
10983 int
10984 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10985 {
10986     char buf[MSG_SIZ], *p, *q;
10987     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10988     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10989     skip = !all && group[0]; // if group requested, we start in skip mode
10990     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10991         p = names; q = buf; header = 0;
10992         while(*p && *p != '\n') *q++ = *p++;
10993         *q = 0;
10994         if(*p == '\n') p++;
10995         if(buf[0] == '#') {
10996             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10997             depth++; // we must be entering a new group
10998             if(all) continue; // suppress printing group headers when complete list requested
10999             header = 1;
11000             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11001         }
11002         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11003         if(engineList[i]) free(engineList[i]);
11004         engineList[i] = strdup(buf);
11005         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11006         if(engineMnemonic[i]) free(engineMnemonic[i]);
11007         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11008             strcat(buf, " (");
11009             sscanf(q + 8, "%s", buf + strlen(buf));
11010             strcat(buf, ")");
11011         }
11012         engineMnemonic[i] = strdup(buf);
11013         i++;
11014     }
11015     engineList[i] = engineMnemonic[i] = NULL;
11016     return i;
11017 }
11018
11019 // following implemented as macro to avoid type limitations
11020 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11021
11022 void
11023 SwapEngines (int n)
11024 {   // swap settings for first engine and other engine (so far only some selected options)
11025     int h;
11026     char *p;
11027     if(n == 0) return;
11028     SWAP(directory, p)
11029     SWAP(chessProgram, p)
11030     SWAP(isUCI, h)
11031     SWAP(hasOwnBookUCI, h)
11032     SWAP(protocolVersion, h)
11033     SWAP(reuse, h)
11034     SWAP(scoreIsAbsolute, h)
11035     SWAP(timeOdds, h)
11036     SWAP(logo, p)
11037     SWAP(pgnName, p)
11038     SWAP(pvSAN, h)
11039     SWAP(engOptions, p)
11040     SWAP(engInitString, p)
11041     SWAP(computerString, p)
11042     SWAP(features, p)
11043     SWAP(fenOverride, p)
11044     SWAP(NPS, h)
11045     SWAP(accumulateTC, h)
11046     SWAP(drawDepth, h)
11047     SWAP(host, p)
11048     SWAP(pseudo, h)
11049 }
11050
11051 int
11052 GetEngineLine (char *s, int n)
11053 {
11054     int i;
11055     char buf[MSG_SIZ];
11056     extern char *icsNames;
11057     if(!s || !*s) return 0;
11058     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11059     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11060     if(!mnemonic[i]) return 0;
11061     if(n == 11) return 1; // just testing if there was a match
11062     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11063     if(n == 1) SwapEngines(n);
11064     ParseArgsFromString(buf);
11065     if(n == 1) SwapEngines(n);
11066     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11067         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11068         ParseArgsFromString(buf);
11069     }
11070     return 1;
11071 }
11072
11073 int
11074 SetPlayer (int player, char *p)
11075 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11076     int i;
11077     char buf[MSG_SIZ], *engineName;
11078     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11079     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11080     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11081     if(mnemonic[i]) {
11082         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11083         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11084         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11085         ParseArgsFromString(buf);
11086     } else { // no engine with this nickname is installed!
11087         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11088         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11089         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11090         ModeHighlight();
11091         DisplayError(buf, 0);
11092         return 0;
11093     }
11094     free(engineName);
11095     return i;
11096 }
11097
11098 char *recentEngines;
11099
11100 void
11101 RecentEngineEvent (int nr)
11102 {
11103     int n;
11104 //    SwapEngines(1); // bump first to second
11105 //    ReplaceEngine(&second, 1); // and load it there
11106     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11107     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11108     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11109         ReplaceEngine(&first, 0);
11110         FloatToFront(&appData.recentEngineList, command[n]);
11111     }
11112 }
11113
11114 int
11115 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11116 {   // determine players from game number
11117     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11118
11119     if(appData.tourneyType == 0) {
11120         roundsPerCycle = (nPlayers - 1) | 1;
11121         pairingsPerRound = nPlayers / 2;
11122     } else if(appData.tourneyType > 0) {
11123         roundsPerCycle = nPlayers - appData.tourneyType;
11124         pairingsPerRound = appData.tourneyType;
11125     }
11126     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11127     gamesPerCycle = gamesPerRound * roundsPerCycle;
11128     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11129     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11130     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11131     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11132     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11133     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11134
11135     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11136     if(appData.roundSync) *syncInterval = gamesPerRound;
11137
11138     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11139
11140     if(appData.tourneyType == 0) {
11141         if(curPairing == (nPlayers-1)/2 ) {
11142             *whitePlayer = curRound;
11143             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11144         } else {
11145             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11146             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11147             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11148             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11149         }
11150     } else if(appData.tourneyType > 1) {
11151         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11152         *whitePlayer = curRound + appData.tourneyType;
11153     } else if(appData.tourneyType > 0) {
11154         *whitePlayer = curPairing;
11155         *blackPlayer = curRound + appData.tourneyType;
11156     }
11157
11158     // take care of white/black alternation per round.
11159     // For cycles and games this is already taken care of by default, derived from matchGame!
11160     return curRound & 1;
11161 }
11162
11163 int
11164 NextTourneyGame (int nr, int *swapColors)
11165 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11166     char *p, *q;
11167     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11168     FILE *tf;
11169     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11170     tf = fopen(appData.tourneyFile, "r");
11171     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11172     ParseArgsFromFile(tf); fclose(tf);
11173     InitTimeControls(); // TC might be altered from tourney file
11174
11175     nPlayers = CountPlayers(appData.participants); // count participants
11176     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11177     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11178
11179     if(syncInterval) {
11180         p = q = appData.results;
11181         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11182         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11183             DisplayMessage(_("Waiting for other game(s)"),"");
11184             waitingForGame = TRUE;
11185             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11186             return 0;
11187         }
11188         waitingForGame = FALSE;
11189     }
11190
11191     if(appData.tourneyType < 0) {
11192         if(nr>=0 && !pairingReceived) {
11193             char buf[1<<16];
11194             if(pairing.pr == NoProc) {
11195                 if(!appData.pairingEngine[0]) {
11196                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11197                     return 0;
11198                 }
11199                 StartChessProgram(&pairing); // starts the pairing engine
11200             }
11201             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11202             SendToProgram(buf, &pairing);
11203             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11204             SendToProgram(buf, &pairing);
11205             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11206         }
11207         pairingReceived = 0;                              // ... so we continue here
11208         *swapColors = 0;
11209         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11210         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11211         matchGame = 1; roundNr = nr / syncInterval + 1;
11212     }
11213
11214     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11215
11216     // redefine engines, engine dir, etc.
11217     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11218     if(first.pr == NoProc) {
11219       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11220       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11221     }
11222     if(second.pr == NoProc) {
11223       SwapEngines(1);
11224       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11225       SwapEngines(1);         // and make that valid for second engine by swapping
11226       InitEngine(&second, 1);
11227     }
11228     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11229     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11230     return OK;
11231 }
11232
11233 void
11234 NextMatchGame ()
11235 {   // performs game initialization that does not invoke engines, and then tries to start the game
11236     int res, firstWhite, swapColors = 0;
11237     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11238     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
11239         char buf[MSG_SIZ];
11240         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11241         if(strcmp(buf, currentDebugFile)) { // name has changed
11242             FILE *f = fopen(buf, "w");
11243             if(f) { // if opening the new file failed, just keep using the old one
11244                 ASSIGN(currentDebugFile, buf);
11245                 fclose(debugFP);
11246                 debugFP = f;
11247             }
11248             if(appData.serverFileName) {
11249                 if(serverFP) fclose(serverFP);
11250                 serverFP = fopen(appData.serverFileName, "w");
11251                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11252                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11253             }
11254         }
11255     }
11256     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11257     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11258     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11259     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11260     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11261     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11262     Reset(FALSE, first.pr != NoProc);
11263     res = LoadGameOrPosition(matchGame); // setup game
11264     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11265     if(!res) return; // abort when bad game/pos file
11266     TwoMachinesEvent();
11267 }
11268
11269 void
11270 UserAdjudicationEvent (int result)
11271 {
11272     ChessMove gameResult = GameIsDrawn;
11273
11274     if( result > 0 ) {
11275         gameResult = WhiteWins;
11276     }
11277     else if( result < 0 ) {
11278         gameResult = BlackWins;
11279     }
11280
11281     if( gameMode == TwoMachinesPlay ) {
11282         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11283     }
11284 }
11285
11286
11287 // [HGM] save: calculate checksum of game to make games easily identifiable
11288 int
11289 StringCheckSum (char *s)
11290 {
11291         int i = 0;
11292         if(s==NULL) return 0;
11293         while(*s) i = i*259 + *s++;
11294         return i;
11295 }
11296
11297 int
11298 GameCheckSum ()
11299 {
11300         int i, sum=0;
11301         for(i=backwardMostMove; i<forwardMostMove; i++) {
11302                 sum += pvInfoList[i].depth;
11303                 sum += StringCheckSum(parseList[i]);
11304                 sum += StringCheckSum(commentList[i]);
11305                 sum *= 261;
11306         }
11307         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11308         return sum + StringCheckSum(commentList[i]);
11309 } // end of save patch
11310
11311 void
11312 GameEnds (ChessMove result, char *resultDetails, int whosays)
11313 {
11314     GameMode nextGameMode;
11315     int isIcsGame;
11316     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11317
11318     if(endingGame) return; /* [HGM] crash: forbid recursion */
11319     endingGame = 1;
11320     if(twoBoards) { // [HGM] dual: switch back to one board
11321         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11322         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11323     }
11324     if (appData.debugMode) {
11325       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11326               result, resultDetails ? resultDetails : "(null)", whosays);
11327     }
11328
11329     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11330
11331     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11332
11333     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11334         /* If we are playing on ICS, the server decides when the
11335            game is over, but the engine can offer to draw, claim
11336            a draw, or resign.
11337          */
11338 #if ZIPPY
11339         if (appData.zippyPlay && first.initDone) {
11340             if (result == GameIsDrawn) {
11341                 /* In case draw still needs to be claimed */
11342                 SendToICS(ics_prefix);
11343                 SendToICS("draw\n");
11344             } else if (StrCaseStr(resultDetails, "resign")) {
11345                 SendToICS(ics_prefix);
11346                 SendToICS("resign\n");
11347             }
11348         }
11349 #endif
11350         endingGame = 0; /* [HGM] crash */
11351         return;
11352     }
11353
11354     /* If we're loading the game from a file, stop */
11355     if (whosays == GE_FILE) {
11356       (void) StopLoadGameTimer();
11357       gameFileFP = NULL;
11358     }
11359
11360     /* Cancel draw offers */
11361     first.offeredDraw = second.offeredDraw = 0;
11362
11363     /* If this is an ICS game, only ICS can really say it's done;
11364        if not, anyone can. */
11365     isIcsGame = (gameMode == IcsPlayingWhite ||
11366                  gameMode == IcsPlayingBlack ||
11367                  gameMode == IcsObserving    ||
11368                  gameMode == IcsExamining);
11369
11370     if (!isIcsGame || whosays == GE_ICS) {
11371         /* OK -- not an ICS game, or ICS said it was done */
11372         StopClocks();
11373         if (!isIcsGame && !appData.noChessProgram)
11374           SetUserThinkingEnables();
11375
11376         /* [HGM] if a machine claims the game end we verify this claim */
11377         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11378             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11379                 char claimer;
11380                 ChessMove trueResult = (ChessMove) -1;
11381
11382                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11383                                             first.twoMachinesColor[0] :
11384                                             second.twoMachinesColor[0] ;
11385
11386                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11387                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11388                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11389                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11390                 } else
11391                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11392                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11393                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11394                 } else
11395                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11396                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11397                 }
11398
11399                 // now verify win claims, but not in drop games, as we don't understand those yet
11400                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11401                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11402                     (result == WhiteWins && claimer == 'w' ||
11403                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11404                       if (appData.debugMode) {
11405                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11406                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11407                       }
11408                       if(result != trueResult) {
11409                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11410                               result = claimer == 'w' ? BlackWins : WhiteWins;
11411                               resultDetails = buf;
11412                       }
11413                 } else
11414                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11415                     && (forwardMostMove <= backwardMostMove ||
11416                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11417                         (claimer=='b')==(forwardMostMove&1))
11418                                                                                   ) {
11419                       /* [HGM] verify: draws that were not flagged are false claims */
11420                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11421                       result = claimer == 'w' ? BlackWins : WhiteWins;
11422                       resultDetails = buf;
11423                 }
11424                 /* (Claiming a loss is accepted no questions asked!) */
11425             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11426                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11427                 result = GameUnfinished;
11428                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11429             }
11430             /* [HGM] bare: don't allow bare King to win */
11431             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11432                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11433                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11434                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11435                && result != GameIsDrawn)
11436             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11437                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11438                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11439                         if(p >= 0 && p <= (int)WhiteKing) k++;
11440                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11441                 }
11442                 if (appData.debugMode) {
11443                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11444                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11445                 }
11446                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11447                         result = GameIsDrawn;
11448                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11449                         resultDetails = buf;
11450                 }
11451             }
11452         }
11453
11454
11455         if(serverMoves != NULL && !loadFlag) { char c = '=';
11456             if(result==WhiteWins) c = '+';
11457             if(result==BlackWins) c = '-';
11458             if(resultDetails != NULL)
11459                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11460         }
11461         if (resultDetails != NULL) {
11462             gameInfo.result = result;
11463             gameInfo.resultDetails = StrSave(resultDetails);
11464
11465             /* display last move only if game was not loaded from file */
11466             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11467                 DisplayMove(currentMove - 1);
11468
11469             if (forwardMostMove != 0) {
11470                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11471                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11472                                                                 ) {
11473                     if (*appData.saveGameFile != NULLCHAR) {
11474                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11475                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11476                         else
11477                         SaveGameToFile(appData.saveGameFile, TRUE);
11478                     } else if (appData.autoSaveGames) {
11479                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11480                     }
11481                     if (*appData.savePositionFile != NULLCHAR) {
11482                         SavePositionToFile(appData.savePositionFile);
11483                     }
11484                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11485                 }
11486             }
11487
11488             /* Tell program how game ended in case it is learning */
11489             /* [HGM] Moved this to after saving the PGN, just in case */
11490             /* engine died and we got here through time loss. In that */
11491             /* case we will get a fatal error writing the pipe, which */
11492             /* would otherwise lose us the PGN.                       */
11493             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11494             /* output during GameEnds should never be fatal anymore   */
11495             if (gameMode == MachinePlaysWhite ||
11496                 gameMode == MachinePlaysBlack ||
11497                 gameMode == TwoMachinesPlay ||
11498                 gameMode == IcsPlayingWhite ||
11499                 gameMode == IcsPlayingBlack ||
11500                 gameMode == BeginningOfGame) {
11501                 char buf[MSG_SIZ];
11502                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11503                         resultDetails);
11504                 if (first.pr != NoProc) {
11505                     SendToProgram(buf, &first);
11506                 }
11507                 if (second.pr != NoProc &&
11508                     gameMode == TwoMachinesPlay) {
11509                     SendToProgram(buf, &second);
11510                 }
11511             }
11512         }
11513
11514         if (appData.icsActive) {
11515             if (appData.quietPlay &&
11516                 (gameMode == IcsPlayingWhite ||
11517                  gameMode == IcsPlayingBlack)) {
11518                 SendToICS(ics_prefix);
11519                 SendToICS("set shout 1\n");
11520             }
11521             nextGameMode = IcsIdle;
11522             ics_user_moved = FALSE;
11523             /* clean up premove.  It's ugly when the game has ended and the
11524              * premove highlights are still on the board.
11525              */
11526             if (gotPremove) {
11527               gotPremove = FALSE;
11528               ClearPremoveHighlights();
11529               DrawPosition(FALSE, boards[currentMove]);
11530             }
11531             if (whosays == GE_ICS) {
11532                 switch (result) {
11533                 case WhiteWins:
11534                     if (gameMode == IcsPlayingWhite)
11535                         PlayIcsWinSound();
11536                     else if(gameMode == IcsPlayingBlack)
11537                         PlayIcsLossSound();
11538                     break;
11539                 case BlackWins:
11540                     if (gameMode == IcsPlayingBlack)
11541                         PlayIcsWinSound();
11542                     else if(gameMode == IcsPlayingWhite)
11543                         PlayIcsLossSound();
11544                     break;
11545                 case GameIsDrawn:
11546                     PlayIcsDrawSound();
11547                     break;
11548                 default:
11549                     PlayIcsUnfinishedSound();
11550                 }
11551             }
11552             if(appData.quitNext) { ExitEvent(0); return; }
11553         } else if (gameMode == EditGame ||
11554                    gameMode == PlayFromGameFile ||
11555                    gameMode == AnalyzeMode ||
11556                    gameMode == AnalyzeFile) {
11557             nextGameMode = gameMode;
11558         } else {
11559             nextGameMode = EndOfGame;
11560         }
11561         pausing = FALSE;
11562         ModeHighlight();
11563     } else {
11564         nextGameMode = gameMode;
11565     }
11566
11567     if (appData.noChessProgram) {
11568         gameMode = nextGameMode;
11569         ModeHighlight();
11570         endingGame = 0; /* [HGM] crash */
11571         return;
11572     }
11573
11574     if (first.reuse) {
11575         /* Put first chess program into idle state */
11576         if (first.pr != NoProc &&
11577             (gameMode == MachinePlaysWhite ||
11578              gameMode == MachinePlaysBlack ||
11579              gameMode == TwoMachinesPlay ||
11580              gameMode == IcsPlayingWhite ||
11581              gameMode == IcsPlayingBlack ||
11582              gameMode == BeginningOfGame)) {
11583             SendToProgram("force\n", &first);
11584             if (first.usePing) {
11585               char buf[MSG_SIZ];
11586               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11587               SendToProgram(buf, &first);
11588             }
11589         }
11590     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11591         /* Kill off first chess program */
11592         if (first.isr != NULL)
11593           RemoveInputSource(first.isr);
11594         first.isr = NULL;
11595
11596         if (first.pr != NoProc) {
11597             ExitAnalyzeMode();
11598             DoSleep( appData.delayBeforeQuit );
11599             SendToProgram("quit\n", &first);
11600             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11601             first.reload = TRUE;
11602         }
11603         first.pr = NoProc;
11604     }
11605     if (second.reuse) {
11606         /* Put second chess program into idle state */
11607         if (second.pr != NoProc &&
11608             gameMode == TwoMachinesPlay) {
11609             SendToProgram("force\n", &second);
11610             if (second.usePing) {
11611               char buf[MSG_SIZ];
11612               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11613               SendToProgram(buf, &second);
11614             }
11615         }
11616     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11617         /* Kill off second chess program */
11618         if (second.isr != NULL)
11619           RemoveInputSource(second.isr);
11620         second.isr = NULL;
11621
11622         if (second.pr != NoProc) {
11623             DoSleep( appData.delayBeforeQuit );
11624             SendToProgram("quit\n", &second);
11625             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11626             second.reload = TRUE;
11627         }
11628         second.pr = NoProc;
11629     }
11630
11631     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11632         char resChar = '=';
11633         switch (result) {
11634         case WhiteWins:
11635           resChar = '+';
11636           if (first.twoMachinesColor[0] == 'w') {
11637             first.matchWins++;
11638           } else {
11639             second.matchWins++;
11640           }
11641           break;
11642         case BlackWins:
11643           resChar = '-';
11644           if (first.twoMachinesColor[0] == 'b') {
11645             first.matchWins++;
11646           } else {
11647             second.matchWins++;
11648           }
11649           break;
11650         case GameUnfinished:
11651           resChar = ' ';
11652         default:
11653           break;
11654         }
11655
11656         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11657         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11658             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11659             ReserveGame(nextGame, resChar); // sets nextGame
11660             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11661             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11662         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11663
11664         if (nextGame <= appData.matchGames && !abortMatch) {
11665             gameMode = nextGameMode;
11666             matchGame = nextGame; // this will be overruled in tourney mode!
11667             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11668             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11669             endingGame = 0; /* [HGM] crash */
11670             return;
11671         } else {
11672             gameMode = nextGameMode;
11673             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11674                      first.tidy, second.tidy,
11675                      first.matchWins, second.matchWins,
11676                      appData.matchGames - (first.matchWins + second.matchWins));
11677             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11678             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11679             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11680             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11681                 first.twoMachinesColor = "black\n";
11682                 second.twoMachinesColor = "white\n";
11683             } else {
11684                 first.twoMachinesColor = "white\n";
11685                 second.twoMachinesColor = "black\n";
11686             }
11687         }
11688     }
11689     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11690         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11691       ExitAnalyzeMode();
11692     gameMode = nextGameMode;
11693     ModeHighlight();
11694     endingGame = 0;  /* [HGM] crash */
11695     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11696         if(matchMode == TRUE) { // match through command line: exit with or without popup
11697             if(ranking) {
11698                 ToNrEvent(forwardMostMove);
11699                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11700                 else ExitEvent(0);
11701             } else DisplayFatalError(buf, 0, 0);
11702         } else { // match through menu; just stop, with or without popup
11703             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11704             ModeHighlight();
11705             if(ranking){
11706                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11707             } else DisplayNote(buf);
11708       }
11709       if(ranking) free(ranking);
11710     }
11711 }
11712
11713 /* Assumes program was just initialized (initString sent).
11714    Leaves program in force mode. */
11715 void
11716 FeedMovesToProgram (ChessProgramState *cps, int upto)
11717 {
11718     int i;
11719
11720     if (appData.debugMode)
11721       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11722               startedFromSetupPosition ? "position and " : "",
11723               backwardMostMove, upto, cps->which);
11724     if(currentlyInitializedVariant != gameInfo.variant) {
11725       char buf[MSG_SIZ];
11726         // [HGM] variantswitch: make engine aware of new variant
11727         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11728                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11729                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11730         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11731         SendToProgram(buf, cps);
11732         currentlyInitializedVariant = gameInfo.variant;
11733     }
11734     SendToProgram("force\n", cps);
11735     if (startedFromSetupPosition) {
11736         SendBoard(cps, backwardMostMove);
11737     if (appData.debugMode) {
11738         fprintf(debugFP, "feedMoves\n");
11739     }
11740     }
11741     for (i = backwardMostMove; i < upto; i++) {
11742         SendMoveToProgram(i, cps);
11743     }
11744 }
11745
11746
11747 int
11748 ResurrectChessProgram ()
11749 {
11750      /* The chess program may have exited.
11751         If so, restart it and feed it all the moves made so far. */
11752     static int doInit = 0;
11753
11754     if (appData.noChessProgram) return 1;
11755
11756     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11757         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11758         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11759         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11760     } else {
11761         if (first.pr != NoProc) return 1;
11762         StartChessProgram(&first);
11763     }
11764     InitChessProgram(&first, FALSE);
11765     FeedMovesToProgram(&first, currentMove);
11766
11767     if (!first.sendTime) {
11768         /* can't tell gnuchess what its clock should read,
11769            so we bow to its notion. */
11770         ResetClocks();
11771         timeRemaining[0][currentMove] = whiteTimeRemaining;
11772         timeRemaining[1][currentMove] = blackTimeRemaining;
11773     }
11774
11775     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11776                 appData.icsEngineAnalyze) && first.analysisSupport) {
11777       SendToProgram("analyze\n", &first);
11778       first.analyzing = TRUE;
11779     }
11780     return 1;
11781 }
11782
11783 /*
11784  * Button procedures
11785  */
11786 void
11787 Reset (int redraw, int init)
11788 {
11789     int i;
11790
11791     if (appData.debugMode) {
11792         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11793                 redraw, init, gameMode);
11794     }
11795     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11796     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11797     CleanupTail(); // [HGM] vari: delete any stored variations
11798     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11799     pausing = pauseExamInvalid = FALSE;
11800     startedFromSetupPosition = blackPlaysFirst = FALSE;
11801     firstMove = TRUE;
11802     whiteFlag = blackFlag = FALSE;
11803     userOfferedDraw = FALSE;
11804     hintRequested = bookRequested = FALSE;
11805     first.maybeThinking = FALSE;
11806     second.maybeThinking = FALSE;
11807     first.bookSuspend = FALSE; // [HGM] book
11808     second.bookSuspend = FALSE;
11809     thinkOutput[0] = NULLCHAR;
11810     lastHint[0] = NULLCHAR;
11811     ClearGameInfo(&gameInfo);
11812     gameInfo.variant = StringToVariant(appData.variant);
11813     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11814     ics_user_moved = ics_clock_paused = FALSE;
11815     ics_getting_history = H_FALSE;
11816     ics_gamenum = -1;
11817     white_holding[0] = black_holding[0] = NULLCHAR;
11818     ClearProgramStats();
11819     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11820
11821     ResetFrontEnd();
11822     ClearHighlights();
11823     flipView = appData.flipView;
11824     ClearPremoveHighlights();
11825     gotPremove = FALSE;
11826     alarmSounded = FALSE;
11827     killX = killY = -1; // [HGM] lion
11828
11829     GameEnds(EndOfFile, NULL, GE_PLAYER);
11830     if(appData.serverMovesName != NULL) {
11831         /* [HGM] prepare to make moves file for broadcasting */
11832         clock_t t = clock();
11833         if(serverMoves != NULL) fclose(serverMoves);
11834         serverMoves = fopen(appData.serverMovesName, "r");
11835         if(serverMoves != NULL) {
11836             fclose(serverMoves);
11837             /* delay 15 sec before overwriting, so all clients can see end */
11838             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11839         }
11840         serverMoves = fopen(appData.serverMovesName, "w");
11841     }
11842
11843     ExitAnalyzeMode();
11844     gameMode = BeginningOfGame;
11845     ModeHighlight();
11846     if(appData.icsActive) gameInfo.variant = VariantNormal;
11847     currentMove = forwardMostMove = backwardMostMove = 0;
11848     MarkTargetSquares(1);
11849     InitPosition(redraw);
11850     for (i = 0; i < MAX_MOVES; i++) {
11851         if (commentList[i] != NULL) {
11852             free(commentList[i]);
11853             commentList[i] = NULL;
11854         }
11855     }
11856     ResetClocks();
11857     timeRemaining[0][0] = whiteTimeRemaining;
11858     timeRemaining[1][0] = blackTimeRemaining;
11859
11860     if (first.pr == NoProc) {
11861         StartChessProgram(&first);
11862     }
11863     if (init) {
11864             InitChessProgram(&first, startedFromSetupPosition);
11865     }
11866     DisplayTitle("");
11867     DisplayMessage("", "");
11868     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11869     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11870     ClearMap();        // [HGM] exclude: invalidate map
11871 }
11872
11873 void
11874 AutoPlayGameLoop ()
11875 {
11876     for (;;) {
11877         if (!AutoPlayOneMove())
11878           return;
11879         if (matchMode || appData.timeDelay == 0)
11880           continue;
11881         if (appData.timeDelay < 0)
11882           return;
11883         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11884         break;
11885     }
11886 }
11887
11888 void
11889 AnalyzeNextGame()
11890 {
11891     ReloadGame(1); // next game
11892 }
11893
11894 int
11895 AutoPlayOneMove ()
11896 {
11897     int fromX, fromY, toX, toY;
11898
11899     if (appData.debugMode) {
11900       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11901     }
11902
11903     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11904       return FALSE;
11905
11906     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11907       pvInfoList[currentMove].depth = programStats.depth;
11908       pvInfoList[currentMove].score = programStats.score;
11909       pvInfoList[currentMove].time  = 0;
11910       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11911       else { // append analysis of final position as comment
11912         char buf[MSG_SIZ];
11913         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11914         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11915       }
11916       programStats.depth = 0;
11917     }
11918
11919     if (currentMove >= forwardMostMove) {
11920       if(gameMode == AnalyzeFile) {
11921           if(appData.loadGameIndex == -1) {
11922             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11923           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11924           } else {
11925           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11926         }
11927       }
11928 //      gameMode = EndOfGame;
11929 //      ModeHighlight();
11930
11931       /* [AS] Clear current move marker at the end of a game */
11932       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11933
11934       return FALSE;
11935     }
11936
11937     toX = moveList[currentMove][2] - AAA;
11938     toY = moveList[currentMove][3] - ONE;
11939
11940     if (moveList[currentMove][1] == '@') {
11941         if (appData.highlightLastMove) {
11942             SetHighlights(-1, -1, toX, toY);
11943         }
11944     } else {
11945         int viaX = moveList[currentMove][5] - AAA;
11946         int viaY = moveList[currentMove][6] - ONE;
11947         fromX = moveList[currentMove][0] - AAA;
11948         fromY = moveList[currentMove][1] - ONE;
11949
11950         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11951
11952         if(moveList[currentMove][4] == ';') { // multi-leg
11953             ChessSquare piece = boards[currentMove][viaY][viaX];
11954             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11955             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11956             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11957             boards[currentMove][viaY][viaX] = piece;
11958         } else
11959         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11960
11961         if (appData.highlightLastMove) {
11962             SetHighlights(fromX, fromY, toX, toY);
11963         }
11964     }
11965     DisplayMove(currentMove);
11966     SendMoveToProgram(currentMove++, &first);
11967     DisplayBothClocks();
11968     DrawPosition(FALSE, boards[currentMove]);
11969     // [HGM] PV info: always display, routine tests if empty
11970     DisplayComment(currentMove - 1, commentList[currentMove]);
11971     return TRUE;
11972 }
11973
11974
11975 int
11976 LoadGameOneMove (ChessMove readAhead)
11977 {
11978     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11979     char promoChar = NULLCHAR;
11980     ChessMove moveType;
11981     char move[MSG_SIZ];
11982     char *p, *q;
11983
11984     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11985         gameMode != AnalyzeMode && gameMode != Training) {
11986         gameFileFP = NULL;
11987         return FALSE;
11988     }
11989
11990     yyboardindex = forwardMostMove;
11991     if (readAhead != EndOfFile) {
11992       moveType = readAhead;
11993     } else {
11994       if (gameFileFP == NULL)
11995           return FALSE;
11996       moveType = (ChessMove) Myylex();
11997     }
11998
11999     done = FALSE;
12000     switch (moveType) {
12001       case Comment:
12002         if (appData.debugMode)
12003           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12004         p = yy_text;
12005
12006         /* append the comment but don't display it */
12007         AppendComment(currentMove, p, FALSE);
12008         return TRUE;
12009
12010       case WhiteCapturesEnPassant:
12011       case BlackCapturesEnPassant:
12012       case WhitePromotion:
12013       case BlackPromotion:
12014       case WhiteNonPromotion:
12015       case BlackNonPromotion:
12016       case NormalMove:
12017       case FirstLeg:
12018       case WhiteKingSideCastle:
12019       case WhiteQueenSideCastle:
12020       case BlackKingSideCastle:
12021       case BlackQueenSideCastle:
12022       case WhiteKingSideCastleWild:
12023       case WhiteQueenSideCastleWild:
12024       case BlackKingSideCastleWild:
12025       case BlackQueenSideCastleWild:
12026       /* PUSH Fabien */
12027       case WhiteHSideCastleFR:
12028       case WhiteASideCastleFR:
12029       case BlackHSideCastleFR:
12030       case BlackASideCastleFR:
12031       /* POP Fabien */
12032         if (appData.debugMode)
12033           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12034         fromX = currentMoveString[0] - AAA;
12035         fromY = currentMoveString[1] - ONE;
12036         toX = currentMoveString[2] - AAA;
12037         toY = currentMoveString[3] - ONE;
12038         promoChar = currentMoveString[4];
12039         if(promoChar == ';') promoChar = NULLCHAR;
12040         break;
12041
12042       case WhiteDrop:
12043       case BlackDrop:
12044         if (appData.debugMode)
12045           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12046         fromX = moveType == WhiteDrop ?
12047           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12048         (int) CharToPiece(ToLower(currentMoveString[0]));
12049         fromY = DROP_RANK;
12050         toX = currentMoveString[2] - AAA;
12051         toY = currentMoveString[3] - ONE;
12052         break;
12053
12054       case WhiteWins:
12055       case BlackWins:
12056       case GameIsDrawn:
12057       case GameUnfinished:
12058         if (appData.debugMode)
12059           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12060         p = strchr(yy_text, '{');
12061         if (p == NULL) p = strchr(yy_text, '(');
12062         if (p == NULL) {
12063             p = yy_text;
12064             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12065         } else {
12066             q = strchr(p, *p == '{' ? '}' : ')');
12067             if (q != NULL) *q = NULLCHAR;
12068             p++;
12069         }
12070         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12071         GameEnds(moveType, p, GE_FILE);
12072         done = TRUE;
12073         if (cmailMsgLoaded) {
12074             ClearHighlights();
12075             flipView = WhiteOnMove(currentMove);
12076             if (moveType == GameUnfinished) flipView = !flipView;
12077             if (appData.debugMode)
12078               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12079         }
12080         break;
12081
12082       case EndOfFile:
12083         if (appData.debugMode)
12084           fprintf(debugFP, "Parser hit end of file\n");
12085         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12086           case MT_NONE:
12087           case MT_CHECK:
12088             break;
12089           case MT_CHECKMATE:
12090           case MT_STAINMATE:
12091             if (WhiteOnMove(currentMove)) {
12092                 GameEnds(BlackWins, "Black mates", GE_FILE);
12093             } else {
12094                 GameEnds(WhiteWins, "White mates", GE_FILE);
12095             }
12096             break;
12097           case MT_STALEMATE:
12098             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12099             break;
12100         }
12101         done = TRUE;
12102         break;
12103
12104       case MoveNumberOne:
12105         if (lastLoadGameStart == GNUChessGame) {
12106             /* GNUChessGames have numbers, but they aren't move numbers */
12107             if (appData.debugMode)
12108               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12109                       yy_text, (int) moveType);
12110             return LoadGameOneMove(EndOfFile); /* tail recursion */
12111         }
12112         /* else fall thru */
12113
12114       case XBoardGame:
12115       case GNUChessGame:
12116       case PGNTag:
12117         /* Reached start of next game in file */
12118         if (appData.debugMode)
12119           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12120         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12121           case MT_NONE:
12122           case MT_CHECK:
12123             break;
12124           case MT_CHECKMATE:
12125           case MT_STAINMATE:
12126             if (WhiteOnMove(currentMove)) {
12127                 GameEnds(BlackWins, "Black mates", GE_FILE);
12128             } else {
12129                 GameEnds(WhiteWins, "White mates", GE_FILE);
12130             }
12131             break;
12132           case MT_STALEMATE:
12133             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12134             break;
12135         }
12136         done = TRUE;
12137         break;
12138
12139       case PositionDiagram:     /* should not happen; ignore */
12140       case ElapsedTime:         /* ignore */
12141       case NAG:                 /* ignore */
12142         if (appData.debugMode)
12143           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12144                   yy_text, (int) moveType);
12145         return LoadGameOneMove(EndOfFile); /* tail recursion */
12146
12147       case IllegalMove:
12148         if (appData.testLegality) {
12149             if (appData.debugMode)
12150               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12151             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12152                     (forwardMostMove / 2) + 1,
12153                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12154             DisplayError(move, 0);
12155             done = TRUE;
12156         } else {
12157             if (appData.debugMode)
12158               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12159                       yy_text, currentMoveString);
12160             if(currentMoveString[1] == '@') {
12161                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12162                 fromY = DROP_RANK;
12163             } else {
12164                 fromX = currentMoveString[0] - AAA;
12165                 fromY = currentMoveString[1] - ONE;
12166             }
12167             toX = currentMoveString[2] - AAA;
12168             toY = currentMoveString[3] - ONE;
12169             promoChar = currentMoveString[4];
12170         }
12171         break;
12172
12173       case AmbiguousMove:
12174         if (appData.debugMode)
12175           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12176         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12177                 (forwardMostMove / 2) + 1,
12178                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12179         DisplayError(move, 0);
12180         done = TRUE;
12181         break;
12182
12183       default:
12184       case ImpossibleMove:
12185         if (appData.debugMode)
12186           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12187         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12188                 (forwardMostMove / 2) + 1,
12189                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12190         DisplayError(move, 0);
12191         done = TRUE;
12192         break;
12193     }
12194
12195     if (done) {
12196         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12197             DrawPosition(FALSE, boards[currentMove]);
12198             DisplayBothClocks();
12199             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12200               DisplayComment(currentMove - 1, commentList[currentMove]);
12201         }
12202         (void) StopLoadGameTimer();
12203         gameFileFP = NULL;
12204         cmailOldMove = forwardMostMove;
12205         return FALSE;
12206     } else {
12207         /* currentMoveString is set as a side-effect of yylex */
12208
12209         thinkOutput[0] = NULLCHAR;
12210         MakeMove(fromX, fromY, toX, toY, promoChar);
12211         killX = killY = -1; // [HGM] lion: used up
12212         currentMove = forwardMostMove;
12213         return TRUE;
12214     }
12215 }
12216
12217 /* Load the nth game from the given file */
12218 int
12219 LoadGameFromFile (char *filename, int n, char *title, int useList)
12220 {
12221     FILE *f;
12222     char buf[MSG_SIZ];
12223
12224     if (strcmp(filename, "-") == 0) {
12225         f = stdin;
12226         title = "stdin";
12227     } else {
12228         f = fopen(filename, "rb");
12229         if (f == NULL) {
12230           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12231             DisplayError(buf, errno);
12232             return FALSE;
12233         }
12234     }
12235     if (fseek(f, 0, 0) == -1) {
12236         /* f is not seekable; probably a pipe */
12237         useList = FALSE;
12238     }
12239     if (useList && n == 0) {
12240         int error = GameListBuild(f);
12241         if (error) {
12242             DisplayError(_("Cannot build game list"), error);
12243         } else if (!ListEmpty(&gameList) &&
12244                    ((ListGame *) gameList.tailPred)->number > 1) {
12245             GameListPopUp(f, title);
12246             return TRUE;
12247         }
12248         GameListDestroy();
12249         n = 1;
12250     }
12251     if (n == 0) n = 1;
12252     return LoadGame(f, n, title, FALSE);
12253 }
12254
12255
12256 void
12257 MakeRegisteredMove ()
12258 {
12259     int fromX, fromY, toX, toY;
12260     char promoChar;
12261     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12262         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12263           case CMAIL_MOVE:
12264           case CMAIL_DRAW:
12265             if (appData.debugMode)
12266               fprintf(debugFP, "Restoring %s for game %d\n",
12267                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12268
12269             thinkOutput[0] = NULLCHAR;
12270             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12271             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12272             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12273             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12274             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12275             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12276             MakeMove(fromX, fromY, toX, toY, promoChar);
12277             ShowMove(fromX, fromY, toX, toY);
12278
12279             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12280               case MT_NONE:
12281               case MT_CHECK:
12282                 break;
12283
12284               case MT_CHECKMATE:
12285               case MT_STAINMATE:
12286                 if (WhiteOnMove(currentMove)) {
12287                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12288                 } else {
12289                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12290                 }
12291                 break;
12292
12293               case MT_STALEMATE:
12294                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12295                 break;
12296             }
12297
12298             break;
12299
12300           case CMAIL_RESIGN:
12301             if (WhiteOnMove(currentMove)) {
12302                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12303             } else {
12304                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12305             }
12306             break;
12307
12308           case CMAIL_ACCEPT:
12309             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12310             break;
12311
12312           default:
12313             break;
12314         }
12315     }
12316
12317     return;
12318 }
12319
12320 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12321 int
12322 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12323 {
12324     int retVal;
12325
12326     if (gameNumber > nCmailGames) {
12327         DisplayError(_("No more games in this message"), 0);
12328         return FALSE;
12329     }
12330     if (f == lastLoadGameFP) {
12331         int offset = gameNumber - lastLoadGameNumber;
12332         if (offset == 0) {
12333             cmailMsg[0] = NULLCHAR;
12334             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12335                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12336                 nCmailMovesRegistered--;
12337             }
12338             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12339             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12340                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12341             }
12342         } else {
12343             if (! RegisterMove()) return FALSE;
12344         }
12345     }
12346
12347     retVal = LoadGame(f, gameNumber, title, useList);
12348
12349     /* Make move registered during previous look at this game, if any */
12350     MakeRegisteredMove();
12351
12352     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12353         commentList[currentMove]
12354           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12355         DisplayComment(currentMove - 1, commentList[currentMove]);
12356     }
12357
12358     return retVal;
12359 }
12360
12361 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12362 int
12363 ReloadGame (int offset)
12364 {
12365     int gameNumber = lastLoadGameNumber + offset;
12366     if (lastLoadGameFP == NULL) {
12367         DisplayError(_("No game has been loaded yet"), 0);
12368         return FALSE;
12369     }
12370     if (gameNumber <= 0) {
12371         DisplayError(_("Can't back up any further"), 0);
12372         return FALSE;
12373     }
12374     if (cmailMsgLoaded) {
12375         return CmailLoadGame(lastLoadGameFP, gameNumber,
12376                              lastLoadGameTitle, lastLoadGameUseList);
12377     } else {
12378         return LoadGame(lastLoadGameFP, gameNumber,
12379                         lastLoadGameTitle, lastLoadGameUseList);
12380     }
12381 }
12382
12383 int keys[EmptySquare+1];
12384
12385 int
12386 PositionMatches (Board b1, Board b2)
12387 {
12388     int r, f, sum=0;
12389     switch(appData.searchMode) {
12390         case 1: return CompareWithRights(b1, b2);
12391         case 2:
12392             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12393                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12394             }
12395             return TRUE;
12396         case 3:
12397             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12398               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12399                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12400             }
12401             return sum==0;
12402         case 4:
12403             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12404                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12405             }
12406             return sum==0;
12407     }
12408     return TRUE;
12409 }
12410
12411 #define Q_PROMO  4
12412 #define Q_EP     3
12413 #define Q_BCASTL 2
12414 #define Q_WCASTL 1
12415
12416 int pieceList[256], quickBoard[256];
12417 ChessSquare pieceType[256] = { EmptySquare };
12418 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12419 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12420 int soughtTotal, turn;
12421 Boolean epOK, flipSearch;
12422
12423 typedef struct {
12424     unsigned char piece, to;
12425 } Move;
12426
12427 #define DSIZE (250000)
12428
12429 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12430 Move *moveDatabase = initialSpace;
12431 unsigned int movePtr, dataSize = DSIZE;
12432
12433 int
12434 MakePieceList (Board board, int *counts)
12435 {
12436     int r, f, n=Q_PROMO, total=0;
12437     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12438     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12439         int sq = f + (r<<4);
12440         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12441             quickBoard[sq] = ++n;
12442             pieceList[n] = sq;
12443             pieceType[n] = board[r][f];
12444             counts[board[r][f]]++;
12445             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12446             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12447             total++;
12448         }
12449     }
12450     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12451     return total;
12452 }
12453
12454 void
12455 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12456 {
12457     int sq = fromX + (fromY<<4);
12458     int piece = quickBoard[sq], rook;
12459     quickBoard[sq] = 0;
12460     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12461     if(piece == pieceList[1] && fromY == toY) {
12462       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12463         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12464         moveDatabase[movePtr++].piece = Q_WCASTL;
12465         quickBoard[sq] = piece;
12466         piece = quickBoard[from]; quickBoard[from] = 0;
12467         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12468       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12469         quickBoard[sq] = 0; // remove Rook
12470         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12471         moveDatabase[movePtr++].piece = Q_WCASTL;
12472         quickBoard[sq] = pieceList[1]; // put King
12473         piece = rook;
12474         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12475       }
12476     } else
12477     if(piece == pieceList[2] && fromY == toY) {
12478       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12479         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12480         moveDatabase[movePtr++].piece = Q_BCASTL;
12481         quickBoard[sq] = piece;
12482         piece = quickBoard[from]; quickBoard[from] = 0;
12483         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12484       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12485         quickBoard[sq] = 0; // remove Rook
12486         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12487         moveDatabase[movePtr++].piece = Q_BCASTL;
12488         quickBoard[sq] = pieceList[2]; // put King
12489         piece = rook;
12490         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12491       }
12492     } else
12493     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12494         quickBoard[(fromY<<4)+toX] = 0;
12495         moveDatabase[movePtr].piece = Q_EP;
12496         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12497         moveDatabase[movePtr].to = sq;
12498     } else
12499     if(promoPiece != pieceType[piece]) {
12500         moveDatabase[movePtr++].piece = Q_PROMO;
12501         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12502     }
12503     moveDatabase[movePtr].piece = piece;
12504     quickBoard[sq] = piece;
12505     movePtr++;
12506 }
12507
12508 int
12509 PackGame (Board board)
12510 {
12511     Move *newSpace = NULL;
12512     moveDatabase[movePtr].piece = 0; // terminate previous game
12513     if(movePtr > dataSize) {
12514         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12515         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12516         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12517         if(newSpace) {
12518             int i;
12519             Move *p = moveDatabase, *q = newSpace;
12520             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12521             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12522             moveDatabase = newSpace;
12523         } else { // calloc failed, we must be out of memory. Too bad...
12524             dataSize = 0; // prevent calloc events for all subsequent games
12525             return 0;     // and signal this one isn't cached
12526         }
12527     }
12528     movePtr++;
12529     MakePieceList(board, counts);
12530     return movePtr;
12531 }
12532
12533 int
12534 QuickCompare (Board board, int *minCounts, int *maxCounts)
12535 {   // compare according to search mode
12536     int r, f;
12537     switch(appData.searchMode)
12538     {
12539       case 1: // exact position match
12540         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12541         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12542             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12543         }
12544         break;
12545       case 2: // can have extra material on empty squares
12546         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12547             if(board[r][f] == EmptySquare) continue;
12548             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12549         }
12550         break;
12551       case 3: // material with exact Pawn structure
12552         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12553             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12554             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12555         } // fall through to material comparison
12556       case 4: // exact material
12557         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12558         break;
12559       case 6: // material range with given imbalance
12560         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12561         // fall through to range comparison
12562       case 5: // material range
12563         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12564     }
12565     return TRUE;
12566 }
12567
12568 int
12569 QuickScan (Board board, Move *move)
12570 {   // reconstruct game,and compare all positions in it
12571     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12572     do {
12573         int piece = move->piece;
12574         int to = move->to, from = pieceList[piece];
12575         if(found < 0) { // if already found just scan to game end for final piece count
12576           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12577            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12578            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12579                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12580             ) {
12581             static int lastCounts[EmptySquare+1];
12582             int i;
12583             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12584             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12585           } else stretch = 0;
12586           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12587           if(found >= 0 && !appData.minPieces) return found;
12588         }
12589         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12590           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12591           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12592             piece = (++move)->piece;
12593             from = pieceList[piece];
12594             counts[pieceType[piece]]--;
12595             pieceType[piece] = (ChessSquare) move->to;
12596             counts[move->to]++;
12597           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12598             counts[pieceType[quickBoard[to]]]--;
12599             quickBoard[to] = 0; total--;
12600             move++;
12601             continue;
12602           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12603             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12604             from  = pieceList[piece]; // so this must be King
12605             quickBoard[from] = 0;
12606             pieceList[piece] = to;
12607             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12608             quickBoard[from] = 0; // rook
12609             quickBoard[to] = piece;
12610             to = move->to; piece = move->piece;
12611             goto aftercastle;
12612           }
12613         }
12614         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12615         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12616         quickBoard[from] = 0;
12617       aftercastle:
12618         quickBoard[to] = piece;
12619         pieceList[piece] = to;
12620         cnt++; turn ^= 3;
12621         move++;
12622     } while(1);
12623 }
12624
12625 void
12626 InitSearch ()
12627 {
12628     int r, f;
12629     flipSearch = FALSE;
12630     CopyBoard(soughtBoard, boards[currentMove]);
12631     soughtTotal = MakePieceList(soughtBoard, maxSought);
12632     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12633     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12634     CopyBoard(reverseBoard, boards[currentMove]);
12635     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12636         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12637         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12638         reverseBoard[r][f] = piece;
12639     }
12640     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12641     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12642     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12643                  || (boards[currentMove][CASTLING][2] == NoRights ||
12644                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12645                  && (boards[currentMove][CASTLING][5] == NoRights ||
12646                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12647       ) {
12648         flipSearch = TRUE;
12649         CopyBoard(flipBoard, soughtBoard);
12650         CopyBoard(rotateBoard, reverseBoard);
12651         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12652             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12653             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12654         }
12655     }
12656     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12657     if(appData.searchMode >= 5) {
12658         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12659         MakePieceList(soughtBoard, minSought);
12660         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12661     }
12662     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12663         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12664 }
12665
12666 GameInfo dummyInfo;
12667 static int creatingBook;
12668
12669 int
12670 GameContainsPosition (FILE *f, ListGame *lg)
12671 {
12672     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12673     int fromX, fromY, toX, toY;
12674     char promoChar;
12675     static int initDone=FALSE;
12676
12677     // weed out games based on numerical tag comparison
12678     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12679     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12680     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12681     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12682     if(!initDone) {
12683         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12684         initDone = TRUE;
12685     }
12686     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12687     else CopyBoard(boards[scratch], initialPosition); // default start position
12688     if(lg->moves) {
12689         turn = btm + 1;
12690         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12691         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12692     }
12693     if(btm) plyNr++;
12694     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12695     fseek(f, lg->offset, 0);
12696     yynewfile(f);
12697     while(1) {
12698         yyboardindex = scratch;
12699         quickFlag = plyNr+1;
12700         next = Myylex();
12701         quickFlag = 0;
12702         switch(next) {
12703             case PGNTag:
12704                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12705             default:
12706                 continue;
12707
12708             case XBoardGame:
12709             case GNUChessGame:
12710                 if(plyNr) return -1; // after we have seen moves, this is for new game
12711               continue;
12712
12713             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12714             case ImpossibleMove:
12715             case WhiteWins: // game ends here with these four
12716             case BlackWins:
12717             case GameIsDrawn:
12718             case GameUnfinished:
12719                 return -1;
12720
12721             case IllegalMove:
12722                 if(appData.testLegality) return -1;
12723             case WhiteCapturesEnPassant:
12724             case BlackCapturesEnPassant:
12725             case WhitePromotion:
12726             case BlackPromotion:
12727             case WhiteNonPromotion:
12728             case BlackNonPromotion:
12729             case NormalMove:
12730             case FirstLeg:
12731             case WhiteKingSideCastle:
12732             case WhiteQueenSideCastle:
12733             case BlackKingSideCastle:
12734             case BlackQueenSideCastle:
12735             case WhiteKingSideCastleWild:
12736             case WhiteQueenSideCastleWild:
12737             case BlackKingSideCastleWild:
12738             case BlackQueenSideCastleWild:
12739             case WhiteHSideCastleFR:
12740             case WhiteASideCastleFR:
12741             case BlackHSideCastleFR:
12742             case BlackASideCastleFR:
12743                 fromX = currentMoveString[0] - AAA;
12744                 fromY = currentMoveString[1] - ONE;
12745                 toX = currentMoveString[2] - AAA;
12746                 toY = currentMoveString[3] - ONE;
12747                 promoChar = currentMoveString[4];
12748                 break;
12749             case WhiteDrop:
12750             case BlackDrop:
12751                 fromX = next == WhiteDrop ?
12752                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12753                   (int) CharToPiece(ToLower(currentMoveString[0]));
12754                 fromY = DROP_RANK;
12755                 toX = currentMoveString[2] - AAA;
12756                 toY = currentMoveString[3] - ONE;
12757                 promoChar = 0;
12758                 break;
12759         }
12760         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12761         plyNr++;
12762         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12763         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12764         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12765         if(appData.findMirror) {
12766             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12767             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12768         }
12769     }
12770 }
12771
12772 /* Load the nth game from open file f */
12773 int
12774 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12775 {
12776     ChessMove cm;
12777     char buf[MSG_SIZ];
12778     int gn = gameNumber;
12779     ListGame *lg = NULL;
12780     int numPGNTags = 0;
12781     int err, pos = -1;
12782     GameMode oldGameMode;
12783     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12784     char oldName[MSG_SIZ];
12785
12786     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12787
12788     if (appData.debugMode)
12789         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12790
12791     if (gameMode == Training )
12792         SetTrainingModeOff();
12793
12794     oldGameMode = gameMode;
12795     if (gameMode != BeginningOfGame) {
12796       Reset(FALSE, TRUE);
12797     }
12798     killX = killY = -1; // [HGM] lion: in case we did not Reset
12799
12800     gameFileFP = f;
12801     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12802         fclose(lastLoadGameFP);
12803     }
12804
12805     if (useList) {
12806         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12807
12808         if (lg) {
12809             fseek(f, lg->offset, 0);
12810             GameListHighlight(gameNumber);
12811             pos = lg->position;
12812             gn = 1;
12813         }
12814         else {
12815             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12816               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12817             else
12818             DisplayError(_("Game number out of range"), 0);
12819             return FALSE;
12820         }
12821     } else {
12822         GameListDestroy();
12823         if (fseek(f, 0, 0) == -1) {
12824             if (f == lastLoadGameFP ?
12825                 gameNumber == lastLoadGameNumber + 1 :
12826                 gameNumber == 1) {
12827                 gn = 1;
12828             } else {
12829                 DisplayError(_("Can't seek on game file"), 0);
12830                 return FALSE;
12831             }
12832         }
12833     }
12834     lastLoadGameFP = f;
12835     lastLoadGameNumber = gameNumber;
12836     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12837     lastLoadGameUseList = useList;
12838
12839     yynewfile(f);
12840
12841     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12842       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12843                 lg->gameInfo.black);
12844             DisplayTitle(buf);
12845     } else if (*title != NULLCHAR) {
12846         if (gameNumber > 1) {
12847           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12848             DisplayTitle(buf);
12849         } else {
12850             DisplayTitle(title);
12851         }
12852     }
12853
12854     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12855         gameMode = PlayFromGameFile;
12856         ModeHighlight();
12857     }
12858
12859     currentMove = forwardMostMove = backwardMostMove = 0;
12860     CopyBoard(boards[0], initialPosition);
12861     StopClocks();
12862
12863     /*
12864      * Skip the first gn-1 games in the file.
12865      * Also skip over anything that precedes an identifiable
12866      * start of game marker, to avoid being confused by
12867      * garbage at the start of the file.  Currently
12868      * recognized start of game markers are the move number "1",
12869      * the pattern "gnuchess .* game", the pattern
12870      * "^[#;%] [^ ]* game file", and a PGN tag block.
12871      * A game that starts with one of the latter two patterns
12872      * will also have a move number 1, possibly
12873      * following a position diagram.
12874      * 5-4-02: Let's try being more lenient and allowing a game to
12875      * start with an unnumbered move.  Does that break anything?
12876      */
12877     cm = lastLoadGameStart = EndOfFile;
12878     while (gn > 0) {
12879         yyboardindex = forwardMostMove;
12880         cm = (ChessMove) Myylex();
12881         switch (cm) {
12882           case EndOfFile:
12883             if (cmailMsgLoaded) {
12884                 nCmailGames = CMAIL_MAX_GAMES - gn;
12885             } else {
12886                 Reset(TRUE, TRUE);
12887                 DisplayError(_("Game not found in file"), 0);
12888             }
12889             return FALSE;
12890
12891           case GNUChessGame:
12892           case XBoardGame:
12893             gn--;
12894             lastLoadGameStart = cm;
12895             break;
12896
12897           case MoveNumberOne:
12898             switch (lastLoadGameStart) {
12899               case GNUChessGame:
12900               case XBoardGame:
12901               case PGNTag:
12902                 break;
12903               case MoveNumberOne:
12904               case EndOfFile:
12905                 gn--;           /* count this game */
12906                 lastLoadGameStart = cm;
12907                 break;
12908               default:
12909                 /* impossible */
12910                 break;
12911             }
12912             break;
12913
12914           case PGNTag:
12915             switch (lastLoadGameStart) {
12916               case GNUChessGame:
12917               case PGNTag:
12918               case MoveNumberOne:
12919               case EndOfFile:
12920                 gn--;           /* count this game */
12921                 lastLoadGameStart = cm;
12922                 break;
12923               case XBoardGame:
12924                 lastLoadGameStart = cm; /* game counted already */
12925                 break;
12926               default:
12927                 /* impossible */
12928                 break;
12929             }
12930             if (gn > 0) {
12931                 do {
12932                     yyboardindex = forwardMostMove;
12933                     cm = (ChessMove) Myylex();
12934                 } while (cm == PGNTag || cm == Comment);
12935             }
12936             break;
12937
12938           case WhiteWins:
12939           case BlackWins:
12940           case GameIsDrawn:
12941             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12942                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12943                     != CMAIL_OLD_RESULT) {
12944                     nCmailResults ++ ;
12945                     cmailResult[  CMAIL_MAX_GAMES
12946                                 - gn - 1] = CMAIL_OLD_RESULT;
12947                 }
12948             }
12949             break;
12950
12951           case NormalMove:
12952           case FirstLeg:
12953             /* Only a NormalMove can be at the start of a game
12954              * without a position diagram. */
12955             if (lastLoadGameStart == EndOfFile ) {
12956               gn--;
12957               lastLoadGameStart = MoveNumberOne;
12958             }
12959             break;
12960
12961           default:
12962             break;
12963         }
12964     }
12965
12966     if (appData.debugMode)
12967       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12968
12969     if (cm == XBoardGame) {
12970         /* Skip any header junk before position diagram and/or move 1 */
12971         for (;;) {
12972             yyboardindex = forwardMostMove;
12973             cm = (ChessMove) Myylex();
12974
12975             if (cm == EndOfFile ||
12976                 cm == GNUChessGame || cm == XBoardGame) {
12977                 /* Empty game; pretend end-of-file and handle later */
12978                 cm = EndOfFile;
12979                 break;
12980             }
12981
12982             if (cm == MoveNumberOne || cm == PositionDiagram ||
12983                 cm == PGNTag || cm == Comment)
12984               break;
12985         }
12986     } else if (cm == GNUChessGame) {
12987         if (gameInfo.event != NULL) {
12988             free(gameInfo.event);
12989         }
12990         gameInfo.event = StrSave(yy_text);
12991     }
12992
12993     startedFromSetupPosition = startedFromPositionFile; // [HGM]
12994     while (cm == PGNTag) {
12995         if (appData.debugMode)
12996           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12997         err = ParsePGNTag(yy_text, &gameInfo);
12998         if (!err) numPGNTags++;
12999
13000         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13001         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13002             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13003             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13004             InitPosition(TRUE);
13005             oldVariant = gameInfo.variant;
13006             if (appData.debugMode)
13007               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13008         }
13009
13010
13011         if (gameInfo.fen != NULL) {
13012           Board initial_position;
13013           startedFromSetupPosition = TRUE;
13014           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13015             Reset(TRUE, TRUE);
13016             DisplayError(_("Bad FEN position in file"), 0);
13017             return FALSE;
13018           }
13019           CopyBoard(boards[0], initial_position);
13020           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13021             CopyBoard(initialPosition, initial_position);
13022           if (blackPlaysFirst) {
13023             currentMove = forwardMostMove = backwardMostMove = 1;
13024             CopyBoard(boards[1], initial_position);
13025             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13026             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13027             timeRemaining[0][1] = whiteTimeRemaining;
13028             timeRemaining[1][1] = blackTimeRemaining;
13029             if (commentList[0] != NULL) {
13030               commentList[1] = commentList[0];
13031               commentList[0] = NULL;
13032             }
13033           } else {
13034             currentMove = forwardMostMove = backwardMostMove = 0;
13035           }
13036           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13037           {   int i;
13038               initialRulePlies = FENrulePlies;
13039               for( i=0; i< nrCastlingRights; i++ )
13040                   initialRights[i] = initial_position[CASTLING][i];
13041           }
13042           yyboardindex = forwardMostMove;
13043           free(gameInfo.fen);
13044           gameInfo.fen = NULL;
13045         }
13046
13047         yyboardindex = forwardMostMove;
13048         cm = (ChessMove) Myylex();
13049
13050         /* Handle comments interspersed among the tags */
13051         while (cm == Comment) {
13052             char *p;
13053             if (appData.debugMode)
13054               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13055             p = yy_text;
13056             AppendComment(currentMove, p, FALSE);
13057             yyboardindex = forwardMostMove;
13058             cm = (ChessMove) Myylex();
13059         }
13060     }
13061
13062     /* don't rely on existence of Event tag since if game was
13063      * pasted from clipboard the Event tag may not exist
13064      */
13065     if (numPGNTags > 0){
13066         char *tags;
13067         if (gameInfo.variant == VariantNormal) {
13068           VariantClass v = StringToVariant(gameInfo.event);
13069           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13070           if(v < VariantShogi) gameInfo.variant = v;
13071         }
13072         if (!matchMode) {
13073           if( appData.autoDisplayTags ) {
13074             tags = PGNTags(&gameInfo);
13075             TagsPopUp(tags, CmailMsg());
13076             free(tags);
13077           }
13078         }
13079     } else {
13080         /* Make something up, but don't display it now */
13081         SetGameInfo();
13082         TagsPopDown();
13083     }
13084
13085     if (cm == PositionDiagram) {
13086         int i, j;
13087         char *p;
13088         Board initial_position;
13089
13090         if (appData.debugMode)
13091           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13092
13093         if (!startedFromSetupPosition) {
13094             p = yy_text;
13095             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13096               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13097                 switch (*p) {
13098                   case '{':
13099                   case '[':
13100                   case '-':
13101                   case ' ':
13102                   case '\t':
13103                   case '\n':
13104                   case '\r':
13105                     break;
13106                   default:
13107                     initial_position[i][j++] = CharToPiece(*p);
13108                     break;
13109                 }
13110             while (*p == ' ' || *p == '\t' ||
13111                    *p == '\n' || *p == '\r') p++;
13112
13113             if (strncmp(p, "black", strlen("black"))==0)
13114               blackPlaysFirst = TRUE;
13115             else
13116               blackPlaysFirst = FALSE;
13117             startedFromSetupPosition = TRUE;
13118
13119             CopyBoard(boards[0], initial_position);
13120             if (blackPlaysFirst) {
13121                 currentMove = forwardMostMove = backwardMostMove = 1;
13122                 CopyBoard(boards[1], initial_position);
13123                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13124                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13125                 timeRemaining[0][1] = whiteTimeRemaining;
13126                 timeRemaining[1][1] = blackTimeRemaining;
13127                 if (commentList[0] != NULL) {
13128                     commentList[1] = commentList[0];
13129                     commentList[0] = NULL;
13130                 }
13131             } else {
13132                 currentMove = forwardMostMove = backwardMostMove = 0;
13133             }
13134         }
13135         yyboardindex = forwardMostMove;
13136         cm = (ChessMove) Myylex();
13137     }
13138
13139   if(!creatingBook) {
13140     if (first.pr == NoProc) {
13141         StartChessProgram(&first);
13142     }
13143     InitChessProgram(&first, FALSE);
13144     if(gameInfo.variant == VariantUnknown && *oldName) {
13145         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13146         gameInfo.variant = v;
13147     }
13148     SendToProgram("force\n", &first);
13149     if (startedFromSetupPosition) {
13150         SendBoard(&first, forwardMostMove);
13151     if (appData.debugMode) {
13152         fprintf(debugFP, "Load Game\n");
13153     }
13154         DisplayBothClocks();
13155     }
13156   }
13157
13158     /* [HGM] server: flag to write setup moves in broadcast file as one */
13159     loadFlag = appData.suppressLoadMoves;
13160
13161     while (cm == Comment) {
13162         char *p;
13163         if (appData.debugMode)
13164           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13165         p = yy_text;
13166         AppendComment(currentMove, p, FALSE);
13167         yyboardindex = forwardMostMove;
13168         cm = (ChessMove) Myylex();
13169     }
13170
13171     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13172         cm == WhiteWins || cm == BlackWins ||
13173         cm == GameIsDrawn || cm == GameUnfinished) {
13174         DisplayMessage("", _("No moves in game"));
13175         if (cmailMsgLoaded) {
13176             if (appData.debugMode)
13177               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13178             ClearHighlights();
13179             flipView = FALSE;
13180         }
13181         DrawPosition(FALSE, boards[currentMove]);
13182         DisplayBothClocks();
13183         gameMode = EditGame;
13184         ModeHighlight();
13185         gameFileFP = NULL;
13186         cmailOldMove = 0;
13187         return TRUE;
13188     }
13189
13190     // [HGM] PV info: routine tests if comment empty
13191     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13192         DisplayComment(currentMove - 1, commentList[currentMove]);
13193     }
13194     if (!matchMode && appData.timeDelay != 0)
13195       DrawPosition(FALSE, boards[currentMove]);
13196
13197     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13198       programStats.ok_to_send = 1;
13199     }
13200
13201     /* if the first token after the PGN tags is a move
13202      * and not move number 1, retrieve it from the parser
13203      */
13204     if (cm != MoveNumberOne)
13205         LoadGameOneMove(cm);
13206
13207     /* load the remaining moves from the file */
13208     while (LoadGameOneMove(EndOfFile)) {
13209       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13210       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13211     }
13212
13213     /* rewind to the start of the game */
13214     currentMove = backwardMostMove;
13215
13216     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13217
13218     if (oldGameMode == AnalyzeFile) {
13219       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13220       AnalyzeFileEvent();
13221     } else
13222     if (oldGameMode == AnalyzeMode) {
13223       AnalyzeFileEvent();
13224     }
13225
13226     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13227         long int w, b; // [HGM] adjourn: restore saved clock times
13228         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13229         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13230             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13231             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13232         }
13233     }
13234
13235     if(creatingBook) return TRUE;
13236     if (!matchMode && pos > 0) {
13237         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13238     } else
13239     if (matchMode || appData.timeDelay == 0) {
13240       ToEndEvent();
13241     } else if (appData.timeDelay > 0) {
13242       AutoPlayGameLoop();
13243     }
13244
13245     if (appData.debugMode)
13246         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13247
13248     loadFlag = 0; /* [HGM] true game starts */
13249     return TRUE;
13250 }
13251
13252 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13253 int
13254 ReloadPosition (int offset)
13255 {
13256     int positionNumber = lastLoadPositionNumber + offset;
13257     if (lastLoadPositionFP == NULL) {
13258         DisplayError(_("No position has been loaded yet"), 0);
13259         return FALSE;
13260     }
13261     if (positionNumber <= 0) {
13262         DisplayError(_("Can't back up any further"), 0);
13263         return FALSE;
13264     }
13265     return LoadPosition(lastLoadPositionFP, positionNumber,
13266                         lastLoadPositionTitle);
13267 }
13268
13269 /* Load the nth position from the given file */
13270 int
13271 LoadPositionFromFile (char *filename, int n, char *title)
13272 {
13273     FILE *f;
13274     char buf[MSG_SIZ];
13275
13276     if (strcmp(filename, "-") == 0) {
13277         return LoadPosition(stdin, n, "stdin");
13278     } else {
13279         f = fopen(filename, "rb");
13280         if (f == NULL) {
13281             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13282             DisplayError(buf, errno);
13283             return FALSE;
13284         } else {
13285             return LoadPosition(f, n, title);
13286         }
13287     }
13288 }
13289
13290 /* Load the nth position from the given open file, and close it */
13291 int
13292 LoadPosition (FILE *f, int positionNumber, char *title)
13293 {
13294     char *p, line[MSG_SIZ];
13295     Board initial_position;
13296     int i, j, fenMode, pn;
13297
13298     if (gameMode == Training )
13299         SetTrainingModeOff();
13300
13301     if (gameMode != BeginningOfGame) {
13302         Reset(FALSE, TRUE);
13303     }
13304     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13305         fclose(lastLoadPositionFP);
13306     }
13307     if (positionNumber == 0) positionNumber = 1;
13308     lastLoadPositionFP = f;
13309     lastLoadPositionNumber = positionNumber;
13310     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13311     if (first.pr == NoProc && !appData.noChessProgram) {
13312       StartChessProgram(&first);
13313       InitChessProgram(&first, FALSE);
13314     }
13315     pn = positionNumber;
13316     if (positionNumber < 0) {
13317         /* Negative position number means to seek to that byte offset */
13318         if (fseek(f, -positionNumber, 0) == -1) {
13319             DisplayError(_("Can't seek on position file"), 0);
13320             return FALSE;
13321         };
13322         pn = 1;
13323     } else {
13324         if (fseek(f, 0, 0) == -1) {
13325             if (f == lastLoadPositionFP ?
13326                 positionNumber == lastLoadPositionNumber + 1 :
13327                 positionNumber == 1) {
13328                 pn = 1;
13329             } else {
13330                 DisplayError(_("Can't seek on position file"), 0);
13331                 return FALSE;
13332             }
13333         }
13334     }
13335     /* See if this file is FEN or old-style xboard */
13336     if (fgets(line, MSG_SIZ, f) == NULL) {
13337         DisplayError(_("Position not found in file"), 0);
13338         return FALSE;
13339     }
13340     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13341     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13342
13343     if (pn >= 2) {
13344         if (fenMode || line[0] == '#') pn--;
13345         while (pn > 0) {
13346             /* skip positions before number pn */
13347             if (fgets(line, MSG_SIZ, f) == NULL) {
13348                 Reset(TRUE, TRUE);
13349                 DisplayError(_("Position not found in file"), 0);
13350                 return FALSE;
13351             }
13352             if (fenMode || line[0] == '#') pn--;
13353         }
13354     }
13355
13356     if (fenMode) {
13357         char *p;
13358         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13359             DisplayError(_("Bad FEN position in file"), 0);
13360             return FALSE;
13361         }
13362         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13363             sscanf(p+3, "%s", bestMove);
13364         } else *bestMove = NULLCHAR;
13365     } else {
13366         (void) fgets(line, MSG_SIZ, f);
13367         (void) fgets(line, MSG_SIZ, f);
13368
13369         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13370             (void) fgets(line, MSG_SIZ, f);
13371             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13372                 if (*p == ' ')
13373                   continue;
13374                 initial_position[i][j++] = CharToPiece(*p);
13375             }
13376         }
13377
13378         blackPlaysFirst = FALSE;
13379         if (!feof(f)) {
13380             (void) fgets(line, MSG_SIZ, f);
13381             if (strncmp(line, "black", strlen("black"))==0)
13382               blackPlaysFirst = TRUE;
13383         }
13384     }
13385     startedFromSetupPosition = TRUE;
13386
13387     CopyBoard(boards[0], initial_position);
13388     if (blackPlaysFirst) {
13389         currentMove = forwardMostMove = backwardMostMove = 1;
13390         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13391         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13392         CopyBoard(boards[1], initial_position);
13393         DisplayMessage("", _("Black to play"));
13394     } else {
13395         currentMove = forwardMostMove = backwardMostMove = 0;
13396         DisplayMessage("", _("White to play"));
13397     }
13398     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13399     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13400         SendToProgram("force\n", &first);
13401         SendBoard(&first, forwardMostMove);
13402     }
13403     if (appData.debugMode) {
13404 int i, j;
13405   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13406   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13407         fprintf(debugFP, "Load Position\n");
13408     }
13409
13410     if (positionNumber > 1) {
13411       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13412         DisplayTitle(line);
13413     } else {
13414         DisplayTitle(title);
13415     }
13416     gameMode = EditGame;
13417     ModeHighlight();
13418     ResetClocks();
13419     timeRemaining[0][1] = whiteTimeRemaining;
13420     timeRemaining[1][1] = blackTimeRemaining;
13421     DrawPosition(FALSE, boards[currentMove]);
13422
13423     return TRUE;
13424 }
13425
13426
13427 void
13428 CopyPlayerNameIntoFileName (char **dest, char *src)
13429 {
13430     while (*src != NULLCHAR && *src != ',') {
13431         if (*src == ' ') {
13432             *(*dest)++ = '_';
13433             src++;
13434         } else {
13435             *(*dest)++ = *src++;
13436         }
13437     }
13438 }
13439
13440 char *
13441 DefaultFileName (char *ext)
13442 {
13443     static char def[MSG_SIZ];
13444     char *p;
13445
13446     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13447         p = def;
13448         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13449         *p++ = '-';
13450         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13451         *p++ = '.';
13452         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13453     } else {
13454         def[0] = NULLCHAR;
13455     }
13456     return def;
13457 }
13458
13459 /* Save the current game to the given file */
13460 int
13461 SaveGameToFile (char *filename, int append)
13462 {
13463     FILE *f;
13464     char buf[MSG_SIZ];
13465     int result, i, t,tot=0;
13466
13467     if (strcmp(filename, "-") == 0) {
13468         return SaveGame(stdout, 0, NULL);
13469     } else {
13470         for(i=0; i<10; i++) { // upto 10 tries
13471              f = fopen(filename, append ? "a" : "w");
13472              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13473              if(f || errno != 13) break;
13474              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13475              tot += t;
13476         }
13477         if (f == NULL) {
13478             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13479             DisplayError(buf, errno);
13480             return FALSE;
13481         } else {
13482             safeStrCpy(buf, lastMsg, MSG_SIZ);
13483             DisplayMessage(_("Waiting for access to save file"), "");
13484             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13485             DisplayMessage(_("Saving game"), "");
13486             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13487             result = SaveGame(f, 0, NULL);
13488             DisplayMessage(buf, "");
13489             return result;
13490         }
13491     }
13492 }
13493
13494 char *
13495 SavePart (char *str)
13496 {
13497     static char buf[MSG_SIZ];
13498     char *p;
13499
13500     p = strchr(str, ' ');
13501     if (p == NULL) return str;
13502     strncpy(buf, str, p - str);
13503     buf[p - str] = NULLCHAR;
13504     return buf;
13505 }
13506
13507 #define PGN_MAX_LINE 75
13508
13509 #define PGN_SIDE_WHITE  0
13510 #define PGN_SIDE_BLACK  1
13511
13512 static int
13513 FindFirstMoveOutOfBook (int side)
13514 {
13515     int result = -1;
13516
13517     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13518         int index = backwardMostMove;
13519         int has_book_hit = 0;
13520
13521         if( (index % 2) != side ) {
13522             index++;
13523         }
13524
13525         while( index < forwardMostMove ) {
13526             /* Check to see if engine is in book */
13527             int depth = pvInfoList[index].depth;
13528             int score = pvInfoList[index].score;
13529             int in_book = 0;
13530
13531             if( depth <= 2 ) {
13532                 in_book = 1;
13533             }
13534             else if( score == 0 && depth == 63 ) {
13535                 in_book = 1; /* Zappa */
13536             }
13537             else if( score == 2 && depth == 99 ) {
13538                 in_book = 1; /* Abrok */
13539             }
13540
13541             has_book_hit += in_book;
13542
13543             if( ! in_book ) {
13544                 result = index;
13545
13546                 break;
13547             }
13548
13549             index += 2;
13550         }
13551     }
13552
13553     return result;
13554 }
13555
13556 void
13557 GetOutOfBookInfo (char * buf)
13558 {
13559     int oob[2];
13560     int i;
13561     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13562
13563     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13564     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13565
13566     *buf = '\0';
13567
13568     if( oob[0] >= 0 || oob[1] >= 0 ) {
13569         for( i=0; i<2; i++ ) {
13570             int idx = oob[i];
13571
13572             if( idx >= 0 ) {
13573                 if( i > 0 && oob[0] >= 0 ) {
13574                     strcat( buf, "   " );
13575                 }
13576
13577                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13578                 sprintf( buf+strlen(buf), "%s%.2f",
13579                     pvInfoList[idx].score >= 0 ? "+" : "",
13580                     pvInfoList[idx].score / 100.0 );
13581             }
13582         }
13583     }
13584 }
13585
13586 /* Save game in PGN style */
13587 static void
13588 SaveGamePGN2 (FILE *f)
13589 {
13590     int i, offset, linelen, newblock;
13591 //    char *movetext;
13592     char numtext[32];
13593     int movelen, numlen, blank;
13594     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13595
13596     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13597
13598     PrintPGNTags(f, &gameInfo);
13599
13600     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13601
13602     if (backwardMostMove > 0 || startedFromSetupPosition) {
13603         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13604         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13605         fprintf(f, "\n{--------------\n");
13606         PrintPosition(f, backwardMostMove);
13607         fprintf(f, "--------------}\n");
13608         free(fen);
13609     }
13610     else {
13611         /* [AS] Out of book annotation */
13612         if( appData.saveOutOfBookInfo ) {
13613             char buf[64];
13614
13615             GetOutOfBookInfo( buf );
13616
13617             if( buf[0] != '\0' ) {
13618                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13619             }
13620         }
13621
13622         fprintf(f, "\n");
13623     }
13624
13625     i = backwardMostMove;
13626     linelen = 0;
13627     newblock = TRUE;
13628
13629     while (i < forwardMostMove) {
13630         /* Print comments preceding this move */
13631         if (commentList[i] != NULL) {
13632             if (linelen > 0) fprintf(f, "\n");
13633             fprintf(f, "%s", commentList[i]);
13634             linelen = 0;
13635             newblock = TRUE;
13636         }
13637
13638         /* Format move number */
13639         if ((i % 2) == 0)
13640           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13641         else
13642           if (newblock)
13643             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13644           else
13645             numtext[0] = NULLCHAR;
13646
13647         numlen = strlen(numtext);
13648         newblock = FALSE;
13649
13650         /* Print move number */
13651         blank = linelen > 0 && numlen > 0;
13652         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13653             fprintf(f, "\n");
13654             linelen = 0;
13655             blank = 0;
13656         }
13657         if (blank) {
13658             fprintf(f, " ");
13659             linelen++;
13660         }
13661         fprintf(f, "%s", numtext);
13662         linelen += numlen;
13663
13664         /* Get move */
13665         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13666         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13667
13668         /* Print move */
13669         blank = linelen > 0 && movelen > 0;
13670         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13671             fprintf(f, "\n");
13672             linelen = 0;
13673             blank = 0;
13674         }
13675         if (blank) {
13676             fprintf(f, " ");
13677             linelen++;
13678         }
13679         fprintf(f, "%s", move_buffer);
13680         linelen += movelen;
13681
13682         /* [AS] Add PV info if present */
13683         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13684             /* [HGM] add time */
13685             char buf[MSG_SIZ]; int seconds;
13686
13687             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13688
13689             if( seconds <= 0)
13690               buf[0] = 0;
13691             else
13692               if( seconds < 30 )
13693                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13694               else
13695                 {
13696                   seconds = (seconds + 4)/10; // round to full seconds
13697                   if( seconds < 60 )
13698                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13699                   else
13700                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13701                 }
13702
13703             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13704                       pvInfoList[i].score >= 0 ? "+" : "",
13705                       pvInfoList[i].score / 100.0,
13706                       pvInfoList[i].depth,
13707                       buf );
13708
13709             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13710
13711             /* Print score/depth */
13712             blank = linelen > 0 && movelen > 0;
13713             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13714                 fprintf(f, "\n");
13715                 linelen = 0;
13716                 blank = 0;
13717             }
13718             if (blank) {
13719                 fprintf(f, " ");
13720                 linelen++;
13721             }
13722             fprintf(f, "%s", move_buffer);
13723             linelen += movelen;
13724         }
13725
13726         i++;
13727     }
13728
13729     /* Start a new line */
13730     if (linelen > 0) fprintf(f, "\n");
13731
13732     /* Print comments after last move */
13733     if (commentList[i] != NULL) {
13734         fprintf(f, "%s\n", commentList[i]);
13735     }
13736
13737     /* Print result */
13738     if (gameInfo.resultDetails != NULL &&
13739         gameInfo.resultDetails[0] != NULLCHAR) {
13740         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13741         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13742            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13743             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13744         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13745     } else {
13746         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13747     }
13748 }
13749
13750 /* Save game in PGN style and close the file */
13751 int
13752 SaveGamePGN (FILE *f)
13753 {
13754     SaveGamePGN2(f);
13755     fclose(f);
13756     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13757     return TRUE;
13758 }
13759
13760 /* Save game in old style and close the file */
13761 int
13762 SaveGameOldStyle (FILE *f)
13763 {
13764     int i, offset;
13765     time_t tm;
13766
13767     tm = time((time_t *) NULL);
13768
13769     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13770     PrintOpponents(f);
13771
13772     if (backwardMostMove > 0 || startedFromSetupPosition) {
13773         fprintf(f, "\n[--------------\n");
13774         PrintPosition(f, backwardMostMove);
13775         fprintf(f, "--------------]\n");
13776     } else {
13777         fprintf(f, "\n");
13778     }
13779
13780     i = backwardMostMove;
13781     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13782
13783     while (i < forwardMostMove) {
13784         if (commentList[i] != NULL) {
13785             fprintf(f, "[%s]\n", commentList[i]);
13786         }
13787
13788         if ((i % 2) == 1) {
13789             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13790             i++;
13791         } else {
13792             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13793             i++;
13794             if (commentList[i] != NULL) {
13795                 fprintf(f, "\n");
13796                 continue;
13797             }
13798             if (i >= forwardMostMove) {
13799                 fprintf(f, "\n");
13800                 break;
13801             }
13802             fprintf(f, "%s\n", parseList[i]);
13803             i++;
13804         }
13805     }
13806
13807     if (commentList[i] != NULL) {
13808         fprintf(f, "[%s]\n", commentList[i]);
13809     }
13810
13811     /* This isn't really the old style, but it's close enough */
13812     if (gameInfo.resultDetails != NULL &&
13813         gameInfo.resultDetails[0] != NULLCHAR) {
13814         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13815                 gameInfo.resultDetails);
13816     } else {
13817         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13818     }
13819
13820     fclose(f);
13821     return TRUE;
13822 }
13823
13824 /* Save the current game to open file f and close the file */
13825 int
13826 SaveGame (FILE *f, int dummy, char *dummy2)
13827 {
13828     if (gameMode == EditPosition) EditPositionDone(TRUE);
13829     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13830     if (appData.oldSaveStyle)
13831       return SaveGameOldStyle(f);
13832     else
13833       return SaveGamePGN(f);
13834 }
13835
13836 /* Save the current position to the given file */
13837 int
13838 SavePositionToFile (char *filename)
13839 {
13840     FILE *f;
13841     char buf[MSG_SIZ];
13842
13843     if (strcmp(filename, "-") == 0) {
13844         return SavePosition(stdout, 0, NULL);
13845     } else {
13846         f = fopen(filename, "a");
13847         if (f == NULL) {
13848             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13849             DisplayError(buf, errno);
13850             return FALSE;
13851         } else {
13852             safeStrCpy(buf, lastMsg, MSG_SIZ);
13853             DisplayMessage(_("Waiting for access to save file"), "");
13854             flock(fileno(f), LOCK_EX); // [HGM] lock
13855             DisplayMessage(_("Saving position"), "");
13856             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13857             SavePosition(f, 0, NULL);
13858             DisplayMessage(buf, "");
13859             return TRUE;
13860         }
13861     }
13862 }
13863
13864 /* Save the current position to the given open file and close the file */
13865 int
13866 SavePosition (FILE *f, int dummy, char *dummy2)
13867 {
13868     time_t tm;
13869     char *fen;
13870
13871     if (gameMode == EditPosition) EditPositionDone(TRUE);
13872     if (appData.oldSaveStyle) {
13873         tm = time((time_t *) NULL);
13874
13875         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13876         PrintOpponents(f);
13877         fprintf(f, "[--------------\n");
13878         PrintPosition(f, currentMove);
13879         fprintf(f, "--------------]\n");
13880     } else {
13881         fen = PositionToFEN(currentMove, NULL, 1);
13882         fprintf(f, "%s\n", fen);
13883         free(fen);
13884     }
13885     fclose(f);
13886     return TRUE;
13887 }
13888
13889 void
13890 ReloadCmailMsgEvent (int unregister)
13891 {
13892 #if !WIN32
13893     static char *inFilename = NULL;
13894     static char *outFilename;
13895     int i;
13896     struct stat inbuf, outbuf;
13897     int status;
13898
13899     /* Any registered moves are unregistered if unregister is set, */
13900     /* i.e. invoked by the signal handler */
13901     if (unregister) {
13902         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13903             cmailMoveRegistered[i] = FALSE;
13904             if (cmailCommentList[i] != NULL) {
13905                 free(cmailCommentList[i]);
13906                 cmailCommentList[i] = NULL;
13907             }
13908         }
13909         nCmailMovesRegistered = 0;
13910     }
13911
13912     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13913         cmailResult[i] = CMAIL_NOT_RESULT;
13914     }
13915     nCmailResults = 0;
13916
13917     if (inFilename == NULL) {
13918         /* Because the filenames are static they only get malloced once  */
13919         /* and they never get freed                                      */
13920         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13921         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13922
13923         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13924         sprintf(outFilename, "%s.out", appData.cmailGameName);
13925     }
13926
13927     status = stat(outFilename, &outbuf);
13928     if (status < 0) {
13929         cmailMailedMove = FALSE;
13930     } else {
13931         status = stat(inFilename, &inbuf);
13932         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13933     }
13934
13935     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13936        counts the games, notes how each one terminated, etc.
13937
13938        It would be nice to remove this kludge and instead gather all
13939        the information while building the game list.  (And to keep it
13940        in the game list nodes instead of having a bunch of fixed-size
13941        parallel arrays.)  Note this will require getting each game's
13942        termination from the PGN tags, as the game list builder does
13943        not process the game moves.  --mann
13944        */
13945     cmailMsgLoaded = TRUE;
13946     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13947
13948     /* Load first game in the file or popup game menu */
13949     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13950
13951 #endif /* !WIN32 */
13952     return;
13953 }
13954
13955 int
13956 RegisterMove ()
13957 {
13958     FILE *f;
13959     char string[MSG_SIZ];
13960
13961     if (   cmailMailedMove
13962         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13963         return TRUE;            /* Allow free viewing  */
13964     }
13965
13966     /* Unregister move to ensure that we don't leave RegisterMove        */
13967     /* with the move registered when the conditions for registering no   */
13968     /* longer hold                                                       */
13969     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13970         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13971         nCmailMovesRegistered --;
13972
13973         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13974           {
13975               free(cmailCommentList[lastLoadGameNumber - 1]);
13976               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13977           }
13978     }
13979
13980     if (cmailOldMove == -1) {
13981         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13982         return FALSE;
13983     }
13984
13985     if (currentMove > cmailOldMove + 1) {
13986         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13987         return FALSE;
13988     }
13989
13990     if (currentMove < cmailOldMove) {
13991         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13992         return FALSE;
13993     }
13994
13995     if (forwardMostMove > currentMove) {
13996         /* Silently truncate extra moves */
13997         TruncateGame();
13998     }
13999
14000     if (   (currentMove == cmailOldMove + 1)
14001         || (   (currentMove == cmailOldMove)
14002             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14003                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14004         if (gameInfo.result != GameUnfinished) {
14005             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14006         }
14007
14008         if (commentList[currentMove] != NULL) {
14009             cmailCommentList[lastLoadGameNumber - 1]
14010               = StrSave(commentList[currentMove]);
14011         }
14012         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14013
14014         if (appData.debugMode)
14015           fprintf(debugFP, "Saving %s for game %d\n",
14016                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14017
14018         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14019
14020         f = fopen(string, "w");
14021         if (appData.oldSaveStyle) {
14022             SaveGameOldStyle(f); /* also closes the file */
14023
14024             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14025             f = fopen(string, "w");
14026             SavePosition(f, 0, NULL); /* also closes the file */
14027         } else {
14028             fprintf(f, "{--------------\n");
14029             PrintPosition(f, currentMove);
14030             fprintf(f, "--------------}\n\n");
14031
14032             SaveGame(f, 0, NULL); /* also closes the file*/
14033         }
14034
14035         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14036         nCmailMovesRegistered ++;
14037     } else if (nCmailGames == 1) {
14038         DisplayError(_("You have not made a move yet"), 0);
14039         return FALSE;
14040     }
14041
14042     return TRUE;
14043 }
14044
14045 void
14046 MailMoveEvent ()
14047 {
14048 #if !WIN32
14049     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14050     FILE *commandOutput;
14051     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14052     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14053     int nBuffers;
14054     int i;
14055     int archived;
14056     char *arcDir;
14057
14058     if (! cmailMsgLoaded) {
14059         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14060         return;
14061     }
14062
14063     if (nCmailGames == nCmailResults) {
14064         DisplayError(_("No unfinished games"), 0);
14065         return;
14066     }
14067
14068 #if CMAIL_PROHIBIT_REMAIL
14069     if (cmailMailedMove) {
14070       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);
14071         DisplayError(msg, 0);
14072         return;
14073     }
14074 #endif
14075
14076     if (! (cmailMailedMove || RegisterMove())) return;
14077
14078     if (   cmailMailedMove
14079         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14080       snprintf(string, MSG_SIZ, partCommandString,
14081                appData.debugMode ? " -v" : "", appData.cmailGameName);
14082         commandOutput = popen(string, "r");
14083
14084         if (commandOutput == NULL) {
14085             DisplayError(_("Failed to invoke cmail"), 0);
14086         } else {
14087             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14088                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14089             }
14090             if (nBuffers > 1) {
14091                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14092                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14093                 nBytes = MSG_SIZ - 1;
14094             } else {
14095                 (void) memcpy(msg, buffer, nBytes);
14096             }
14097             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14098
14099             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14100                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14101
14102                 archived = TRUE;
14103                 for (i = 0; i < nCmailGames; i ++) {
14104                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14105                         archived = FALSE;
14106                     }
14107                 }
14108                 if (   archived
14109                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14110                         != NULL)) {
14111                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14112                            arcDir,
14113                            appData.cmailGameName,
14114                            gameInfo.date);
14115                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14116                     cmailMsgLoaded = FALSE;
14117                 }
14118             }
14119
14120             DisplayInformation(msg);
14121             pclose(commandOutput);
14122         }
14123     } else {
14124         if ((*cmailMsg) != '\0') {
14125             DisplayInformation(cmailMsg);
14126         }
14127     }
14128
14129     return;
14130 #endif /* !WIN32 */
14131 }
14132
14133 char *
14134 CmailMsg ()
14135 {
14136 #if WIN32
14137     return NULL;
14138 #else
14139     int  prependComma = 0;
14140     char number[5];
14141     char string[MSG_SIZ];       /* Space for game-list */
14142     int  i;
14143
14144     if (!cmailMsgLoaded) return "";
14145
14146     if (cmailMailedMove) {
14147       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14148     } else {
14149         /* Create a list of games left */
14150       snprintf(string, MSG_SIZ, "[");
14151         for (i = 0; i < nCmailGames; i ++) {
14152             if (! (   cmailMoveRegistered[i]
14153                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14154                 if (prependComma) {
14155                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14156                 } else {
14157                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14158                     prependComma = 1;
14159                 }
14160
14161                 strcat(string, number);
14162             }
14163         }
14164         strcat(string, "]");
14165
14166         if (nCmailMovesRegistered + nCmailResults == 0) {
14167             switch (nCmailGames) {
14168               case 1:
14169                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14170                 break;
14171
14172               case 2:
14173                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14174                 break;
14175
14176               default:
14177                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14178                          nCmailGames);
14179                 break;
14180             }
14181         } else {
14182             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14183               case 1:
14184                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14185                          string);
14186                 break;
14187
14188               case 0:
14189                 if (nCmailResults == nCmailGames) {
14190                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14191                 } else {
14192                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14193                 }
14194                 break;
14195
14196               default:
14197                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14198                          string);
14199             }
14200         }
14201     }
14202     return cmailMsg;
14203 #endif /* WIN32 */
14204 }
14205
14206 void
14207 ResetGameEvent ()
14208 {
14209     if (gameMode == Training)
14210       SetTrainingModeOff();
14211
14212     Reset(TRUE, TRUE);
14213     cmailMsgLoaded = FALSE;
14214     if (appData.icsActive) {
14215       SendToICS(ics_prefix);
14216       SendToICS("refresh\n");
14217     }
14218 }
14219
14220 void
14221 ExitEvent (int status)
14222 {
14223     exiting++;
14224     if (exiting > 2) {
14225       /* Give up on clean exit */
14226       exit(status);
14227     }
14228     if (exiting > 1) {
14229       /* Keep trying for clean exit */
14230       return;
14231     }
14232
14233     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14234     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14235
14236     if (telnetISR != NULL) {
14237       RemoveInputSource(telnetISR);
14238     }
14239     if (icsPR != NoProc) {
14240       DestroyChildProcess(icsPR, TRUE);
14241     }
14242
14243     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14244     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14245
14246     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14247     /* make sure this other one finishes before killing it!                  */
14248     if(endingGame) { int count = 0;
14249         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14250         while(endingGame && count++ < 10) DoSleep(1);
14251         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14252     }
14253
14254     /* Kill off chess programs */
14255     if (first.pr != NoProc) {
14256         ExitAnalyzeMode();
14257
14258         DoSleep( appData.delayBeforeQuit );
14259         SendToProgram("quit\n", &first);
14260         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14261     }
14262     if (second.pr != NoProc) {
14263         DoSleep( appData.delayBeforeQuit );
14264         SendToProgram("quit\n", &second);
14265         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14266     }
14267     if (first.isr != NULL) {
14268         RemoveInputSource(first.isr);
14269     }
14270     if (second.isr != NULL) {
14271         RemoveInputSource(second.isr);
14272     }
14273
14274     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14275     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14276
14277     ShutDownFrontEnd();
14278     exit(status);
14279 }
14280
14281 void
14282 PauseEngine (ChessProgramState *cps)
14283 {
14284     SendToProgram("pause\n", cps);
14285     cps->pause = 2;
14286 }
14287
14288 void
14289 UnPauseEngine (ChessProgramState *cps)
14290 {
14291     SendToProgram("resume\n", cps);
14292     cps->pause = 1;
14293 }
14294
14295 void
14296 PauseEvent ()
14297 {
14298     if (appData.debugMode)
14299         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14300     if (pausing) {
14301         pausing = FALSE;
14302         ModeHighlight();
14303         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14304             StartClocks();
14305             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14306                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14307                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14308             }
14309             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14310             HandleMachineMove(stashedInputMove, stalledEngine);
14311             stalledEngine = NULL;
14312             return;
14313         }
14314         if (gameMode == MachinePlaysWhite ||
14315             gameMode == TwoMachinesPlay   ||
14316             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14317             if(first.pause)  UnPauseEngine(&first);
14318             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14319             if(second.pause) UnPauseEngine(&second);
14320             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14321             StartClocks();
14322         } else {
14323             DisplayBothClocks();
14324         }
14325         if (gameMode == PlayFromGameFile) {
14326             if (appData.timeDelay >= 0)
14327                 AutoPlayGameLoop();
14328         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14329             Reset(FALSE, TRUE);
14330             SendToICS(ics_prefix);
14331             SendToICS("refresh\n");
14332         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14333             ForwardInner(forwardMostMove);
14334         }
14335         pauseExamInvalid = FALSE;
14336     } else {
14337         switch (gameMode) {
14338           default:
14339             return;
14340           case IcsExamining:
14341             pauseExamForwardMostMove = forwardMostMove;
14342             pauseExamInvalid = FALSE;
14343             /* fall through */
14344           case IcsObserving:
14345           case IcsPlayingWhite:
14346           case IcsPlayingBlack:
14347             pausing = TRUE;
14348             ModeHighlight();
14349             return;
14350           case PlayFromGameFile:
14351             (void) StopLoadGameTimer();
14352             pausing = TRUE;
14353             ModeHighlight();
14354             break;
14355           case BeginningOfGame:
14356             if (appData.icsActive) return;
14357             /* else fall through */
14358           case MachinePlaysWhite:
14359           case MachinePlaysBlack:
14360           case TwoMachinesPlay:
14361             if (forwardMostMove == 0)
14362               return;           /* don't pause if no one has moved */
14363             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14364                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14365                 if(onMove->pause) {           // thinking engine can be paused
14366                     PauseEngine(onMove);      // do it
14367                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14368                         PauseEngine(onMove->other);
14369                     else
14370                         SendToProgram("easy\n", onMove->other);
14371                     StopClocks();
14372                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14373             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14374                 if(first.pause) {
14375                     PauseEngine(&first);
14376                     StopClocks();
14377                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14378             } else { // human on move, pause pondering by either method
14379                 if(first.pause)
14380                     PauseEngine(&first);
14381                 else if(appData.ponderNextMove)
14382                     SendToProgram("easy\n", &first);
14383                 StopClocks();
14384             }
14385             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14386           case AnalyzeMode:
14387             pausing = TRUE;
14388             ModeHighlight();
14389             break;
14390         }
14391     }
14392 }
14393
14394 void
14395 EditCommentEvent ()
14396 {
14397     char title[MSG_SIZ];
14398
14399     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14400       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14401     } else {
14402       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14403                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14404                parseList[currentMove - 1]);
14405     }
14406
14407     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14408 }
14409
14410
14411 void
14412 EditTagsEvent ()
14413 {
14414     char *tags = PGNTags(&gameInfo);
14415     bookUp = FALSE;
14416     EditTagsPopUp(tags, NULL);
14417     free(tags);
14418 }
14419
14420 void
14421 ToggleSecond ()
14422 {
14423   if(second.analyzing) {
14424     SendToProgram("exit\n", &second);
14425     second.analyzing = FALSE;
14426   } else {
14427     if (second.pr == NoProc) StartChessProgram(&second);
14428     InitChessProgram(&second, FALSE);
14429     FeedMovesToProgram(&second, currentMove);
14430
14431     SendToProgram("analyze\n", &second);
14432     second.analyzing = TRUE;
14433   }
14434 }
14435
14436 /* Toggle ShowThinking */
14437 void
14438 ToggleShowThinking()
14439 {
14440   appData.showThinking = !appData.showThinking;
14441   ShowThinkingEvent();
14442 }
14443
14444 int
14445 AnalyzeModeEvent ()
14446 {
14447     char buf[MSG_SIZ];
14448
14449     if (!first.analysisSupport) {
14450       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14451       DisplayError(buf, 0);
14452       return 0;
14453     }
14454     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14455     if (appData.icsActive) {
14456         if (gameMode != IcsObserving) {
14457           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14458             DisplayError(buf, 0);
14459             /* secure check */
14460             if (appData.icsEngineAnalyze) {
14461                 if (appData.debugMode)
14462                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14463                 ExitAnalyzeMode();
14464                 ModeHighlight();
14465             }
14466             return 0;
14467         }
14468         /* if enable, user wants to disable icsEngineAnalyze */
14469         if (appData.icsEngineAnalyze) {
14470                 ExitAnalyzeMode();
14471                 ModeHighlight();
14472                 return 0;
14473         }
14474         appData.icsEngineAnalyze = TRUE;
14475         if (appData.debugMode)
14476             fprintf(debugFP, "ICS engine analyze starting... \n");
14477     }
14478
14479     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14480     if (appData.noChessProgram || gameMode == AnalyzeMode)
14481       return 0;
14482
14483     if (gameMode != AnalyzeFile) {
14484         if (!appData.icsEngineAnalyze) {
14485                EditGameEvent();
14486                if (gameMode != EditGame) return 0;
14487         }
14488         if (!appData.showThinking) ToggleShowThinking();
14489         ResurrectChessProgram();
14490         SendToProgram("analyze\n", &first);
14491         first.analyzing = TRUE;
14492         /*first.maybeThinking = TRUE;*/
14493         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14494         EngineOutputPopUp();
14495     }
14496     if (!appData.icsEngineAnalyze) {
14497         gameMode = AnalyzeMode;
14498         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14499     }
14500     pausing = FALSE;
14501     ModeHighlight();
14502     SetGameInfo();
14503
14504     StartAnalysisClock();
14505     GetTimeMark(&lastNodeCountTime);
14506     lastNodeCount = 0;
14507     return 1;
14508 }
14509
14510 void
14511 AnalyzeFileEvent ()
14512 {
14513     if (appData.noChessProgram || gameMode == AnalyzeFile)
14514       return;
14515
14516     if (!first.analysisSupport) {
14517       char buf[MSG_SIZ];
14518       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14519       DisplayError(buf, 0);
14520       return;
14521     }
14522
14523     if (gameMode != AnalyzeMode) {
14524         keepInfo = 1; // mere annotating should not alter PGN tags
14525         EditGameEvent();
14526         keepInfo = 0;
14527         if (gameMode != EditGame) return;
14528         if (!appData.showThinking) ToggleShowThinking();
14529         ResurrectChessProgram();
14530         SendToProgram("analyze\n", &first);
14531         first.analyzing = TRUE;
14532         /*first.maybeThinking = TRUE;*/
14533         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14534         EngineOutputPopUp();
14535     }
14536     gameMode = AnalyzeFile;
14537     pausing = FALSE;
14538     ModeHighlight();
14539
14540     StartAnalysisClock();
14541     GetTimeMark(&lastNodeCountTime);
14542     lastNodeCount = 0;
14543     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14544     AnalysisPeriodicEvent(1);
14545 }
14546
14547 void
14548 MachineWhiteEvent ()
14549 {
14550     char buf[MSG_SIZ];
14551     char *bookHit = NULL;
14552
14553     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14554       return;
14555
14556
14557     if (gameMode == PlayFromGameFile ||
14558         gameMode == TwoMachinesPlay  ||
14559         gameMode == Training         ||
14560         gameMode == AnalyzeMode      ||
14561         gameMode == EndOfGame)
14562         EditGameEvent();
14563
14564     if (gameMode == EditPosition)
14565         EditPositionDone(TRUE);
14566
14567     if (!WhiteOnMove(currentMove)) {
14568         DisplayError(_("It is not White's turn"), 0);
14569         return;
14570     }
14571
14572     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14573       ExitAnalyzeMode();
14574
14575     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14576         gameMode == AnalyzeFile)
14577         TruncateGame();
14578
14579     ResurrectChessProgram();    /* in case it isn't running */
14580     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14581         gameMode = MachinePlaysWhite;
14582         ResetClocks();
14583     } else
14584     gameMode = MachinePlaysWhite;
14585     pausing = FALSE;
14586     ModeHighlight();
14587     SetGameInfo();
14588     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14589     DisplayTitle(buf);
14590     if (first.sendName) {
14591       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14592       SendToProgram(buf, &first);
14593     }
14594     if (first.sendTime) {
14595       if (first.useColors) {
14596         SendToProgram("black\n", &first); /*gnu kludge*/
14597       }
14598       SendTimeRemaining(&first, TRUE);
14599     }
14600     if (first.useColors) {
14601       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14602     }
14603     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14604     SetMachineThinkingEnables();
14605     first.maybeThinking = TRUE;
14606     StartClocks();
14607     firstMove = FALSE;
14608
14609     if (appData.autoFlipView && !flipView) {
14610       flipView = !flipView;
14611       DrawPosition(FALSE, NULL);
14612       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14613     }
14614
14615     if(bookHit) { // [HGM] book: simulate book reply
14616         static char bookMove[MSG_SIZ]; // a bit generous?
14617
14618         programStats.nodes = programStats.depth = programStats.time =
14619         programStats.score = programStats.got_only_move = 0;
14620         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14621
14622         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14623         strcat(bookMove, bookHit);
14624         HandleMachineMove(bookMove, &first);
14625     }
14626 }
14627
14628 void
14629 MachineBlackEvent ()
14630 {
14631   char buf[MSG_SIZ];
14632   char *bookHit = NULL;
14633
14634     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14635         return;
14636
14637
14638     if (gameMode == PlayFromGameFile ||
14639         gameMode == TwoMachinesPlay  ||
14640         gameMode == Training         ||
14641         gameMode == AnalyzeMode      ||
14642         gameMode == EndOfGame)
14643         EditGameEvent();
14644
14645     if (gameMode == EditPosition)
14646         EditPositionDone(TRUE);
14647
14648     if (WhiteOnMove(currentMove)) {
14649         DisplayError(_("It is not Black's turn"), 0);
14650         return;
14651     }
14652
14653     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14654       ExitAnalyzeMode();
14655
14656     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14657         gameMode == AnalyzeFile)
14658         TruncateGame();
14659
14660     ResurrectChessProgram();    /* in case it isn't running */
14661     gameMode = MachinePlaysBlack;
14662     pausing = FALSE;
14663     ModeHighlight();
14664     SetGameInfo();
14665     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14666     DisplayTitle(buf);
14667     if (first.sendName) {
14668       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14669       SendToProgram(buf, &first);
14670     }
14671     if (first.sendTime) {
14672       if (first.useColors) {
14673         SendToProgram("white\n", &first); /*gnu kludge*/
14674       }
14675       SendTimeRemaining(&first, FALSE);
14676     }
14677     if (first.useColors) {
14678       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14679     }
14680     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14681     SetMachineThinkingEnables();
14682     first.maybeThinking = TRUE;
14683     StartClocks();
14684
14685     if (appData.autoFlipView && flipView) {
14686       flipView = !flipView;
14687       DrawPosition(FALSE, NULL);
14688       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14689     }
14690     if(bookHit) { // [HGM] book: simulate book reply
14691         static char bookMove[MSG_SIZ]; // a bit generous?
14692
14693         programStats.nodes = programStats.depth = programStats.time =
14694         programStats.score = programStats.got_only_move = 0;
14695         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14696
14697         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14698         strcat(bookMove, bookHit);
14699         HandleMachineMove(bookMove, &first);
14700     }
14701 }
14702
14703
14704 void
14705 DisplayTwoMachinesTitle ()
14706 {
14707     char buf[MSG_SIZ];
14708     if (appData.matchGames > 0) {
14709         if(appData.tourneyFile[0]) {
14710           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14711                    gameInfo.white, _("vs."), gameInfo.black,
14712                    nextGame+1, appData.matchGames+1,
14713                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14714         } else
14715         if (first.twoMachinesColor[0] == 'w') {
14716           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14717                    gameInfo.white, _("vs."),  gameInfo.black,
14718                    first.matchWins, second.matchWins,
14719                    matchGame - 1 - (first.matchWins + second.matchWins));
14720         } else {
14721           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14722                    gameInfo.white, _("vs."), gameInfo.black,
14723                    second.matchWins, first.matchWins,
14724                    matchGame - 1 - (first.matchWins + second.matchWins));
14725         }
14726     } else {
14727       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14728     }
14729     DisplayTitle(buf);
14730 }
14731
14732 void
14733 SettingsMenuIfReady ()
14734 {
14735   if (second.lastPing != second.lastPong) {
14736     DisplayMessage("", _("Waiting for second chess program"));
14737     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14738     return;
14739   }
14740   ThawUI();
14741   DisplayMessage("", "");
14742   SettingsPopUp(&second);
14743 }
14744
14745 int
14746 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14747 {
14748     char buf[MSG_SIZ];
14749     if (cps->pr == NoProc) {
14750         StartChessProgram(cps);
14751         if (cps->protocolVersion == 1) {
14752           retry();
14753           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14754         } else {
14755           /* kludge: allow timeout for initial "feature" command */
14756           if(retry != TwoMachinesEventIfReady) FreezeUI();
14757           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14758           DisplayMessage("", buf);
14759           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14760         }
14761         return 1;
14762     }
14763     return 0;
14764 }
14765
14766 void
14767 TwoMachinesEvent P((void))
14768 {
14769     int i;
14770     char buf[MSG_SIZ];
14771     ChessProgramState *onmove;
14772     char *bookHit = NULL;
14773     static int stalling = 0;
14774     TimeMark now;
14775     long wait;
14776
14777     if (appData.noChessProgram) return;
14778
14779     switch (gameMode) {
14780       case TwoMachinesPlay:
14781         return;
14782       case MachinePlaysWhite:
14783       case MachinePlaysBlack:
14784         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14785             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14786             return;
14787         }
14788         /* fall through */
14789       case BeginningOfGame:
14790       case PlayFromGameFile:
14791       case EndOfGame:
14792         EditGameEvent();
14793         if (gameMode != EditGame) return;
14794         break;
14795       case EditPosition:
14796         EditPositionDone(TRUE);
14797         break;
14798       case AnalyzeMode:
14799       case AnalyzeFile:
14800         ExitAnalyzeMode();
14801         break;
14802       case EditGame:
14803       default:
14804         break;
14805     }
14806
14807 //    forwardMostMove = currentMove;
14808     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14809     startingEngine = TRUE;
14810
14811     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14812
14813     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14814     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14815       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14816       return;
14817     }
14818     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14819
14820     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14821                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14822         startingEngine = matchMode = FALSE;
14823         DisplayError("second engine does not play this", 0);
14824         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14825         EditGameEvent(); // switch back to EditGame mode
14826         return;
14827     }
14828
14829     if(!stalling) {
14830       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14831       SendToProgram("force\n", &second);
14832       stalling = 1;
14833       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14834       return;
14835     }
14836     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14837     if(appData.matchPause>10000 || appData.matchPause<10)
14838                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14839     wait = SubtractTimeMarks(&now, &pauseStart);
14840     if(wait < appData.matchPause) {
14841         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14842         return;
14843     }
14844     // we are now committed to starting the game
14845     stalling = 0;
14846     DisplayMessage("", "");
14847     if (startedFromSetupPosition) {
14848         SendBoard(&second, backwardMostMove);
14849     if (appData.debugMode) {
14850         fprintf(debugFP, "Two Machines\n");
14851     }
14852     }
14853     for (i = backwardMostMove; i < forwardMostMove; i++) {
14854         SendMoveToProgram(i, &second);
14855     }
14856
14857     gameMode = TwoMachinesPlay;
14858     pausing = startingEngine = FALSE;
14859     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14860     SetGameInfo();
14861     DisplayTwoMachinesTitle();
14862     firstMove = TRUE;
14863     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14864         onmove = &first;
14865     } else {
14866         onmove = &second;
14867     }
14868     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14869     SendToProgram(first.computerString, &first);
14870     if (first.sendName) {
14871       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14872       SendToProgram(buf, &first);
14873     }
14874     SendToProgram(second.computerString, &second);
14875     if (second.sendName) {
14876       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14877       SendToProgram(buf, &second);
14878     }
14879
14880     ResetClocks();
14881     if (!first.sendTime || !second.sendTime) {
14882         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14883         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14884     }
14885     if (onmove->sendTime) {
14886       if (onmove->useColors) {
14887         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14888       }
14889       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14890     }
14891     if (onmove->useColors) {
14892       SendToProgram(onmove->twoMachinesColor, onmove);
14893     }
14894     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14895 //    SendToProgram("go\n", onmove);
14896     onmove->maybeThinking = TRUE;
14897     SetMachineThinkingEnables();
14898
14899     StartClocks();
14900
14901     if(bookHit) { // [HGM] book: simulate book reply
14902         static char bookMove[MSG_SIZ]; // a bit generous?
14903
14904         programStats.nodes = programStats.depth = programStats.time =
14905         programStats.score = programStats.got_only_move = 0;
14906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14907
14908         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14909         strcat(bookMove, bookHit);
14910         savedMessage = bookMove; // args for deferred call
14911         savedState = onmove;
14912         ScheduleDelayedEvent(DeferredBookMove, 1);
14913     }
14914 }
14915
14916 void
14917 TrainingEvent ()
14918 {
14919     if (gameMode == Training) {
14920       SetTrainingModeOff();
14921       gameMode = PlayFromGameFile;
14922       DisplayMessage("", _("Training mode off"));
14923     } else {
14924       gameMode = Training;
14925       animateTraining = appData.animate;
14926
14927       /* make sure we are not already at the end of the game */
14928       if (currentMove < forwardMostMove) {
14929         SetTrainingModeOn();
14930         DisplayMessage("", _("Training mode on"));
14931       } else {
14932         gameMode = PlayFromGameFile;
14933         DisplayError(_("Already at end of game"), 0);
14934       }
14935     }
14936     ModeHighlight();
14937 }
14938
14939 void
14940 IcsClientEvent ()
14941 {
14942     if (!appData.icsActive) return;
14943     switch (gameMode) {
14944       case IcsPlayingWhite:
14945       case IcsPlayingBlack:
14946       case IcsObserving:
14947       case IcsIdle:
14948       case BeginningOfGame:
14949       case IcsExamining:
14950         return;
14951
14952       case EditGame:
14953         break;
14954
14955       case EditPosition:
14956         EditPositionDone(TRUE);
14957         break;
14958
14959       case AnalyzeMode:
14960       case AnalyzeFile:
14961         ExitAnalyzeMode();
14962         break;
14963
14964       default:
14965         EditGameEvent();
14966         break;
14967     }
14968
14969     gameMode = IcsIdle;
14970     ModeHighlight();
14971     return;
14972 }
14973
14974 void
14975 EditGameEvent ()
14976 {
14977     int i;
14978
14979     switch (gameMode) {
14980       case Training:
14981         SetTrainingModeOff();
14982         break;
14983       case MachinePlaysWhite:
14984       case MachinePlaysBlack:
14985       case BeginningOfGame:
14986         SendToProgram("force\n", &first);
14987         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
14988             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
14989                 char buf[MSG_SIZ];
14990                 abortEngineThink = TRUE;
14991                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
14992                 SendToProgram(buf, &first);
14993                 DisplayMessage("Aborting engine think", "");
14994                 FreezeUI();
14995             }
14996         }
14997         SetUserThinkingEnables();
14998         break;
14999       case PlayFromGameFile:
15000         (void) StopLoadGameTimer();
15001         if (gameFileFP != NULL) {
15002             gameFileFP = NULL;
15003         }
15004         break;
15005       case EditPosition:
15006         EditPositionDone(TRUE);
15007         break;
15008       case AnalyzeMode:
15009       case AnalyzeFile:
15010         ExitAnalyzeMode();
15011         SendToProgram("force\n", &first);
15012         break;
15013       case TwoMachinesPlay:
15014         GameEnds(EndOfFile, NULL, GE_PLAYER);
15015         ResurrectChessProgram();
15016         SetUserThinkingEnables();
15017         break;
15018       case EndOfGame:
15019         ResurrectChessProgram();
15020         break;
15021       case IcsPlayingBlack:
15022       case IcsPlayingWhite:
15023         DisplayError(_("Warning: You are still playing a game"), 0);
15024         break;
15025       case IcsObserving:
15026         DisplayError(_("Warning: You are still observing a game"), 0);
15027         break;
15028       case IcsExamining:
15029         DisplayError(_("Warning: You are still examining a game"), 0);
15030         break;
15031       case IcsIdle:
15032         break;
15033       case EditGame:
15034       default:
15035         return;
15036     }
15037
15038     pausing = FALSE;
15039     StopClocks();
15040     first.offeredDraw = second.offeredDraw = 0;
15041
15042     if (gameMode == PlayFromGameFile) {
15043         whiteTimeRemaining = timeRemaining[0][currentMove];
15044         blackTimeRemaining = timeRemaining[1][currentMove];
15045         DisplayTitle("");
15046     }
15047
15048     if (gameMode == MachinePlaysWhite ||
15049         gameMode == MachinePlaysBlack ||
15050         gameMode == TwoMachinesPlay ||
15051         gameMode == EndOfGame) {
15052         i = forwardMostMove;
15053         while (i > currentMove) {
15054             SendToProgram("undo\n", &first);
15055             i--;
15056         }
15057         if(!adjustedClock) {
15058         whiteTimeRemaining = timeRemaining[0][currentMove];
15059         blackTimeRemaining = timeRemaining[1][currentMove];
15060         DisplayBothClocks();
15061         }
15062         if (whiteFlag || blackFlag) {
15063             whiteFlag = blackFlag = 0;
15064         }
15065         DisplayTitle("");
15066     }
15067
15068     gameMode = EditGame;
15069     ModeHighlight();
15070     SetGameInfo();
15071 }
15072
15073
15074 void
15075 EditPositionEvent ()
15076 {
15077     if (gameMode == EditPosition) {
15078         EditGameEvent();
15079         return;
15080     }
15081
15082     EditGameEvent();
15083     if (gameMode != EditGame) return;
15084
15085     gameMode = EditPosition;
15086     ModeHighlight();
15087     SetGameInfo();
15088     if (currentMove > 0)
15089       CopyBoard(boards[0], boards[currentMove]);
15090
15091     blackPlaysFirst = !WhiteOnMove(currentMove);
15092     ResetClocks();
15093     currentMove = forwardMostMove = backwardMostMove = 0;
15094     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15095     DisplayMove(-1);
15096     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15097 }
15098
15099 void
15100 ExitAnalyzeMode ()
15101 {
15102     /* [DM] icsEngineAnalyze - possible call from other functions */
15103     if (appData.icsEngineAnalyze) {
15104         appData.icsEngineAnalyze = FALSE;
15105
15106         DisplayMessage("",_("Close ICS engine analyze..."));
15107     }
15108     if (first.analysisSupport && first.analyzing) {
15109       SendToBoth("exit\n");
15110       first.analyzing = second.analyzing = FALSE;
15111     }
15112     thinkOutput[0] = NULLCHAR;
15113 }
15114
15115 void
15116 EditPositionDone (Boolean fakeRights)
15117 {
15118     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15119
15120     startedFromSetupPosition = TRUE;
15121     InitChessProgram(&first, FALSE);
15122     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15123       boards[0][EP_STATUS] = EP_NONE;
15124       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15125       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15126         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15127         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15128       } else boards[0][CASTLING][2] = NoRights;
15129       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15130         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15131         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15132       } else boards[0][CASTLING][5] = NoRights;
15133       if(gameInfo.variant == VariantSChess) {
15134         int i;
15135         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15136           boards[0][VIRGIN][i] = 0;
15137           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15138           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15139         }
15140       }
15141     }
15142     SendToProgram("force\n", &first);
15143     if (blackPlaysFirst) {
15144         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15145         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15146         currentMove = forwardMostMove = backwardMostMove = 1;
15147         CopyBoard(boards[1], boards[0]);
15148     } else {
15149         currentMove = forwardMostMove = backwardMostMove = 0;
15150     }
15151     SendBoard(&first, forwardMostMove);
15152     if (appData.debugMode) {
15153         fprintf(debugFP, "EditPosDone\n");
15154     }
15155     DisplayTitle("");
15156     DisplayMessage("", "");
15157     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15158     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15159     gameMode = EditGame;
15160     ModeHighlight();
15161     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15162     ClearHighlights(); /* [AS] */
15163 }
15164
15165 /* Pause for `ms' milliseconds */
15166 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15167 void
15168 TimeDelay (long ms)
15169 {
15170     TimeMark m1, m2;
15171
15172     GetTimeMark(&m1);
15173     do {
15174         GetTimeMark(&m2);
15175     } while (SubtractTimeMarks(&m2, &m1) < ms);
15176 }
15177
15178 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15179 void
15180 SendMultiLineToICS (char *buf)
15181 {
15182     char temp[MSG_SIZ+1], *p;
15183     int len;
15184
15185     len = strlen(buf);
15186     if (len > MSG_SIZ)
15187       len = MSG_SIZ;
15188
15189     strncpy(temp, buf, len);
15190     temp[len] = 0;
15191
15192     p = temp;
15193     while (*p) {
15194         if (*p == '\n' || *p == '\r')
15195           *p = ' ';
15196         ++p;
15197     }
15198
15199     strcat(temp, "\n");
15200     SendToICS(temp);
15201     SendToPlayer(temp, strlen(temp));
15202 }
15203
15204 void
15205 SetWhiteToPlayEvent ()
15206 {
15207     if (gameMode == EditPosition) {
15208         blackPlaysFirst = FALSE;
15209         DisplayBothClocks();    /* works because currentMove is 0 */
15210     } else if (gameMode == IcsExamining) {
15211         SendToICS(ics_prefix);
15212         SendToICS("tomove white\n");
15213     }
15214 }
15215
15216 void
15217 SetBlackToPlayEvent ()
15218 {
15219     if (gameMode == EditPosition) {
15220         blackPlaysFirst = TRUE;
15221         currentMove = 1;        /* kludge */
15222         DisplayBothClocks();
15223         currentMove = 0;
15224     } else if (gameMode == IcsExamining) {
15225         SendToICS(ics_prefix);
15226         SendToICS("tomove black\n");
15227     }
15228 }
15229
15230 void
15231 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15232 {
15233     char buf[MSG_SIZ];
15234     ChessSquare piece = boards[0][y][x];
15235     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15236     static int lastVariant;
15237
15238     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15239
15240     switch (selection) {
15241       case ClearBoard:
15242         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15243         MarkTargetSquares(1);
15244         CopyBoard(currentBoard, boards[0]);
15245         CopyBoard(menuBoard, initialPosition);
15246         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15247             SendToICS(ics_prefix);
15248             SendToICS("bsetup clear\n");
15249         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15250             SendToICS(ics_prefix);
15251             SendToICS("clearboard\n");
15252         } else {
15253             int nonEmpty = 0;
15254             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15255                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15256                 for (y = 0; y < BOARD_HEIGHT; y++) {
15257                     if (gameMode == IcsExamining) {
15258                         if (boards[currentMove][y][x] != EmptySquare) {
15259                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15260                                     AAA + x, ONE + y);
15261                             SendToICS(buf);
15262                         }
15263                     } else if(boards[0][y][x] != DarkSquare) {
15264                         if(boards[0][y][x] != p) nonEmpty++;
15265                         boards[0][y][x] = p;
15266                     }
15267                 }
15268             }
15269             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15270                 int r;
15271                 for(r = 0; r < BOARD_HEIGHT; r++) {
15272                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15273                     ChessSquare p = menuBoard[r][x];
15274                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15275                   }
15276                 }
15277                 DisplayMessage("Clicking clock again restores position", "");
15278                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15279                 if(!nonEmpty) { // asked to clear an empty board
15280                     CopyBoard(boards[0], menuBoard);
15281                 } else
15282                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15283                     CopyBoard(boards[0], initialPosition);
15284                 } else
15285                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15286                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15287                     CopyBoard(boards[0], erasedBoard);
15288                 } else
15289                     CopyBoard(erasedBoard, currentBoard);
15290
15291             }
15292         }
15293         if (gameMode == EditPosition) {
15294             DrawPosition(FALSE, boards[0]);
15295         }
15296         break;
15297
15298       case WhitePlay:
15299         SetWhiteToPlayEvent();
15300         break;
15301
15302       case BlackPlay:
15303         SetBlackToPlayEvent();
15304         break;
15305
15306       case EmptySquare:
15307         if (gameMode == IcsExamining) {
15308             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15309             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15310             SendToICS(buf);
15311         } else {
15312             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15313                 if(x == BOARD_LEFT-2) {
15314                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15315                     boards[0][y][1] = 0;
15316                 } else
15317                 if(x == BOARD_RGHT+1) {
15318                     if(y >= gameInfo.holdingsSize) break;
15319                     boards[0][y][BOARD_WIDTH-2] = 0;
15320                 } else break;
15321             }
15322             boards[0][y][x] = EmptySquare;
15323             DrawPosition(FALSE, boards[0]);
15324         }
15325         break;
15326
15327       case PromotePiece:
15328         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15329            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15330             selection = (ChessSquare) (PROMOTED piece);
15331         } else if(piece == EmptySquare) selection = WhiteSilver;
15332         else selection = (ChessSquare)((int)piece - 1);
15333         goto defaultlabel;
15334
15335       case DemotePiece:
15336         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15337            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15338             selection = (ChessSquare) (DEMOTED piece);
15339         } else if(piece == EmptySquare) selection = BlackSilver;
15340         else selection = (ChessSquare)((int)piece + 1);
15341         goto defaultlabel;
15342
15343       case WhiteQueen:
15344       case BlackQueen:
15345         if(gameInfo.variant == VariantShatranj ||
15346            gameInfo.variant == VariantXiangqi  ||
15347            gameInfo.variant == VariantCourier  ||
15348            gameInfo.variant == VariantASEAN    ||
15349            gameInfo.variant == VariantMakruk     )
15350             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15351         goto defaultlabel;
15352
15353       case WhiteKing:
15354       case BlackKing:
15355         if(gameInfo.variant == VariantXiangqi)
15356             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15357         if(gameInfo.variant == VariantKnightmate)
15358             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15359       default:
15360         defaultlabel:
15361         if (gameMode == IcsExamining) {
15362             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15363             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15364                      PieceToChar(selection), AAA + x, ONE + y);
15365             SendToICS(buf);
15366         } else {
15367             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15368                 int n;
15369                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15370                     n = PieceToNumber(selection - BlackPawn);
15371                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15372                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15373                     boards[0][BOARD_HEIGHT-1-n][1]++;
15374                 } else
15375                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15376                     n = PieceToNumber(selection);
15377                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15378                     boards[0][n][BOARD_WIDTH-1] = selection;
15379                     boards[0][n][BOARD_WIDTH-2]++;
15380                 }
15381             } else
15382             boards[0][y][x] = selection;
15383             DrawPosition(TRUE, boards[0]);
15384             ClearHighlights();
15385             fromX = fromY = -1;
15386         }
15387         break;
15388     }
15389 }
15390
15391
15392 void
15393 DropMenuEvent (ChessSquare selection, int x, int y)
15394 {
15395     ChessMove moveType;
15396
15397     switch (gameMode) {
15398       case IcsPlayingWhite:
15399       case MachinePlaysBlack:
15400         if (!WhiteOnMove(currentMove)) {
15401             DisplayMoveError(_("It is Black's turn"));
15402             return;
15403         }
15404         moveType = WhiteDrop;
15405         break;
15406       case IcsPlayingBlack:
15407       case MachinePlaysWhite:
15408         if (WhiteOnMove(currentMove)) {
15409             DisplayMoveError(_("It is White's turn"));
15410             return;
15411         }
15412         moveType = BlackDrop;
15413         break;
15414       case EditGame:
15415         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15416         break;
15417       default:
15418         return;
15419     }
15420
15421     if (moveType == BlackDrop && selection < BlackPawn) {
15422       selection = (ChessSquare) ((int) selection
15423                                  + (int) BlackPawn - (int) WhitePawn);
15424     }
15425     if (boards[currentMove][y][x] != EmptySquare) {
15426         DisplayMoveError(_("That square is occupied"));
15427         return;
15428     }
15429
15430     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15431 }
15432
15433 void
15434 AcceptEvent ()
15435 {
15436     /* Accept a pending offer of any kind from opponent */
15437
15438     if (appData.icsActive) {
15439         SendToICS(ics_prefix);
15440         SendToICS("accept\n");
15441     } else if (cmailMsgLoaded) {
15442         if (currentMove == cmailOldMove &&
15443             commentList[cmailOldMove] != NULL &&
15444             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15445                    "Black offers a draw" : "White offers a draw")) {
15446             TruncateGame();
15447             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15448             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15449         } else {
15450             DisplayError(_("There is no pending offer on this move"), 0);
15451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15452         }
15453     } else {
15454         /* Not used for offers from chess program */
15455     }
15456 }
15457
15458 void
15459 DeclineEvent ()
15460 {
15461     /* Decline a pending offer of any kind from opponent */
15462
15463     if (appData.icsActive) {
15464         SendToICS(ics_prefix);
15465         SendToICS("decline\n");
15466     } else if (cmailMsgLoaded) {
15467         if (currentMove == cmailOldMove &&
15468             commentList[cmailOldMove] != NULL &&
15469             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15470                    "Black offers a draw" : "White offers a draw")) {
15471 #ifdef NOTDEF
15472             AppendComment(cmailOldMove, "Draw declined", TRUE);
15473             DisplayComment(cmailOldMove - 1, "Draw declined");
15474 #endif /*NOTDEF*/
15475         } else {
15476             DisplayError(_("There is no pending offer on this move"), 0);
15477         }
15478     } else {
15479         /* Not used for offers from chess program */
15480     }
15481 }
15482
15483 void
15484 RematchEvent ()
15485 {
15486     /* Issue ICS rematch command */
15487     if (appData.icsActive) {
15488         SendToICS(ics_prefix);
15489         SendToICS("rematch\n");
15490     }
15491 }
15492
15493 void
15494 CallFlagEvent ()
15495 {
15496     /* Call your opponent's flag (claim a win on time) */
15497     if (appData.icsActive) {
15498         SendToICS(ics_prefix);
15499         SendToICS("flag\n");
15500     } else {
15501         switch (gameMode) {
15502           default:
15503             return;
15504           case MachinePlaysWhite:
15505             if (whiteFlag) {
15506                 if (blackFlag)
15507                   GameEnds(GameIsDrawn, "Both players ran out of time",
15508                            GE_PLAYER);
15509                 else
15510                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15511             } else {
15512                 DisplayError(_("Your opponent is not out of time"), 0);
15513             }
15514             break;
15515           case MachinePlaysBlack:
15516             if (blackFlag) {
15517                 if (whiteFlag)
15518                   GameEnds(GameIsDrawn, "Both players ran out of time",
15519                            GE_PLAYER);
15520                 else
15521                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15522             } else {
15523                 DisplayError(_("Your opponent is not out of time"), 0);
15524             }
15525             break;
15526         }
15527     }
15528 }
15529
15530 void
15531 ClockClick (int which)
15532 {       // [HGM] code moved to back-end from winboard.c
15533         if(which) { // black clock
15534           if (gameMode == EditPosition || gameMode == IcsExamining) {
15535             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15536             SetBlackToPlayEvent();
15537           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15538                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15539           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15540           } else if (shiftKey) {
15541             AdjustClock(which, -1);
15542           } else if (gameMode == IcsPlayingWhite ||
15543                      gameMode == MachinePlaysBlack) {
15544             CallFlagEvent();
15545           }
15546         } else { // white clock
15547           if (gameMode == EditPosition || gameMode == IcsExamining) {
15548             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15549             SetWhiteToPlayEvent();
15550           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15551                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15552           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15553           } else if (shiftKey) {
15554             AdjustClock(which, -1);
15555           } else if (gameMode == IcsPlayingBlack ||
15556                    gameMode == MachinePlaysWhite) {
15557             CallFlagEvent();
15558           }
15559         }
15560 }
15561
15562 void
15563 DrawEvent ()
15564 {
15565     /* Offer draw or accept pending draw offer from opponent */
15566
15567     if (appData.icsActive) {
15568         /* Note: tournament rules require draw offers to be
15569            made after you make your move but before you punch
15570            your clock.  Currently ICS doesn't let you do that;
15571            instead, you immediately punch your clock after making
15572            a move, but you can offer a draw at any time. */
15573
15574         SendToICS(ics_prefix);
15575         SendToICS("draw\n");
15576         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15577     } else if (cmailMsgLoaded) {
15578         if (currentMove == cmailOldMove &&
15579             commentList[cmailOldMove] != NULL &&
15580             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15581                    "Black offers a draw" : "White offers a draw")) {
15582             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15583             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15584         } else if (currentMove == cmailOldMove + 1) {
15585             char *offer = WhiteOnMove(cmailOldMove) ?
15586               "White offers a draw" : "Black offers a draw";
15587             AppendComment(currentMove, offer, TRUE);
15588             DisplayComment(currentMove - 1, offer);
15589             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15590         } else {
15591             DisplayError(_("You must make your move before offering a draw"), 0);
15592             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15593         }
15594     } else if (first.offeredDraw) {
15595         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15596     } else {
15597         if (first.sendDrawOffers) {
15598             SendToProgram("draw\n", &first);
15599             userOfferedDraw = TRUE;
15600         }
15601     }
15602 }
15603
15604 void
15605 AdjournEvent ()
15606 {
15607     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15608
15609     if (appData.icsActive) {
15610         SendToICS(ics_prefix);
15611         SendToICS("adjourn\n");
15612     } else {
15613         /* Currently GNU Chess doesn't offer or accept Adjourns */
15614     }
15615 }
15616
15617
15618 void
15619 AbortEvent ()
15620 {
15621     /* Offer Abort or accept pending Abort offer from opponent */
15622
15623     if (appData.icsActive) {
15624         SendToICS(ics_prefix);
15625         SendToICS("abort\n");
15626     } else {
15627         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15628     }
15629 }
15630
15631 void
15632 ResignEvent ()
15633 {
15634     /* Resign.  You can do this even if it's not your turn. */
15635
15636     if (appData.icsActive) {
15637         SendToICS(ics_prefix);
15638         SendToICS("resign\n");
15639     } else {
15640         switch (gameMode) {
15641           case MachinePlaysWhite:
15642             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15643             break;
15644           case MachinePlaysBlack:
15645             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15646             break;
15647           case EditGame:
15648             if (cmailMsgLoaded) {
15649                 TruncateGame();
15650                 if (WhiteOnMove(cmailOldMove)) {
15651                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15652                 } else {
15653                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15654                 }
15655                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15656             }
15657             break;
15658           default:
15659             break;
15660         }
15661     }
15662 }
15663
15664
15665 void
15666 StopObservingEvent ()
15667 {
15668     /* Stop observing current games */
15669     SendToICS(ics_prefix);
15670     SendToICS("unobserve\n");
15671 }
15672
15673 void
15674 StopExaminingEvent ()
15675 {
15676     /* Stop observing current game */
15677     SendToICS(ics_prefix);
15678     SendToICS("unexamine\n");
15679 }
15680
15681 void
15682 ForwardInner (int target)
15683 {
15684     int limit; int oldSeekGraphUp = seekGraphUp;
15685
15686     if (appData.debugMode)
15687         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15688                 target, currentMove, forwardMostMove);
15689
15690     if (gameMode == EditPosition)
15691       return;
15692
15693     seekGraphUp = FALSE;
15694     MarkTargetSquares(1);
15695     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15696
15697     if (gameMode == PlayFromGameFile && !pausing)
15698       PauseEvent();
15699
15700     if (gameMode == IcsExamining && pausing)
15701       limit = pauseExamForwardMostMove;
15702     else
15703       limit = forwardMostMove;
15704
15705     if (target > limit) target = limit;
15706
15707     if (target > 0 && moveList[target - 1][0]) {
15708         int fromX, fromY, toX, toY;
15709         toX = moveList[target - 1][2] - AAA;
15710         toY = moveList[target - 1][3] - ONE;
15711         if (moveList[target - 1][1] == '@') {
15712             if (appData.highlightLastMove) {
15713                 SetHighlights(-1, -1, toX, toY);
15714             }
15715         } else {
15716             int viaX = moveList[target - 1][5] - AAA;
15717             int viaY = moveList[target - 1][6] - ONE;
15718             fromX = moveList[target - 1][0] - AAA;
15719             fromY = moveList[target - 1][1] - ONE;
15720             if (target == currentMove + 1) {
15721                 if(moveList[target - 1][4] == ';') { // multi-leg
15722                     ChessSquare piece = boards[currentMove][viaY][viaX];
15723                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15724                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15725                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15726                     boards[currentMove][viaY][viaX] = piece;
15727                 } else
15728                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15729             }
15730             if (appData.highlightLastMove) {
15731                 SetHighlights(fromX, fromY, toX, toY);
15732             }
15733         }
15734     }
15735     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15736         gameMode == Training || gameMode == PlayFromGameFile ||
15737         gameMode == AnalyzeFile) {
15738         while (currentMove < target) {
15739             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15740             SendMoveToProgram(currentMove++, &first);
15741         }
15742     } else {
15743         currentMove = target;
15744     }
15745
15746     if (gameMode == EditGame || gameMode == EndOfGame) {
15747         whiteTimeRemaining = timeRemaining[0][currentMove];
15748         blackTimeRemaining = timeRemaining[1][currentMove];
15749     }
15750     DisplayBothClocks();
15751     DisplayMove(currentMove - 1);
15752     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15753     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15754     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15755         DisplayComment(currentMove - 1, commentList[currentMove]);
15756     }
15757     ClearMap(); // [HGM] exclude: invalidate map
15758 }
15759
15760
15761 void
15762 ForwardEvent ()
15763 {
15764     if (gameMode == IcsExamining && !pausing) {
15765         SendToICS(ics_prefix);
15766         SendToICS("forward\n");
15767     } else {
15768         ForwardInner(currentMove + 1);
15769     }
15770 }
15771
15772 void
15773 ToEndEvent ()
15774 {
15775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15776         /* to optimze, we temporarily turn off analysis mode while we feed
15777          * the remaining moves to the engine. Otherwise we get analysis output
15778          * after each move.
15779          */
15780         if (first.analysisSupport) {
15781           SendToProgram("exit\nforce\n", &first);
15782           first.analyzing = FALSE;
15783         }
15784     }
15785
15786     if (gameMode == IcsExamining && !pausing) {
15787         SendToICS(ics_prefix);
15788         SendToICS("forward 999999\n");
15789     } else {
15790         ForwardInner(forwardMostMove);
15791     }
15792
15793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15794         /* we have fed all the moves, so reactivate analysis mode */
15795         SendToProgram("analyze\n", &first);
15796         first.analyzing = TRUE;
15797         /*first.maybeThinking = TRUE;*/
15798         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15799     }
15800 }
15801
15802 void
15803 BackwardInner (int target)
15804 {
15805     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15806
15807     if (appData.debugMode)
15808         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15809                 target, currentMove, forwardMostMove);
15810
15811     if (gameMode == EditPosition) return;
15812     seekGraphUp = FALSE;
15813     MarkTargetSquares(1);
15814     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15815     if (currentMove <= backwardMostMove) {
15816         ClearHighlights();
15817         DrawPosition(full_redraw, boards[currentMove]);
15818         return;
15819     }
15820     if (gameMode == PlayFromGameFile && !pausing)
15821       PauseEvent();
15822
15823     if (moveList[target][0]) {
15824         int fromX, fromY, toX, toY;
15825         toX = moveList[target][2] - AAA;
15826         toY = moveList[target][3] - ONE;
15827         if (moveList[target][1] == '@') {
15828             if (appData.highlightLastMove) {
15829                 SetHighlights(-1, -1, toX, toY);
15830             }
15831         } else {
15832             fromX = moveList[target][0] - AAA;
15833             fromY = moveList[target][1] - ONE;
15834             if (target == currentMove - 1) {
15835                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15836             }
15837             if (appData.highlightLastMove) {
15838                 SetHighlights(fromX, fromY, toX, toY);
15839             }
15840         }
15841     }
15842     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15843         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15844         while (currentMove > target) {
15845             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15846                 // null move cannot be undone. Reload program with move history before it.
15847                 int i;
15848                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15849                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15850                 }
15851                 SendBoard(&first, i);
15852               if(second.analyzing) SendBoard(&second, i);
15853                 for(currentMove=i; currentMove<target; currentMove++) {
15854                     SendMoveToProgram(currentMove, &first);
15855                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15856                 }
15857                 break;
15858             }
15859             SendToBoth("undo\n");
15860             currentMove--;
15861         }
15862     } else {
15863         currentMove = target;
15864     }
15865
15866     if (gameMode == EditGame || gameMode == EndOfGame) {
15867         whiteTimeRemaining = timeRemaining[0][currentMove];
15868         blackTimeRemaining = timeRemaining[1][currentMove];
15869     }
15870     DisplayBothClocks();
15871     DisplayMove(currentMove - 1);
15872     DrawPosition(full_redraw, boards[currentMove]);
15873     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15874     // [HGM] PV info: routine tests if comment empty
15875     DisplayComment(currentMove - 1, commentList[currentMove]);
15876     ClearMap(); // [HGM] exclude: invalidate map
15877 }
15878
15879 void
15880 BackwardEvent ()
15881 {
15882     if (gameMode == IcsExamining && !pausing) {
15883         SendToICS(ics_prefix);
15884         SendToICS("backward\n");
15885     } else {
15886         BackwardInner(currentMove - 1);
15887     }
15888 }
15889
15890 void
15891 ToStartEvent ()
15892 {
15893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15894         /* to optimize, we temporarily turn off analysis mode while we undo
15895          * all the moves. Otherwise we get analysis output after each undo.
15896          */
15897         if (first.analysisSupport) {
15898           SendToProgram("exit\nforce\n", &first);
15899           first.analyzing = FALSE;
15900         }
15901     }
15902
15903     if (gameMode == IcsExamining && !pausing) {
15904         SendToICS(ics_prefix);
15905         SendToICS("backward 999999\n");
15906     } else {
15907         BackwardInner(backwardMostMove);
15908     }
15909
15910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15911         /* we have fed all the moves, so reactivate analysis mode */
15912         SendToProgram("analyze\n", &first);
15913         first.analyzing = TRUE;
15914         /*first.maybeThinking = TRUE;*/
15915         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15916     }
15917 }
15918
15919 void
15920 ToNrEvent (int to)
15921 {
15922   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15923   if (to >= forwardMostMove) to = forwardMostMove;
15924   if (to <= backwardMostMove) to = backwardMostMove;
15925   if (to < currentMove) {
15926     BackwardInner(to);
15927   } else {
15928     ForwardInner(to);
15929   }
15930 }
15931
15932 void
15933 RevertEvent (Boolean annotate)
15934 {
15935     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15936         return;
15937     }
15938     if (gameMode != IcsExamining) {
15939         DisplayError(_("You are not examining a game"), 0);
15940         return;
15941     }
15942     if (pausing) {
15943         DisplayError(_("You can't revert while pausing"), 0);
15944         return;
15945     }
15946     SendToICS(ics_prefix);
15947     SendToICS("revert\n");
15948 }
15949
15950 void
15951 RetractMoveEvent ()
15952 {
15953     switch (gameMode) {
15954       case MachinePlaysWhite:
15955       case MachinePlaysBlack:
15956         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15957             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15958             return;
15959         }
15960         if (forwardMostMove < 2) return;
15961         currentMove = forwardMostMove = forwardMostMove - 2;
15962         whiteTimeRemaining = timeRemaining[0][currentMove];
15963         blackTimeRemaining = timeRemaining[1][currentMove];
15964         DisplayBothClocks();
15965         DisplayMove(currentMove - 1);
15966         ClearHighlights();/*!! could figure this out*/
15967         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15968         SendToProgram("remove\n", &first);
15969         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15970         break;
15971
15972       case BeginningOfGame:
15973       default:
15974         break;
15975
15976       case IcsPlayingWhite:
15977       case IcsPlayingBlack:
15978         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15979             SendToICS(ics_prefix);
15980             SendToICS("takeback 2\n");
15981         } else {
15982             SendToICS(ics_prefix);
15983             SendToICS("takeback 1\n");
15984         }
15985         break;
15986     }
15987 }
15988
15989 void
15990 MoveNowEvent ()
15991 {
15992     ChessProgramState *cps;
15993
15994     switch (gameMode) {
15995       case MachinePlaysWhite:
15996         if (!WhiteOnMove(forwardMostMove)) {
15997             DisplayError(_("It is your turn"), 0);
15998             return;
15999         }
16000         cps = &first;
16001         break;
16002       case MachinePlaysBlack:
16003         if (WhiteOnMove(forwardMostMove)) {
16004             DisplayError(_("It is your turn"), 0);
16005             return;
16006         }
16007         cps = &first;
16008         break;
16009       case TwoMachinesPlay:
16010         if (WhiteOnMove(forwardMostMove) ==
16011             (first.twoMachinesColor[0] == 'w')) {
16012             cps = &first;
16013         } else {
16014             cps = &second;
16015         }
16016         break;
16017       case BeginningOfGame:
16018       default:
16019         return;
16020     }
16021     SendToProgram("?\n", cps);
16022 }
16023
16024 void
16025 TruncateGameEvent ()
16026 {
16027     EditGameEvent();
16028     if (gameMode != EditGame) return;
16029     TruncateGame();
16030 }
16031
16032 void
16033 TruncateGame ()
16034 {
16035     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16036     if (forwardMostMove > currentMove) {
16037         if (gameInfo.resultDetails != NULL) {
16038             free(gameInfo.resultDetails);
16039             gameInfo.resultDetails = NULL;
16040             gameInfo.result = GameUnfinished;
16041         }
16042         forwardMostMove = currentMove;
16043         HistorySet(parseList, backwardMostMove, forwardMostMove,
16044                    currentMove-1);
16045     }
16046 }
16047
16048 void
16049 HintEvent ()
16050 {
16051     if (appData.noChessProgram) return;
16052     switch (gameMode) {
16053       case MachinePlaysWhite:
16054         if (WhiteOnMove(forwardMostMove)) {
16055             DisplayError(_("Wait until your turn."), 0);
16056             return;
16057         }
16058         break;
16059       case BeginningOfGame:
16060       case MachinePlaysBlack:
16061         if (!WhiteOnMove(forwardMostMove)) {
16062             DisplayError(_("Wait until your turn."), 0);
16063             return;
16064         }
16065         break;
16066       default:
16067         DisplayError(_("No hint available"), 0);
16068         return;
16069     }
16070     SendToProgram("hint\n", &first);
16071     hintRequested = TRUE;
16072 }
16073
16074 int
16075 SaveSelected (FILE *g, int dummy, char *dummy2)
16076 {
16077     ListGame * lg = (ListGame *) gameList.head;
16078     int nItem, cnt=0;
16079     FILE *f;
16080
16081     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16082         DisplayError(_("Game list not loaded or empty"), 0);
16083         return 0;
16084     }
16085
16086     creatingBook = TRUE; // suppresses stuff during load game
16087
16088     /* Get list size */
16089     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16090         if(lg->position >= 0) { // selected?
16091             LoadGame(f, nItem, "", TRUE);
16092             SaveGamePGN2(g); // leaves g open
16093             cnt++; DoEvents();
16094         }
16095         lg = (ListGame *) lg->node.succ;
16096     }
16097
16098     fclose(g);
16099     creatingBook = FALSE;
16100
16101     return cnt;
16102 }
16103
16104 void
16105 CreateBookEvent ()
16106 {
16107     ListGame * lg = (ListGame *) gameList.head;
16108     FILE *f, *g;
16109     int nItem;
16110     static int secondTime = FALSE;
16111
16112     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16113         DisplayError(_("Game list not loaded or empty"), 0);
16114         return;
16115     }
16116
16117     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16118         fclose(g);
16119         secondTime++;
16120         DisplayNote(_("Book file exists! Try again for overwrite."));
16121         return;
16122     }
16123
16124     creatingBook = TRUE;
16125     secondTime = FALSE;
16126
16127     /* Get list size */
16128     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16129         if(lg->position >= 0) {
16130             LoadGame(f, nItem, "", TRUE);
16131             AddGameToBook(TRUE);
16132             DoEvents();
16133         }
16134         lg = (ListGame *) lg->node.succ;
16135     }
16136
16137     creatingBook = FALSE;
16138     FlushBook();
16139 }
16140
16141 void
16142 BookEvent ()
16143 {
16144     if (appData.noChessProgram) return;
16145     switch (gameMode) {
16146       case MachinePlaysWhite:
16147         if (WhiteOnMove(forwardMostMove)) {
16148             DisplayError(_("Wait until your turn."), 0);
16149             return;
16150         }
16151         break;
16152       case BeginningOfGame:
16153       case MachinePlaysBlack:
16154         if (!WhiteOnMove(forwardMostMove)) {
16155             DisplayError(_("Wait until your turn."), 0);
16156             return;
16157         }
16158         break;
16159       case EditPosition:
16160         EditPositionDone(TRUE);
16161         break;
16162       case TwoMachinesPlay:
16163         return;
16164       default:
16165         break;
16166     }
16167     SendToProgram("bk\n", &first);
16168     bookOutput[0] = NULLCHAR;
16169     bookRequested = TRUE;
16170 }
16171
16172 void
16173 AboutGameEvent ()
16174 {
16175     char *tags = PGNTags(&gameInfo);
16176     TagsPopUp(tags, CmailMsg());
16177     free(tags);
16178 }
16179
16180 /* end button procedures */
16181
16182 void
16183 PrintPosition (FILE *fp, int move)
16184 {
16185     int i, j;
16186
16187     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16188         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16189             char c = PieceToChar(boards[move][i][j]);
16190             fputc(c == 'x' ? '.' : c, fp);
16191             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16192         }
16193     }
16194     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16195       fprintf(fp, "white to play\n");
16196     else
16197       fprintf(fp, "black to play\n");
16198 }
16199
16200 void
16201 PrintOpponents (FILE *fp)
16202 {
16203     if (gameInfo.white != NULL) {
16204         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16205     } else {
16206         fprintf(fp, "\n");
16207     }
16208 }
16209
16210 /* Find last component of program's own name, using some heuristics */
16211 void
16212 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16213 {
16214     char *p, *q, c;
16215     int local = (strcmp(host, "localhost") == 0);
16216     while (!local && (p = strchr(prog, ';')) != NULL) {
16217         p++;
16218         while (*p == ' ') p++;
16219         prog = p;
16220     }
16221     if (*prog == '"' || *prog == '\'') {
16222         q = strchr(prog + 1, *prog);
16223     } else {
16224         q = strchr(prog, ' ');
16225     }
16226     if (q == NULL) q = prog + strlen(prog);
16227     p = q;
16228     while (p >= prog && *p != '/' && *p != '\\') p--;
16229     p++;
16230     if(p == prog && *p == '"') p++;
16231     c = *q; *q = 0;
16232     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16233     memcpy(buf, p, q - p);
16234     buf[q - p] = NULLCHAR;
16235     if (!local) {
16236         strcat(buf, "@");
16237         strcat(buf, host);
16238     }
16239 }
16240
16241 char *
16242 TimeControlTagValue ()
16243 {
16244     char buf[MSG_SIZ];
16245     if (!appData.clockMode) {
16246       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16247     } else if (movesPerSession > 0) {
16248       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16249     } else if (timeIncrement == 0) {
16250       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16251     } else {
16252       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16253     }
16254     return StrSave(buf);
16255 }
16256
16257 void
16258 SetGameInfo ()
16259 {
16260     /* This routine is used only for certain modes */
16261     VariantClass v = gameInfo.variant;
16262     ChessMove r = GameUnfinished;
16263     char *p = NULL;
16264
16265     if(keepInfo) return;
16266
16267     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16268         r = gameInfo.result;
16269         p = gameInfo.resultDetails;
16270         gameInfo.resultDetails = NULL;
16271     }
16272     ClearGameInfo(&gameInfo);
16273     gameInfo.variant = v;
16274
16275     switch (gameMode) {
16276       case MachinePlaysWhite:
16277         gameInfo.event = StrSave( appData.pgnEventHeader );
16278         gameInfo.site = StrSave(HostName());
16279         gameInfo.date = PGNDate();
16280         gameInfo.round = StrSave("-");
16281         gameInfo.white = StrSave(first.tidy);
16282         gameInfo.black = StrSave(UserName());
16283         gameInfo.timeControl = TimeControlTagValue();
16284         break;
16285
16286       case MachinePlaysBlack:
16287         gameInfo.event = StrSave( appData.pgnEventHeader );
16288         gameInfo.site = StrSave(HostName());
16289         gameInfo.date = PGNDate();
16290         gameInfo.round = StrSave("-");
16291         gameInfo.white = StrSave(UserName());
16292         gameInfo.black = StrSave(first.tidy);
16293         gameInfo.timeControl = TimeControlTagValue();
16294         break;
16295
16296       case TwoMachinesPlay:
16297         gameInfo.event = StrSave( appData.pgnEventHeader );
16298         gameInfo.site = StrSave(HostName());
16299         gameInfo.date = PGNDate();
16300         if (roundNr > 0) {
16301             char buf[MSG_SIZ];
16302             snprintf(buf, MSG_SIZ, "%d", roundNr);
16303             gameInfo.round = StrSave(buf);
16304         } else {
16305             gameInfo.round = StrSave("-");
16306         }
16307         if (first.twoMachinesColor[0] == 'w') {
16308             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16309             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16310         } else {
16311             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16312             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16313         }
16314         gameInfo.timeControl = TimeControlTagValue();
16315         break;
16316
16317       case EditGame:
16318         gameInfo.event = StrSave("Edited game");
16319         gameInfo.site = StrSave(HostName());
16320         gameInfo.date = PGNDate();
16321         gameInfo.round = StrSave("-");
16322         gameInfo.white = StrSave("-");
16323         gameInfo.black = StrSave("-");
16324         gameInfo.result = r;
16325         gameInfo.resultDetails = p;
16326         break;
16327
16328       case EditPosition:
16329         gameInfo.event = StrSave("Edited position");
16330         gameInfo.site = StrSave(HostName());
16331         gameInfo.date = PGNDate();
16332         gameInfo.round = StrSave("-");
16333         gameInfo.white = StrSave("-");
16334         gameInfo.black = StrSave("-");
16335         break;
16336
16337       case IcsPlayingWhite:
16338       case IcsPlayingBlack:
16339       case IcsObserving:
16340       case IcsExamining:
16341         break;
16342
16343       case PlayFromGameFile:
16344         gameInfo.event = StrSave("Game from non-PGN file");
16345         gameInfo.site = StrSave(HostName());
16346         gameInfo.date = PGNDate();
16347         gameInfo.round = StrSave("-");
16348         gameInfo.white = StrSave("?");
16349         gameInfo.black = StrSave("?");
16350         break;
16351
16352       default:
16353         break;
16354     }
16355 }
16356
16357 void
16358 ReplaceComment (int index, char *text)
16359 {
16360     int len;
16361     char *p;
16362     float score;
16363
16364     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16365        pvInfoList[index-1].depth == len &&
16366        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16367        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16368     while (*text == '\n') text++;
16369     len = strlen(text);
16370     while (len > 0 && text[len - 1] == '\n') len--;
16371
16372     if (commentList[index] != NULL)
16373       free(commentList[index]);
16374
16375     if (len == 0) {
16376         commentList[index] = NULL;
16377         return;
16378     }
16379   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16380       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16381       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16382     commentList[index] = (char *) malloc(len + 2);
16383     strncpy(commentList[index], text, len);
16384     commentList[index][len] = '\n';
16385     commentList[index][len + 1] = NULLCHAR;
16386   } else {
16387     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16388     char *p;
16389     commentList[index] = (char *) malloc(len + 7);
16390     safeStrCpy(commentList[index], "{\n", 3);
16391     safeStrCpy(commentList[index]+2, text, len+1);
16392     commentList[index][len+2] = NULLCHAR;
16393     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16394     strcat(commentList[index], "\n}\n");
16395   }
16396 }
16397
16398 void
16399 CrushCRs (char *text)
16400 {
16401   char *p = text;
16402   char *q = text;
16403   char ch;
16404
16405   do {
16406     ch = *p++;
16407     if (ch == '\r') continue;
16408     *q++ = ch;
16409   } while (ch != '\0');
16410 }
16411
16412 void
16413 AppendComment (int index, char *text, Boolean addBraces)
16414 /* addBraces  tells if we should add {} */
16415 {
16416     int oldlen, len;
16417     char *old;
16418
16419 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16420     if(addBraces == 3) addBraces = 0; else // force appending literally
16421     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16422
16423     CrushCRs(text);
16424     while (*text == '\n') text++;
16425     len = strlen(text);
16426     while (len > 0 && text[len - 1] == '\n') len--;
16427     text[len] = NULLCHAR;
16428
16429     if (len == 0) return;
16430
16431     if (commentList[index] != NULL) {
16432       Boolean addClosingBrace = addBraces;
16433         old = commentList[index];
16434         oldlen = strlen(old);
16435         while(commentList[index][oldlen-1] ==  '\n')
16436           commentList[index][--oldlen] = NULLCHAR;
16437         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16438         safeStrCpy(commentList[index], old, oldlen + len + 6);
16439         free(old);
16440         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16441         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16442           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16443           while (*text == '\n') { text++; len--; }
16444           commentList[index][--oldlen] = NULLCHAR;
16445       }
16446         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16447         else          strcat(commentList[index], "\n");
16448         strcat(commentList[index], text);
16449         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16450         else          strcat(commentList[index], "\n");
16451     } else {
16452         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16453         if(addBraces)
16454           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16455         else commentList[index][0] = NULLCHAR;
16456         strcat(commentList[index], text);
16457         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16458         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16459     }
16460 }
16461
16462 static char *
16463 FindStr (char * text, char * sub_text)
16464 {
16465     char * result = strstr( text, sub_text );
16466
16467     if( result != NULL ) {
16468         result += strlen( sub_text );
16469     }
16470
16471     return result;
16472 }
16473
16474 /* [AS] Try to extract PV info from PGN comment */
16475 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16476 char *
16477 GetInfoFromComment (int index, char * text)
16478 {
16479     char * sep = text, *p;
16480
16481     if( text != NULL && index > 0 ) {
16482         int score = 0;
16483         int depth = 0;
16484         int time = -1, sec = 0, deci;
16485         char * s_eval = FindStr( text, "[%eval " );
16486         char * s_emt = FindStr( text, "[%emt " );
16487 #if 0
16488         if( s_eval != NULL || s_emt != NULL ) {
16489 #else
16490         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16491 #endif
16492             /* New style */
16493             char delim;
16494
16495             if( s_eval != NULL ) {
16496                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16497                     return text;
16498                 }
16499
16500                 if( delim != ']' ) {
16501                     return text;
16502                 }
16503             }
16504
16505             if( s_emt != NULL ) {
16506             }
16507                 return text;
16508         }
16509         else {
16510             /* We expect something like: [+|-]nnn.nn/dd */
16511             int score_lo = 0;
16512
16513             if(*text != '{') return text; // [HGM] braces: must be normal comment
16514
16515             sep = strchr( text, '/' );
16516             if( sep == NULL || sep < (text+4) ) {
16517                 return text;
16518             }
16519
16520             p = text;
16521             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16522             if(p[1] == '(') { // comment starts with PV
16523                p = strchr(p, ')'); // locate end of PV
16524                if(p == NULL || sep < p+5) return text;
16525                // at this point we have something like "{(.*) +0.23/6 ..."
16526                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16527                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16528                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16529             }
16530             time = -1; sec = -1; deci = -1;
16531             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16532                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16533                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16534                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16535                 return text;
16536             }
16537
16538             if( score_lo < 0 || score_lo >= 100 ) {
16539                 return text;
16540             }
16541
16542             if(sec >= 0) time = 600*time + 10*sec; else
16543             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16544
16545             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16546
16547             /* [HGM] PV time: now locate end of PV info */
16548             while( *++sep >= '0' && *sep <= '9'); // strip depth
16549             if(time >= 0)
16550             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16551             if(sec >= 0)
16552             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16553             if(deci >= 0)
16554             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16555             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16556         }
16557
16558         if( depth <= 0 ) {
16559             return text;
16560         }
16561
16562         if( time < 0 ) {
16563             time = -1;
16564         }
16565
16566         pvInfoList[index-1].depth = depth;
16567         pvInfoList[index-1].score = score;
16568         pvInfoList[index-1].time  = 10*time; // centi-sec
16569         if(*sep == '}') *sep = 0; else *--sep = '{';
16570         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16571     }
16572     return sep;
16573 }
16574
16575 void
16576 SendToProgram (char *message, ChessProgramState *cps)
16577 {
16578     int count, outCount, error;
16579     char buf[MSG_SIZ];
16580
16581     if (cps->pr == NoProc) return;
16582     Attention(cps);
16583
16584     if (appData.debugMode) {
16585         TimeMark now;
16586         GetTimeMark(&now);
16587         fprintf(debugFP, "%ld >%-6s: %s",
16588                 SubtractTimeMarks(&now, &programStartTime),
16589                 cps->which, message);
16590         if(serverFP)
16591             fprintf(serverFP, "%ld >%-6s: %s",
16592                 SubtractTimeMarks(&now, &programStartTime),
16593                 cps->which, message), fflush(serverFP);
16594     }
16595
16596     count = strlen(message);
16597     outCount = OutputToProcess(cps->pr, message, count, &error);
16598     if (outCount < count && !exiting
16599                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16600       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16601       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16602         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16603             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16604                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16605                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16606                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16607             } else {
16608                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16609                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16610                 gameInfo.result = res;
16611             }
16612             gameInfo.resultDetails = StrSave(buf);
16613         }
16614         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16615         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16616     }
16617 }
16618
16619 void
16620 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16621 {
16622     char *end_str;
16623     char buf[MSG_SIZ];
16624     ChessProgramState *cps = (ChessProgramState *)closure;
16625
16626     if (isr != cps->isr) return; /* Killed intentionally */
16627     if (count <= 0) {
16628         if (count == 0) {
16629             RemoveInputSource(cps->isr);
16630             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16631                     _(cps->which), cps->program);
16632             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16633             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16634                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16635                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16636                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16637                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16638                 } else {
16639                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16640                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16641                     gameInfo.result = res;
16642                 }
16643                 gameInfo.resultDetails = StrSave(buf);
16644             }
16645             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16646             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16647         } else {
16648             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16649                     _(cps->which), cps->program);
16650             RemoveInputSource(cps->isr);
16651
16652             /* [AS] Program is misbehaving badly... kill it */
16653             if( count == -2 ) {
16654                 DestroyChildProcess( cps->pr, 9 );
16655                 cps->pr = NoProc;
16656             }
16657
16658             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16659         }
16660         return;
16661     }
16662
16663     if ((end_str = strchr(message, '\r')) != NULL)
16664       *end_str = NULLCHAR;
16665     if ((end_str = strchr(message, '\n')) != NULL)
16666       *end_str = NULLCHAR;
16667
16668     if (appData.debugMode) {
16669         TimeMark now; int print = 1;
16670         char *quote = ""; char c; int i;
16671
16672         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16673                 char start = message[0];
16674                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16675                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16676                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16677                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16678                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16679                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16680                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16681                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16682                    sscanf(message, "hint: %c", &c)!=1 &&
16683                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16684                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16685                     print = (appData.engineComments >= 2);
16686                 }
16687                 message[0] = start; // restore original message
16688         }
16689         if(print) {
16690                 GetTimeMark(&now);
16691                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16692                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16693                         quote,
16694                         message);
16695                 if(serverFP)
16696                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16697                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16698                         quote,
16699                         message), fflush(serverFP);
16700         }
16701     }
16702
16703     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16704     if (appData.icsEngineAnalyze) {
16705         if (strstr(message, "whisper") != NULL ||
16706              strstr(message, "kibitz") != NULL ||
16707             strstr(message, "tellics") != NULL) return;
16708     }
16709
16710     HandleMachineMove(message, cps);
16711 }
16712
16713
16714 void
16715 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16716 {
16717     char buf[MSG_SIZ];
16718     int seconds;
16719
16720     if( timeControl_2 > 0 ) {
16721         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16722             tc = timeControl_2;
16723         }
16724     }
16725     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16726     inc /= cps->timeOdds;
16727     st  /= cps->timeOdds;
16728
16729     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16730
16731     if (st > 0) {
16732       /* Set exact time per move, normally using st command */
16733       if (cps->stKludge) {
16734         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16735         seconds = st % 60;
16736         if (seconds == 0) {
16737           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16738         } else {
16739           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16740         }
16741       } else {
16742         snprintf(buf, MSG_SIZ, "st %d\n", st);
16743       }
16744     } else {
16745       /* Set conventional or incremental time control, using level command */
16746       if (seconds == 0) {
16747         /* Note old gnuchess bug -- minutes:seconds used to not work.
16748            Fixed in later versions, but still avoid :seconds
16749            when seconds is 0. */
16750         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16751       } else {
16752         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16753                  seconds, inc/1000.);
16754       }
16755     }
16756     SendToProgram(buf, cps);
16757
16758     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16759     /* Orthogonally, limit search to given depth */
16760     if (sd > 0) {
16761       if (cps->sdKludge) {
16762         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16763       } else {
16764         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16765       }
16766       SendToProgram(buf, cps);
16767     }
16768
16769     if(cps->nps >= 0) { /* [HGM] nps */
16770         if(cps->supportsNPS == FALSE)
16771           cps->nps = -1; // don't use if engine explicitly says not supported!
16772         else {
16773           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16774           SendToProgram(buf, cps);
16775         }
16776     }
16777 }
16778
16779 ChessProgramState *
16780 WhitePlayer ()
16781 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16782 {
16783     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16784        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16785         return &second;
16786     return &first;
16787 }
16788
16789 void
16790 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16791 {
16792     char message[MSG_SIZ];
16793     long time, otime;
16794
16795     /* Note: this routine must be called when the clocks are stopped
16796        or when they have *just* been set or switched; otherwise
16797        it will be off by the time since the current tick started.
16798     */
16799     if (machineWhite) {
16800         time = whiteTimeRemaining / 10;
16801         otime = blackTimeRemaining / 10;
16802     } else {
16803         time = blackTimeRemaining / 10;
16804         otime = whiteTimeRemaining / 10;
16805     }
16806     /* [HGM] translate opponent's time by time-odds factor */
16807     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16808
16809     if (time <= 0) time = 1;
16810     if (otime <= 0) otime = 1;
16811
16812     snprintf(message, MSG_SIZ, "time %ld\n", time);
16813     SendToProgram(message, cps);
16814
16815     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16816     SendToProgram(message, cps);
16817 }
16818
16819 char *
16820 EngineDefinedVariant (ChessProgramState *cps, int n)
16821 {   // return name of n-th unknown variant that engine supports
16822     static char buf[MSG_SIZ];
16823     char *p, *s = cps->variants;
16824     if(!s) return NULL;
16825     do { // parse string from variants feature
16826       VariantClass v;
16827         p = strchr(s, ',');
16828         if(p) *p = NULLCHAR;
16829       v = StringToVariant(s);
16830       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16831         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16832             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16833                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16834                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16835                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16836             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16837         }
16838         if(p) *p++ = ',';
16839         if(n < 0) return buf;
16840     } while(s = p);
16841     return NULL;
16842 }
16843
16844 int
16845 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16846 {
16847   char buf[MSG_SIZ];
16848   int len = strlen(name);
16849   int val;
16850
16851   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16852     (*p) += len + 1;
16853     sscanf(*p, "%d", &val);
16854     *loc = (val != 0);
16855     while (**p && **p != ' ')
16856       (*p)++;
16857     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16858     SendToProgram(buf, cps);
16859     return TRUE;
16860   }
16861   return FALSE;
16862 }
16863
16864 int
16865 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16866 {
16867   char buf[MSG_SIZ];
16868   int len = strlen(name);
16869   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16870     (*p) += len + 1;
16871     sscanf(*p, "%d", loc);
16872     while (**p && **p != ' ') (*p)++;
16873     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16874     SendToProgram(buf, cps);
16875     return TRUE;
16876   }
16877   return FALSE;
16878 }
16879
16880 int
16881 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16882 {
16883   char buf[MSG_SIZ];
16884   int len = strlen(name);
16885   if (strncmp((*p), name, len) == 0
16886       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16887     (*p) += len + 2;
16888     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16889     sscanf(*p, "%[^\"]", *loc);
16890     while (**p && **p != '\"') (*p)++;
16891     if (**p == '\"') (*p)++;
16892     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16893     SendToProgram(buf, cps);
16894     return TRUE;
16895   }
16896   return FALSE;
16897 }
16898
16899 int
16900 ParseOption (Option *opt, ChessProgramState *cps)
16901 // [HGM] options: process the string that defines an engine option, and determine
16902 // name, type, default value, and allowed value range
16903 {
16904         char *p, *q, buf[MSG_SIZ];
16905         int n, min = (-1)<<31, max = 1<<31, def;
16906
16907         if(p = strstr(opt->name, " -spin ")) {
16908             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16909             if(max < min) max = min; // enforce consistency
16910             if(def < min) def = min;
16911             if(def > max) def = max;
16912             opt->value = def;
16913             opt->min = min;
16914             opt->max = max;
16915             opt->type = Spin;
16916         } else if((p = strstr(opt->name, " -slider "))) {
16917             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16918             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16919             if(max < min) max = min; // enforce consistency
16920             if(def < min) def = min;
16921             if(def > max) def = max;
16922             opt->value = def;
16923             opt->min = min;
16924             opt->max = max;
16925             opt->type = Spin; // Slider;
16926         } else if((p = strstr(opt->name, " -string "))) {
16927             opt->textValue = p+9;
16928             opt->type = TextBox;
16929         } else if((p = strstr(opt->name, " -file "))) {
16930             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16931             opt->textValue = p+7;
16932             opt->type = FileName; // FileName;
16933         } else if((p = strstr(opt->name, " -path "))) {
16934             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16935             opt->textValue = p+7;
16936             opt->type = PathName; // PathName;
16937         } else if(p = strstr(opt->name, " -check ")) {
16938             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16939             opt->value = (def != 0);
16940             opt->type = CheckBox;
16941         } else if(p = strstr(opt->name, " -combo ")) {
16942             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16943             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16944             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16945             opt->value = n = 0;
16946             while(q = StrStr(q, " /// ")) {
16947                 n++; *q = 0;    // count choices, and null-terminate each of them
16948                 q += 5;
16949                 if(*q == '*') { // remember default, which is marked with * prefix
16950                     q++;
16951                     opt->value = n;
16952                 }
16953                 cps->comboList[cps->comboCnt++] = q;
16954             }
16955             cps->comboList[cps->comboCnt++] = NULL;
16956             opt->max = n + 1;
16957             opt->type = ComboBox;
16958         } else if(p = strstr(opt->name, " -button")) {
16959             opt->type = Button;
16960         } else if(p = strstr(opt->name, " -save")) {
16961             opt->type = SaveButton;
16962         } else return FALSE;
16963         *p = 0; // terminate option name
16964         // now look if the command-line options define a setting for this engine option.
16965         if(cps->optionSettings && cps->optionSettings[0])
16966             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16967         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16968           snprintf(buf, MSG_SIZ, "option %s", p);
16969                 if(p = strstr(buf, ",")) *p = 0;
16970                 if(q = strchr(buf, '=')) switch(opt->type) {
16971                     case ComboBox:
16972                         for(n=0; n<opt->max; n++)
16973                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16974                         break;
16975                     case TextBox:
16976                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16977                         break;
16978                     case Spin:
16979                     case CheckBox:
16980                         opt->value = atoi(q+1);
16981                     default:
16982                         break;
16983                 }
16984                 strcat(buf, "\n");
16985                 SendToProgram(buf, cps);
16986         }
16987         return TRUE;
16988 }
16989
16990 void
16991 FeatureDone (ChessProgramState *cps, int val)
16992 {
16993   DelayedEventCallback cb = GetDelayedEvent();
16994   if ((cb == InitBackEnd3 && cps == &first) ||
16995       (cb == SettingsMenuIfReady && cps == &second) ||
16996       (cb == LoadEngine) ||
16997       (cb == TwoMachinesEventIfReady)) {
16998     CancelDelayedEvent();
16999     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17000   }
17001   cps->initDone = val;
17002   if(val) cps->reload = FALSE;
17003 }
17004
17005 /* Parse feature command from engine */
17006 void
17007 ParseFeatures (char *args, ChessProgramState *cps)
17008 {
17009   char *p = args;
17010   char *q = NULL;
17011   int val;
17012   char buf[MSG_SIZ];
17013
17014   for (;;) {
17015     while (*p == ' ') p++;
17016     if (*p == NULLCHAR) return;
17017
17018     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17019     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17020     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17021     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17022     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17023     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17024     if (BoolFeature(&p, "reuse", &val, cps)) {
17025       /* Engine can disable reuse, but can't enable it if user said no */
17026       if (!val) cps->reuse = FALSE;
17027       continue;
17028     }
17029     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17030     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17031       if (gameMode == TwoMachinesPlay) {
17032         DisplayTwoMachinesTitle();
17033       } else {
17034         DisplayTitle("");
17035       }
17036       continue;
17037     }
17038     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17039     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17040     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17041     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17042     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17043     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17044     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17045     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17046     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17047     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17048     if (IntFeature(&p, "done", &val, cps)) {
17049       FeatureDone(cps, val);
17050       continue;
17051     }
17052     /* Added by Tord: */
17053     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17054     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17055     /* End of additions by Tord */
17056
17057     /* [HGM] added features: */
17058     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17059     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17060     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17061     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17062     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17063     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17064     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17065     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17066         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17067         FREE(cps->option[cps->nrOptions].name);
17068         cps->option[cps->nrOptions].name = q; q = NULL;
17069         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17070           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17071             SendToProgram(buf, cps);
17072             continue;
17073         }
17074         if(cps->nrOptions >= MAX_OPTIONS) {
17075             cps->nrOptions--;
17076             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17077             DisplayError(buf, 0);
17078         }
17079         continue;
17080     }
17081     /* End of additions by HGM */
17082
17083     /* unknown feature: complain and skip */
17084     q = p;
17085     while (*q && *q != '=') q++;
17086     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17087     SendToProgram(buf, cps);
17088     p = q;
17089     if (*p == '=') {
17090       p++;
17091       if (*p == '\"') {
17092         p++;
17093         while (*p && *p != '\"') p++;
17094         if (*p == '\"') p++;
17095       } else {
17096         while (*p && *p != ' ') p++;
17097       }
17098     }
17099   }
17100
17101 }
17102
17103 void
17104 PeriodicUpdatesEvent (int newState)
17105 {
17106     if (newState == appData.periodicUpdates)
17107       return;
17108
17109     appData.periodicUpdates=newState;
17110
17111     /* Display type changes, so update it now */
17112 //    DisplayAnalysis();
17113
17114     /* Get the ball rolling again... */
17115     if (newState) {
17116         AnalysisPeriodicEvent(1);
17117         StartAnalysisClock();
17118     }
17119 }
17120
17121 void
17122 PonderNextMoveEvent (int newState)
17123 {
17124     if (newState == appData.ponderNextMove) return;
17125     if (gameMode == EditPosition) EditPositionDone(TRUE);
17126     if (newState) {
17127         SendToProgram("hard\n", &first);
17128         if (gameMode == TwoMachinesPlay) {
17129             SendToProgram("hard\n", &second);
17130         }
17131     } else {
17132         SendToProgram("easy\n", &first);
17133         thinkOutput[0] = NULLCHAR;
17134         if (gameMode == TwoMachinesPlay) {
17135             SendToProgram("easy\n", &second);
17136         }
17137     }
17138     appData.ponderNextMove = newState;
17139 }
17140
17141 void
17142 NewSettingEvent (int option, int *feature, char *command, int value)
17143 {
17144     char buf[MSG_SIZ];
17145
17146     if (gameMode == EditPosition) EditPositionDone(TRUE);
17147     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17148     if(feature == NULL || *feature) SendToProgram(buf, &first);
17149     if (gameMode == TwoMachinesPlay) {
17150         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17151     }
17152 }
17153
17154 void
17155 ShowThinkingEvent ()
17156 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17157 {
17158     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17159     int newState = appData.showThinking
17160         // [HGM] thinking: other features now need thinking output as well
17161         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17162
17163     if (oldState == newState) return;
17164     oldState = newState;
17165     if (gameMode == EditPosition) EditPositionDone(TRUE);
17166     if (oldState) {
17167         SendToProgram("post\n", &first);
17168         if (gameMode == TwoMachinesPlay) {
17169             SendToProgram("post\n", &second);
17170         }
17171     } else {
17172         SendToProgram("nopost\n", &first);
17173         thinkOutput[0] = NULLCHAR;
17174         if (gameMode == TwoMachinesPlay) {
17175             SendToProgram("nopost\n", &second);
17176         }
17177     }
17178 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17179 }
17180
17181 void
17182 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17183 {
17184   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17185   if (pr == NoProc) return;
17186   AskQuestion(title, question, replyPrefix, pr);
17187 }
17188
17189 void
17190 TypeInEvent (char firstChar)
17191 {
17192     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17193         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17194         gameMode == AnalyzeMode || gameMode == EditGame ||
17195         gameMode == EditPosition || gameMode == IcsExamining ||
17196         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17197         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17198                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17199                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17200         gameMode == Training) PopUpMoveDialog(firstChar);
17201 }
17202
17203 void
17204 TypeInDoneEvent (char *move)
17205 {
17206         Board board;
17207         int n, fromX, fromY, toX, toY;
17208         char promoChar;
17209         ChessMove moveType;
17210
17211         // [HGM] FENedit
17212         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17213                 EditPositionPasteFEN(move);
17214                 return;
17215         }
17216         // [HGM] movenum: allow move number to be typed in any mode
17217         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17218           ToNrEvent(2*n-1);
17219           return;
17220         }
17221         // undocumented kludge: allow command-line option to be typed in!
17222         // (potentially fatal, and does not implement the effect of the option.)
17223         // should only be used for options that are values on which future decisions will be made,
17224         // and definitely not on options that would be used during initialization.
17225         if(strstr(move, "!!! -") == move) {
17226             ParseArgsFromString(move+4);
17227             return;
17228         }
17229
17230       if (gameMode != EditGame && currentMove != forwardMostMove &&
17231         gameMode != Training) {
17232         DisplayMoveError(_("Displayed move is not current"));
17233       } else {
17234         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17235           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17236         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17237         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17238           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17239           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17240         } else {
17241           DisplayMoveError(_("Could not parse move"));
17242         }
17243       }
17244 }
17245
17246 void
17247 DisplayMove (int moveNumber)
17248 {
17249     char message[MSG_SIZ];
17250     char res[MSG_SIZ];
17251     char cpThinkOutput[MSG_SIZ];
17252
17253     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17254
17255     if (moveNumber == forwardMostMove - 1 ||
17256         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17257
17258         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17259
17260         if (strchr(cpThinkOutput, '\n')) {
17261             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17262         }
17263     } else {
17264         *cpThinkOutput = NULLCHAR;
17265     }
17266
17267     /* [AS] Hide thinking from human user */
17268     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17269         *cpThinkOutput = NULLCHAR;
17270         if( thinkOutput[0] != NULLCHAR ) {
17271             int i;
17272
17273             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17274                 cpThinkOutput[i] = '.';
17275             }
17276             cpThinkOutput[i] = NULLCHAR;
17277             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17278         }
17279     }
17280
17281     if (moveNumber == forwardMostMove - 1 &&
17282         gameInfo.resultDetails != NULL) {
17283         if (gameInfo.resultDetails[0] == NULLCHAR) {
17284           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17285         } else {
17286           snprintf(res, MSG_SIZ, " {%s} %s",
17287                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17288         }
17289     } else {
17290         res[0] = NULLCHAR;
17291     }
17292
17293     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17294         DisplayMessage(res, cpThinkOutput);
17295     } else {
17296       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17297                 WhiteOnMove(moveNumber) ? " " : ".. ",
17298                 parseList[moveNumber], res);
17299         DisplayMessage(message, cpThinkOutput);
17300     }
17301 }
17302
17303 void
17304 DisplayComment (int moveNumber, char *text)
17305 {
17306     char title[MSG_SIZ];
17307
17308     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17309       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17310     } else {
17311       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17312               WhiteOnMove(moveNumber) ? " " : ".. ",
17313               parseList[moveNumber]);
17314     }
17315     if (text != NULL && (appData.autoDisplayComment || commentUp))
17316         CommentPopUp(title, text);
17317 }
17318
17319 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17320  * might be busy thinking or pondering.  It can be omitted if your
17321  * gnuchess is configured to stop thinking immediately on any user
17322  * input.  However, that gnuchess feature depends on the FIONREAD
17323  * ioctl, which does not work properly on some flavors of Unix.
17324  */
17325 void
17326 Attention (ChessProgramState *cps)
17327 {
17328 #if ATTENTION
17329     if (!cps->useSigint) return;
17330     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17331     switch (gameMode) {
17332       case MachinePlaysWhite:
17333       case MachinePlaysBlack:
17334       case TwoMachinesPlay:
17335       case IcsPlayingWhite:
17336       case IcsPlayingBlack:
17337       case AnalyzeMode:
17338       case AnalyzeFile:
17339         /* Skip if we know it isn't thinking */
17340         if (!cps->maybeThinking) return;
17341         if (appData.debugMode)
17342           fprintf(debugFP, "Interrupting %s\n", cps->which);
17343         InterruptChildProcess(cps->pr);
17344         cps->maybeThinking = FALSE;
17345         break;
17346       default:
17347         break;
17348     }
17349 #endif /*ATTENTION*/
17350 }
17351
17352 int
17353 CheckFlags ()
17354 {
17355     if (whiteTimeRemaining <= 0) {
17356         if (!whiteFlag) {
17357             whiteFlag = TRUE;
17358             if (appData.icsActive) {
17359                 if (appData.autoCallFlag &&
17360                     gameMode == IcsPlayingBlack && !blackFlag) {
17361                   SendToICS(ics_prefix);
17362                   SendToICS("flag\n");
17363                 }
17364             } else {
17365                 if (blackFlag) {
17366                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17367                 } else {
17368                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17369                     if (appData.autoCallFlag) {
17370                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17371                         return TRUE;
17372                     }
17373                 }
17374             }
17375         }
17376     }
17377     if (blackTimeRemaining <= 0) {
17378         if (!blackFlag) {
17379             blackFlag = TRUE;
17380             if (appData.icsActive) {
17381                 if (appData.autoCallFlag &&
17382                     gameMode == IcsPlayingWhite && !whiteFlag) {
17383                   SendToICS(ics_prefix);
17384                   SendToICS("flag\n");
17385                 }
17386             } else {
17387                 if (whiteFlag) {
17388                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17389                 } else {
17390                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17391                     if (appData.autoCallFlag) {
17392                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17393                         return TRUE;
17394                     }
17395                 }
17396             }
17397         }
17398     }
17399     return FALSE;
17400 }
17401
17402 void
17403 CheckTimeControl ()
17404 {
17405     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17406         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17407
17408     /*
17409      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17410      */
17411     if ( !WhiteOnMove(forwardMostMove) ) {
17412         /* White made time control */
17413         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17414         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17415         /* [HGM] time odds: correct new time quota for time odds! */
17416                                             / WhitePlayer()->timeOdds;
17417         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17418     } else {
17419         lastBlack -= blackTimeRemaining;
17420         /* Black made time control */
17421         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17422                                             / WhitePlayer()->other->timeOdds;
17423         lastWhite = whiteTimeRemaining;
17424     }
17425 }
17426
17427 void
17428 DisplayBothClocks ()
17429 {
17430     int wom = gameMode == EditPosition ?
17431       !blackPlaysFirst : WhiteOnMove(currentMove);
17432     DisplayWhiteClock(whiteTimeRemaining, wom);
17433     DisplayBlackClock(blackTimeRemaining, !wom);
17434 }
17435
17436
17437 /* Timekeeping seems to be a portability nightmare.  I think everyone
17438    has ftime(), but I'm really not sure, so I'm including some ifdefs
17439    to use other calls if you don't.  Clocks will be less accurate if
17440    you have neither ftime nor gettimeofday.
17441 */
17442
17443 /* VS 2008 requires the #include outside of the function */
17444 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17445 #include <sys/timeb.h>
17446 #endif
17447
17448 /* Get the current time as a TimeMark */
17449 void
17450 GetTimeMark (TimeMark *tm)
17451 {
17452 #if HAVE_GETTIMEOFDAY
17453
17454     struct timeval timeVal;
17455     struct timezone timeZone;
17456
17457     gettimeofday(&timeVal, &timeZone);
17458     tm->sec = (long) timeVal.tv_sec;
17459     tm->ms = (int) (timeVal.tv_usec / 1000L);
17460
17461 #else /*!HAVE_GETTIMEOFDAY*/
17462 #if HAVE_FTIME
17463
17464 // include <sys/timeb.h> / moved to just above start of function
17465     struct timeb timeB;
17466
17467     ftime(&timeB);
17468     tm->sec = (long) timeB.time;
17469     tm->ms = (int) timeB.millitm;
17470
17471 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17472     tm->sec = (long) time(NULL);
17473     tm->ms = 0;
17474 #endif
17475 #endif
17476 }
17477
17478 /* Return the difference in milliseconds between two
17479    time marks.  We assume the difference will fit in a long!
17480 */
17481 long
17482 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17483 {
17484     return 1000L*(tm2->sec - tm1->sec) +
17485            (long) (tm2->ms - tm1->ms);
17486 }
17487
17488
17489 /*
17490  * Code to manage the game clocks.
17491  *
17492  * In tournament play, black starts the clock and then white makes a move.
17493  * We give the human user a slight advantage if he is playing white---the
17494  * clocks don't run until he makes his first move, so it takes zero time.
17495  * Also, we don't account for network lag, so we could get out of sync
17496  * with GNU Chess's clock -- but then, referees are always right.
17497  */
17498
17499 static TimeMark tickStartTM;
17500 static long intendedTickLength;
17501
17502 long
17503 NextTickLength (long timeRemaining)
17504 {
17505     long nominalTickLength, nextTickLength;
17506
17507     if (timeRemaining > 0L && timeRemaining <= 10000L)
17508       nominalTickLength = 100L;
17509     else
17510       nominalTickLength = 1000L;
17511     nextTickLength = timeRemaining % nominalTickLength;
17512     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17513
17514     return nextTickLength;
17515 }
17516
17517 /* Adjust clock one minute up or down */
17518 void
17519 AdjustClock (Boolean which, int dir)
17520 {
17521     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17522     if(which) blackTimeRemaining += 60000*dir;
17523     else      whiteTimeRemaining += 60000*dir;
17524     DisplayBothClocks();
17525     adjustedClock = TRUE;
17526 }
17527
17528 /* Stop clocks and reset to a fresh time control */
17529 void
17530 ResetClocks ()
17531 {
17532     (void) StopClockTimer();
17533     if (appData.icsActive) {
17534         whiteTimeRemaining = blackTimeRemaining = 0;
17535     } else if (searchTime) {
17536         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17537         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17538     } else { /* [HGM] correct new time quote for time odds */
17539         whiteTC = blackTC = fullTimeControlString;
17540         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17541         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17542     }
17543     if (whiteFlag || blackFlag) {
17544         DisplayTitle("");
17545         whiteFlag = blackFlag = FALSE;
17546     }
17547     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17548     DisplayBothClocks();
17549     adjustedClock = FALSE;
17550 }
17551
17552 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17553
17554 /* Decrement running clock by amount of time that has passed */
17555 void
17556 DecrementClocks ()
17557 {
17558     long timeRemaining;
17559     long lastTickLength, fudge;
17560     TimeMark now;
17561
17562     if (!appData.clockMode) return;
17563     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17564
17565     GetTimeMark(&now);
17566
17567     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17568
17569     /* Fudge if we woke up a little too soon */
17570     fudge = intendedTickLength - lastTickLength;
17571     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17572
17573     if (WhiteOnMove(forwardMostMove)) {
17574         if(whiteNPS >= 0) lastTickLength = 0;
17575         timeRemaining = whiteTimeRemaining -= lastTickLength;
17576         if(timeRemaining < 0 && !appData.icsActive) {
17577             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17578             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17579                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17580                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17581             }
17582         }
17583         DisplayWhiteClock(whiteTimeRemaining - fudge,
17584                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17585     } else {
17586         if(blackNPS >= 0) lastTickLength = 0;
17587         timeRemaining = blackTimeRemaining -= lastTickLength;
17588         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17589             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17590             if(suddenDeath) {
17591                 blackStartMove = forwardMostMove;
17592                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17593             }
17594         }
17595         DisplayBlackClock(blackTimeRemaining - fudge,
17596                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17597     }
17598     if (CheckFlags()) return;
17599
17600     if(twoBoards) { // count down secondary board's clocks as well
17601         activePartnerTime -= lastTickLength;
17602         partnerUp = 1;
17603         if(activePartner == 'W')
17604             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17605         else
17606             DisplayBlackClock(activePartnerTime, TRUE);
17607         partnerUp = 0;
17608     }
17609
17610     tickStartTM = now;
17611     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17612     StartClockTimer(intendedTickLength);
17613
17614     /* if the time remaining has fallen below the alarm threshold, sound the
17615      * alarm. if the alarm has sounded and (due to a takeback or time control
17616      * with increment) the time remaining has increased to a level above the
17617      * threshold, reset the alarm so it can sound again.
17618      */
17619
17620     if (appData.icsActive && appData.icsAlarm) {
17621
17622         /* make sure we are dealing with the user's clock */
17623         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17624                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17625            )) return;
17626
17627         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17628             alarmSounded = FALSE;
17629         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17630             PlayAlarmSound();
17631             alarmSounded = TRUE;
17632         }
17633     }
17634 }
17635
17636
17637 /* A player has just moved, so stop the previously running
17638    clock and (if in clock mode) start the other one.
17639    We redisplay both clocks in case we're in ICS mode, because
17640    ICS gives us an update to both clocks after every move.
17641    Note that this routine is called *after* forwardMostMove
17642    is updated, so the last fractional tick must be subtracted
17643    from the color that is *not* on move now.
17644 */
17645 void
17646 SwitchClocks (int newMoveNr)
17647 {
17648     long lastTickLength;
17649     TimeMark now;
17650     int flagged = FALSE;
17651
17652     GetTimeMark(&now);
17653
17654     if (StopClockTimer() && appData.clockMode) {
17655         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17656         if (!WhiteOnMove(forwardMostMove)) {
17657             if(blackNPS >= 0) lastTickLength = 0;
17658             blackTimeRemaining -= lastTickLength;
17659            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17660 //         if(pvInfoList[forwardMostMove].time == -1)
17661                  pvInfoList[forwardMostMove].time =               // use GUI time
17662                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17663         } else {
17664            if(whiteNPS >= 0) lastTickLength = 0;
17665            whiteTimeRemaining -= lastTickLength;
17666            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17667 //         if(pvInfoList[forwardMostMove].time == -1)
17668                  pvInfoList[forwardMostMove].time =
17669                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17670         }
17671         flagged = CheckFlags();
17672     }
17673     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17674     CheckTimeControl();
17675
17676     if (flagged || !appData.clockMode) return;
17677
17678     switch (gameMode) {
17679       case MachinePlaysBlack:
17680       case MachinePlaysWhite:
17681       case BeginningOfGame:
17682         if (pausing) return;
17683         break;
17684
17685       case EditGame:
17686       case PlayFromGameFile:
17687       case IcsExamining:
17688         return;
17689
17690       default:
17691         break;
17692     }
17693
17694     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17695         if(WhiteOnMove(forwardMostMove))
17696              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17697         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17698     }
17699
17700     tickStartTM = now;
17701     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17702       whiteTimeRemaining : blackTimeRemaining);
17703     StartClockTimer(intendedTickLength);
17704 }
17705
17706
17707 /* Stop both clocks */
17708 void
17709 StopClocks ()
17710 {
17711     long lastTickLength;
17712     TimeMark now;
17713
17714     if (!StopClockTimer()) return;
17715     if (!appData.clockMode) return;
17716
17717     GetTimeMark(&now);
17718
17719     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17720     if (WhiteOnMove(forwardMostMove)) {
17721         if(whiteNPS >= 0) lastTickLength = 0;
17722         whiteTimeRemaining -= lastTickLength;
17723         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17724     } else {
17725         if(blackNPS >= 0) lastTickLength = 0;
17726         blackTimeRemaining -= lastTickLength;
17727         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17728     }
17729     CheckFlags();
17730 }
17731
17732 /* Start clock of player on move.  Time may have been reset, so
17733    if clock is already running, stop and restart it. */
17734 void
17735 StartClocks ()
17736 {
17737     (void) StopClockTimer(); /* in case it was running already */
17738     DisplayBothClocks();
17739     if (CheckFlags()) return;
17740
17741     if (!appData.clockMode) return;
17742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17743
17744     GetTimeMark(&tickStartTM);
17745     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17746       whiteTimeRemaining : blackTimeRemaining);
17747
17748    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17749     whiteNPS = blackNPS = -1;
17750     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17751        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17752         whiteNPS = first.nps;
17753     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17754        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17755         blackNPS = first.nps;
17756     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17757         whiteNPS = second.nps;
17758     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17759         blackNPS = second.nps;
17760     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17761
17762     StartClockTimer(intendedTickLength);
17763 }
17764
17765 char *
17766 TimeString (long ms)
17767 {
17768     long second, minute, hour, day;
17769     char *sign = "";
17770     static char buf[32];
17771
17772     if (ms > 0 && ms <= 9900) {
17773       /* convert milliseconds to tenths, rounding up */
17774       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17775
17776       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17777       return buf;
17778     }
17779
17780     /* convert milliseconds to seconds, rounding up */
17781     /* use floating point to avoid strangeness of integer division
17782        with negative dividends on many machines */
17783     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17784
17785     if (second < 0) {
17786         sign = "-";
17787         second = -second;
17788     }
17789
17790     day = second / (60 * 60 * 24);
17791     second = second % (60 * 60 * 24);
17792     hour = second / (60 * 60);
17793     second = second % (60 * 60);
17794     minute = second / 60;
17795     second = second % 60;
17796
17797     if (day > 0)
17798       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17799               sign, day, hour, minute, second);
17800     else if (hour > 0)
17801       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17802     else
17803       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17804
17805     return buf;
17806 }
17807
17808
17809 /*
17810  * This is necessary because some C libraries aren't ANSI C compliant yet.
17811  */
17812 char *
17813 StrStr (char *string, char *match)
17814 {
17815     int i, length;
17816
17817     length = strlen(match);
17818
17819     for (i = strlen(string) - length; i >= 0; i--, string++)
17820       if (!strncmp(match, string, length))
17821         return string;
17822
17823     return NULL;
17824 }
17825
17826 char *
17827 StrCaseStr (char *string, char *match)
17828 {
17829     int i, j, length;
17830
17831     length = strlen(match);
17832
17833     for (i = strlen(string) - length; i >= 0; i--, string++) {
17834         for (j = 0; j < length; j++) {
17835             if (ToLower(match[j]) != ToLower(string[j]))
17836               break;
17837         }
17838         if (j == length) return string;
17839     }
17840
17841     return NULL;
17842 }
17843
17844 #ifndef _amigados
17845 int
17846 StrCaseCmp (char *s1, char *s2)
17847 {
17848     char c1, c2;
17849
17850     for (;;) {
17851         c1 = ToLower(*s1++);
17852         c2 = ToLower(*s2++);
17853         if (c1 > c2) return 1;
17854         if (c1 < c2) return -1;
17855         if (c1 == NULLCHAR) return 0;
17856     }
17857 }
17858
17859
17860 int
17861 ToLower (int c)
17862 {
17863     return isupper(c) ? tolower(c) : c;
17864 }
17865
17866
17867 int
17868 ToUpper (int c)
17869 {
17870     return islower(c) ? toupper(c) : c;
17871 }
17872 #endif /* !_amigados    */
17873
17874 char *
17875 StrSave (char *s)
17876 {
17877   char *ret;
17878
17879   if ((ret = (char *) malloc(strlen(s) + 1)))
17880     {
17881       safeStrCpy(ret, s, strlen(s)+1);
17882     }
17883   return ret;
17884 }
17885
17886 char *
17887 StrSavePtr (char *s, char **savePtr)
17888 {
17889     if (*savePtr) {
17890         free(*savePtr);
17891     }
17892     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17893       safeStrCpy(*savePtr, s, strlen(s)+1);
17894     }
17895     return(*savePtr);
17896 }
17897
17898 char *
17899 PGNDate ()
17900 {
17901     time_t clock;
17902     struct tm *tm;
17903     char buf[MSG_SIZ];
17904
17905     clock = time((time_t *)NULL);
17906     tm = localtime(&clock);
17907     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17908             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17909     return StrSave(buf);
17910 }
17911
17912
17913 char *
17914 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17915 {
17916     int i, j, fromX, fromY, toX, toY;
17917     int whiteToPlay, haveRights = nrCastlingRights;
17918     char buf[MSG_SIZ];
17919     char *p, *q;
17920     int emptycount;
17921     ChessSquare piece;
17922
17923     whiteToPlay = (gameMode == EditPosition) ?
17924       !blackPlaysFirst : (move % 2 == 0);
17925     p = buf;
17926
17927     /* Piece placement data */
17928     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17929         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17930         emptycount = 0;
17931         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17932             if (boards[move][i][j] == EmptySquare) {
17933                 emptycount++;
17934             } else { ChessSquare piece = boards[move][i][j];
17935                 if (emptycount > 0) {
17936                     if(emptycount<10) /* [HGM] can be >= 10 */
17937                         *p++ = '0' + emptycount;
17938                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17939                     emptycount = 0;
17940                 }
17941                 if(PieceToChar(piece) == '+') {
17942                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17943                     *p++ = '+';
17944                     piece = (ChessSquare)(CHUDEMOTED piece);
17945                 }
17946                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17947                 if(*p = PieceSuffix(piece)) p++;
17948                 if(p[-1] == '~') {
17949                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17950                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17951                     *p++ = '~';
17952                 }
17953             }
17954         }
17955         if (emptycount > 0) {
17956             if(emptycount<10) /* [HGM] can be >= 10 */
17957                 *p++ = '0' + emptycount;
17958             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17959             emptycount = 0;
17960         }
17961         *p++ = '/';
17962     }
17963     *(p - 1) = ' ';
17964
17965     /* [HGM] print Crazyhouse or Shogi holdings */
17966     if( gameInfo.holdingsWidth ) {
17967         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17968         q = p;
17969         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17970             piece = boards[move][i][BOARD_WIDTH-1];
17971             if( piece != EmptySquare )
17972               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17973                   *p++ = PieceToChar(piece);
17974         }
17975         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17976             piece = boards[move][BOARD_HEIGHT-i-1][0];
17977             if( piece != EmptySquare )
17978               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17979                   *p++ = PieceToChar(piece);
17980         }
17981
17982         if( q == p ) *p++ = '-';
17983         *p++ = ']';
17984         *p++ = ' ';
17985     }
17986
17987     /* Active color */
17988     *p++ = whiteToPlay ? 'w' : 'b';
17989     *p++ = ' ';
17990
17991   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
17992     haveRights = 0; q = p;
17993     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
17994       piece = boards[move][0][i];
17995       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
17996         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
17997       }
17998     }
17999     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18000       piece = boards[move][BOARD_HEIGHT-1][i];
18001       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18002         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18003       }
18004     }
18005     if(p == q) *p++ = '-';
18006     *p++ = ' ';
18007   }
18008
18009   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18010     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18011   } else {
18012   if(haveRights) {
18013      int handW=0, handB=0;
18014      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18015         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18016         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18017      }
18018      q = p;
18019      if(appData.fischerCastling) {
18020         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18021            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18022                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18023         } else {
18024        /* [HGM] write directly from rights */
18025            if(boards[move][CASTLING][2] != NoRights &&
18026               boards[move][CASTLING][0] != NoRights   )
18027                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18028            if(boards[move][CASTLING][2] != NoRights &&
18029               boards[move][CASTLING][1] != NoRights   )
18030                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18031         }
18032         if(handB) {
18033            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18034                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18035         } else {
18036            if(boards[move][CASTLING][5] != NoRights &&
18037               boards[move][CASTLING][3] != NoRights   )
18038                 *p++ = boards[move][CASTLING][3] + AAA;
18039            if(boards[move][CASTLING][5] != NoRights &&
18040               boards[move][CASTLING][4] != NoRights   )
18041                 *p++ = boards[move][CASTLING][4] + AAA;
18042         }
18043      } else {
18044
18045         /* [HGM] write true castling rights */
18046         if( nrCastlingRights == 6 ) {
18047             int q, k=0;
18048             if(boards[move][CASTLING][0] != NoRights &&
18049                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18050             q = (boards[move][CASTLING][1] != NoRights &&
18051                  boards[move][CASTLING][2] != NoRights  );
18052             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18053                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18054                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18055                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18056             }
18057             if(q) *p++ = 'Q';
18058             k = 0;
18059             if(boards[move][CASTLING][3] != NoRights &&
18060                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18061             q = (boards[move][CASTLING][4] != NoRights &&
18062                  boards[move][CASTLING][5] != NoRights  );
18063             if(handB) {
18064                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18065                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18066                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18067             }
18068             if(q) *p++ = 'q';
18069         }
18070      }
18071      if (q == p) *p++ = '-'; /* No castling rights */
18072      *p++ = ' ';
18073   }
18074
18075   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18076      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18077      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18078     /* En passant target square */
18079     if (move > backwardMostMove) {
18080         fromX = moveList[move - 1][0] - AAA;
18081         fromY = moveList[move - 1][1] - ONE;
18082         toX = moveList[move - 1][2] - AAA;
18083         toY = moveList[move - 1][3] - ONE;
18084         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18085             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18086             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18087             fromX == toX) {
18088             /* 2-square pawn move just happened */
18089             *p++ = toX + AAA;
18090             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18091         } else {
18092             *p++ = '-';
18093         }
18094     } else if(move == backwardMostMove) {
18095         // [HGM] perhaps we should always do it like this, and forget the above?
18096         if((signed char)boards[move][EP_STATUS] >= 0) {
18097             *p++ = boards[move][EP_STATUS] + AAA;
18098             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18099         } else {
18100             *p++ = '-';
18101         }
18102     } else {
18103         *p++ = '-';
18104     }
18105     *p++ = ' ';
18106   }
18107   }
18108
18109     if(moveCounts)
18110     {   int i = 0, j=move;
18111
18112         /* [HGM] find reversible plies */
18113         if (appData.debugMode) { int k;
18114             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18115             for(k=backwardMostMove; k<=forwardMostMove; k++)
18116                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18117
18118         }
18119
18120         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18121         if( j == backwardMostMove ) i += initialRulePlies;
18122         sprintf(p, "%d ", i);
18123         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18124
18125         /* Fullmove number */
18126         sprintf(p, "%d", (move / 2) + 1);
18127     } else *--p = NULLCHAR;
18128
18129     return StrSave(buf);
18130 }
18131
18132 Boolean
18133 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18134 {
18135     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18136     char *p, c;
18137     int emptycount, virgin[BOARD_FILES];
18138     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18139
18140     p = fen;
18141
18142     /* Piece placement data */
18143     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18144         j = 0;
18145         for (;;) {
18146             if (*p == '/' || *p == ' ' || *p == '[' ) {
18147                 if(j > w) w = j;
18148                 emptycount = gameInfo.boardWidth - j;
18149                 while (emptycount--)
18150                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18151                 if (*p == '/') p++;
18152                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18153                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18154                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18155                     }
18156                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18157                 }
18158                 break;
18159 #if(BOARD_FILES >= 10)*0
18160             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18161                 p++; emptycount=10;
18162                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18163                 while (emptycount--)
18164                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18165 #endif
18166             } else if (*p == '*') {
18167                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18168             } else if (isdigit(*p)) {
18169                 emptycount = *p++ - '0';
18170                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18171                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18172                 while (emptycount--)
18173                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18174             } else if (*p == '<') {
18175                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18176                 else if (i != 0 || !shuffle) return FALSE;
18177                 p++;
18178             } else if (shuffle && *p == '>') {
18179                 p++; // for now ignore closing shuffle range, and assume rank-end
18180             } else if (*p == '?') {
18181                 if (j >= gameInfo.boardWidth) return FALSE;
18182                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18183                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18184             } else if (*p == '+' || isalpha(*p)) {
18185                 char *q, *s = SUFFIXES;
18186                 if (j >= gameInfo.boardWidth) return FALSE;
18187                 if(*p=='+') {
18188                     char c = *++p;
18189                     if(q = strchr(s, p[1])) p++;
18190                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18191                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18192                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18193                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18194                 } else {
18195                     char c = *p++;
18196                     if(q = strchr(s, *p)) p++;
18197                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18198                 }
18199
18200                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18201                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18202                     piece = (ChessSquare) (PROMOTED piece);
18203                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18204                     p++;
18205                 }
18206                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18207                 if(piece == king) wKingRank = i;
18208                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18209             } else {
18210                 return FALSE;
18211             }
18212         }
18213     }
18214     while (*p == '/' || *p == ' ') p++;
18215
18216     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18217
18218     /* [HGM] by default clear Crazyhouse holdings, if present */
18219     if(gameInfo.holdingsWidth) {
18220        for(i=0; i<BOARD_HEIGHT; i++) {
18221            board[i][0]             = EmptySquare; /* black holdings */
18222            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18223            board[i][1]             = (ChessSquare) 0; /* black counts */
18224            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18225        }
18226     }
18227
18228     /* [HGM] look for Crazyhouse holdings here */
18229     while(*p==' ') p++;
18230     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18231         int swap=0, wcnt=0, bcnt=0;
18232         if(*p == '[') p++;
18233         if(*p == '<') swap++, p++;
18234         if(*p == '-' ) p++; /* empty holdings */ else {
18235             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18236             /* if we would allow FEN reading to set board size, we would   */
18237             /* have to add holdings and shift the board read so far here   */
18238             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18239                 p++;
18240                 if((int) piece >= (int) BlackPawn ) {
18241                     i = (int)piece - (int)BlackPawn;
18242                     i = PieceToNumber((ChessSquare)i);
18243                     if( i >= gameInfo.holdingsSize ) return FALSE;
18244                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18245                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18246                     bcnt++;
18247                 } else {
18248                     i = (int)piece - (int)WhitePawn;
18249                     i = PieceToNumber((ChessSquare)i);
18250                     if( i >= gameInfo.holdingsSize ) return FALSE;
18251                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18252                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18253                     wcnt++;
18254                 }
18255             }
18256             if(subst) { // substitute back-rank question marks by holdings pieces
18257                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18258                     int k, m, n = bcnt + 1;
18259                     if(board[0][j] == ClearBoard) {
18260                         if(!wcnt) return FALSE;
18261                         n = rand() % wcnt;
18262                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18263                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18264                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18265                             break;
18266                         }
18267                     }
18268                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18269                         if(!bcnt) return FALSE;
18270                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18271                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18272                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18273                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18274                             break;
18275                         }
18276                     }
18277                 }
18278                 subst = 0;
18279             }
18280         }
18281         if(*p == ']') p++;
18282     }
18283
18284     if(subst) return FALSE; // substitution requested, but no holdings
18285
18286     while(*p == ' ') p++;
18287
18288     /* Active color */
18289     c = *p++;
18290     if(appData.colorNickNames) {
18291       if( c == appData.colorNickNames[0] ) c = 'w'; else
18292       if( c == appData.colorNickNames[1] ) c = 'b';
18293     }
18294     switch (c) {
18295       case 'w':
18296         *blackPlaysFirst = FALSE;
18297         break;
18298       case 'b':
18299         *blackPlaysFirst = TRUE;
18300         break;
18301       default:
18302         return FALSE;
18303     }
18304
18305     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18306     /* return the extra info in global variiables             */
18307
18308     while(*p==' ') p++;
18309
18310     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18311         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18312         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18313     }
18314
18315     /* set defaults in case FEN is incomplete */
18316     board[EP_STATUS] = EP_UNKNOWN;
18317     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18318     for(i=0; i<nrCastlingRights; i++ ) {
18319         board[CASTLING][i] =
18320             appData.fischerCastling ? NoRights : initialRights[i];
18321     }   /* assume possible unless obviously impossible */
18322     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18323     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18324     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18325                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18326     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18327     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18328     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18329                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18330     FENrulePlies = 0;
18331
18332     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18333       char *q = p;
18334       int w=0, b=0;
18335       while(isalpha(*p)) {
18336         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18337         if(islower(*p)) b |= 1 << (*p++ - 'a');
18338       }
18339       if(*p == '-') p++;
18340       if(p != q) {
18341         board[TOUCHED_W] = ~w;
18342         board[TOUCHED_B] = ~b;
18343         while(*p == ' ') p++;
18344       }
18345     } else
18346
18347     if(nrCastlingRights) {
18348       int fischer = 0;
18349       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18350       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18351           /* castling indicator present, so default becomes no castlings */
18352           for(i=0; i<nrCastlingRights; i++ ) {
18353                  board[CASTLING][i] = NoRights;
18354           }
18355       }
18356       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18357              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18358              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18359              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18360         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18361
18362         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18363             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18364             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18365         }
18366         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18367             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18368         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18369                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18370         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18371                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18372         switch(c) {
18373           case'K':
18374               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18375               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18376               board[CASTLING][2] = whiteKingFile;
18377               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18378               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18379               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18380               break;
18381           case'Q':
18382               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18383               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18384               board[CASTLING][2] = whiteKingFile;
18385               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18386               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18387               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18388               break;
18389           case'k':
18390               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18391               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18392               board[CASTLING][5] = blackKingFile;
18393               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18394               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18395               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18396               break;
18397           case'q':
18398               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18399               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18400               board[CASTLING][5] = blackKingFile;
18401               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18402               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18403               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18404           case '-':
18405               break;
18406           default: /* FRC castlings */
18407               if(c >= 'a') { /* black rights */
18408                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18409                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18410                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18411                   if(i == BOARD_RGHT) break;
18412                   board[CASTLING][5] = i;
18413                   c -= AAA;
18414                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18415                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18416                   if(c > i)
18417                       board[CASTLING][3] = c;
18418                   else
18419                       board[CASTLING][4] = c;
18420               } else { /* white rights */
18421                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18422                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18423                     if(board[0][i] == WhiteKing) break;
18424                   if(i == BOARD_RGHT) break;
18425                   board[CASTLING][2] = i;
18426                   c -= AAA - 'a' + 'A';
18427                   if(board[0][c] >= WhiteKing) break;
18428                   if(c > i)
18429                       board[CASTLING][0] = c;
18430                   else
18431                       board[CASTLING][1] = c;
18432               }
18433         }
18434       }
18435       for(i=0; i<nrCastlingRights; i++)
18436         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18437       if(gameInfo.variant == VariantSChess)
18438         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18439       if(fischer && shuffle) appData.fischerCastling = TRUE;
18440     if (appData.debugMode) {
18441         fprintf(debugFP, "FEN castling rights:");
18442         for(i=0; i<nrCastlingRights; i++)
18443         fprintf(debugFP, " %d", board[CASTLING][i]);
18444         fprintf(debugFP, "\n");
18445     }
18446
18447       while(*p==' ') p++;
18448     }
18449
18450     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18451
18452     /* read e.p. field in games that know e.p. capture */
18453     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18454        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18455        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18456       if(*p=='-') {
18457         p++; board[EP_STATUS] = EP_NONE;
18458       } else {
18459          char c = *p++ - AAA;
18460
18461          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18462          if(*p >= '0' && *p <='9') p++;
18463          board[EP_STATUS] = c;
18464       }
18465     }
18466
18467
18468     if(sscanf(p, "%d", &i) == 1) {
18469         FENrulePlies = i; /* 50-move ply counter */
18470         /* (The move number is still ignored)    */
18471     }
18472
18473     return TRUE;
18474 }
18475
18476 void
18477 EditPositionPasteFEN (char *fen)
18478 {
18479   if (fen != NULL) {
18480     Board initial_position;
18481
18482     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18483       DisplayError(_("Bad FEN position in clipboard"), 0);
18484       return ;
18485     } else {
18486       int savedBlackPlaysFirst = blackPlaysFirst;
18487       EditPositionEvent();
18488       blackPlaysFirst = savedBlackPlaysFirst;
18489       CopyBoard(boards[0], initial_position);
18490       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18491       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18492       DisplayBothClocks();
18493       DrawPosition(FALSE, boards[currentMove]);
18494     }
18495   }
18496 }
18497
18498 static char cseq[12] = "\\   ";
18499
18500 Boolean
18501 set_cont_sequence (char *new_seq)
18502 {
18503     int len;
18504     Boolean ret;
18505
18506     // handle bad attempts to set the sequence
18507         if (!new_seq)
18508                 return 0; // acceptable error - no debug
18509
18510     len = strlen(new_seq);
18511     ret = (len > 0) && (len < sizeof(cseq));
18512     if (ret)
18513       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18514     else if (appData.debugMode)
18515       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18516     return ret;
18517 }
18518
18519 /*
18520     reformat a source message so words don't cross the width boundary.  internal
18521     newlines are not removed.  returns the wrapped size (no null character unless
18522     included in source message).  If dest is NULL, only calculate the size required
18523     for the dest buffer.  lp argument indicats line position upon entry, and it's
18524     passed back upon exit.
18525 */
18526 int
18527 wrap (char *dest, char *src, int count, int width, int *lp)
18528 {
18529     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18530
18531     cseq_len = strlen(cseq);
18532     old_line = line = *lp;
18533     ansi = len = clen = 0;
18534
18535     for (i=0; i < count; i++)
18536     {
18537         if (src[i] == '\033')
18538             ansi = 1;
18539
18540         // if we hit the width, back up
18541         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18542         {
18543             // store i & len in case the word is too long
18544             old_i = i, old_len = len;
18545
18546             // find the end of the last word
18547             while (i && src[i] != ' ' && src[i] != '\n')
18548             {
18549                 i--;
18550                 len--;
18551             }
18552
18553             // word too long?  restore i & len before splitting it
18554             if ((old_i-i+clen) >= width)
18555             {
18556                 i = old_i;
18557                 len = old_len;
18558             }
18559
18560             // extra space?
18561             if (i && src[i-1] == ' ')
18562                 len--;
18563
18564             if (src[i] != ' ' && src[i] != '\n')
18565             {
18566                 i--;
18567                 if (len)
18568                     len--;
18569             }
18570
18571             // now append the newline and continuation sequence
18572             if (dest)
18573                 dest[len] = '\n';
18574             len++;
18575             if (dest)
18576                 strncpy(dest+len, cseq, cseq_len);
18577             len += cseq_len;
18578             line = cseq_len;
18579             clen = cseq_len;
18580             continue;
18581         }
18582
18583         if (dest)
18584             dest[len] = src[i];
18585         len++;
18586         if (!ansi)
18587             line++;
18588         if (src[i] == '\n')
18589             line = 0;
18590         if (src[i] == 'm')
18591             ansi = 0;
18592     }
18593     if (dest && appData.debugMode)
18594     {
18595         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18596             count, width, line, len, *lp);
18597         show_bytes(debugFP, src, count);
18598         fprintf(debugFP, "\ndest: ");
18599         show_bytes(debugFP, dest, len);
18600         fprintf(debugFP, "\n");
18601     }
18602     *lp = dest ? line : old_line;
18603
18604     return len;
18605 }
18606
18607 // [HGM] vari: routines for shelving variations
18608 Boolean modeRestore = FALSE;
18609
18610 void
18611 PushInner (int firstMove, int lastMove)
18612 {
18613         int i, j, nrMoves = lastMove - firstMove;
18614
18615         // push current tail of game on stack
18616         savedResult[storedGames] = gameInfo.result;
18617         savedDetails[storedGames] = gameInfo.resultDetails;
18618         gameInfo.resultDetails = NULL;
18619         savedFirst[storedGames] = firstMove;
18620         savedLast [storedGames] = lastMove;
18621         savedFramePtr[storedGames] = framePtr;
18622         framePtr -= nrMoves; // reserve space for the boards
18623         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18624             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18625             for(j=0; j<MOVE_LEN; j++)
18626                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18627             for(j=0; j<2*MOVE_LEN; j++)
18628                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18629             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18630             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18631             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18632             pvInfoList[firstMove+i-1].depth = 0;
18633             commentList[framePtr+i] = commentList[firstMove+i];
18634             commentList[firstMove+i] = NULL;
18635         }
18636
18637         storedGames++;
18638         forwardMostMove = firstMove; // truncate game so we can start variation
18639 }
18640
18641 void
18642 PushTail (int firstMove, int lastMove)
18643 {
18644         if(appData.icsActive) { // only in local mode
18645                 forwardMostMove = currentMove; // mimic old ICS behavior
18646                 return;
18647         }
18648         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18649
18650         PushInner(firstMove, lastMove);
18651         if(storedGames == 1) GreyRevert(FALSE);
18652         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18653 }
18654
18655 void
18656 PopInner (Boolean annotate)
18657 {
18658         int i, j, nrMoves;
18659         char buf[8000], moveBuf[20];
18660
18661         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18662         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18663         nrMoves = savedLast[storedGames] - currentMove;
18664         if(annotate) {
18665                 int cnt = 10;
18666                 if(!WhiteOnMove(currentMove))
18667                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18668                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18669                 for(i=currentMove; i<forwardMostMove; i++) {
18670                         if(WhiteOnMove(i))
18671                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18672                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18673                         strcat(buf, moveBuf);
18674                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18675                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18676                 }
18677                 strcat(buf, ")");
18678         }
18679         for(i=1; i<=nrMoves; i++) { // copy last variation back
18680             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18681             for(j=0; j<MOVE_LEN; j++)
18682                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18683             for(j=0; j<2*MOVE_LEN; j++)
18684                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18685             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18686             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18687             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18688             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18689             commentList[currentMove+i] = commentList[framePtr+i];
18690             commentList[framePtr+i] = NULL;
18691         }
18692         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18693         framePtr = savedFramePtr[storedGames];
18694         gameInfo.result = savedResult[storedGames];
18695         if(gameInfo.resultDetails != NULL) {
18696             free(gameInfo.resultDetails);
18697       }
18698         gameInfo.resultDetails = savedDetails[storedGames];
18699         forwardMostMove = currentMove + nrMoves;
18700 }
18701
18702 Boolean
18703 PopTail (Boolean annotate)
18704 {
18705         if(appData.icsActive) return FALSE; // only in local mode
18706         if(!storedGames) return FALSE; // sanity
18707         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18708
18709         PopInner(annotate);
18710         if(currentMove < forwardMostMove) ForwardEvent(); else
18711         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18712
18713         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18714         return TRUE;
18715 }
18716
18717 void
18718 CleanupTail ()
18719 {       // remove all shelved variations
18720         int i;
18721         for(i=0; i<storedGames; i++) {
18722             if(savedDetails[i])
18723                 free(savedDetails[i]);
18724             savedDetails[i] = NULL;
18725         }
18726         for(i=framePtr; i<MAX_MOVES; i++) {
18727                 if(commentList[i]) free(commentList[i]);
18728                 commentList[i] = NULL;
18729         }
18730         framePtr = MAX_MOVES-1;
18731         storedGames = 0;
18732 }
18733
18734 void
18735 LoadVariation (int index, char *text)
18736 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18737         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18738         int level = 0, move;
18739
18740         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18741         // first find outermost bracketing variation
18742         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18743             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18744                 if(*p == '{') wait = '}'; else
18745                 if(*p == '[') wait = ']'; else
18746                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18747                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18748             }
18749             if(*p == wait) wait = NULLCHAR; // closing ]} found
18750             p++;
18751         }
18752         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18753         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18754         end[1] = NULLCHAR; // clip off comment beyond variation
18755         ToNrEvent(currentMove-1);
18756         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18757         // kludge: use ParsePV() to append variation to game
18758         move = currentMove;
18759         ParsePV(start, TRUE, TRUE);
18760         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18761         ClearPremoveHighlights();
18762         CommentPopDown();
18763         ToNrEvent(currentMove+1);
18764 }
18765
18766 void
18767 LoadTheme ()
18768 {
18769     char *p, *q, buf[MSG_SIZ];
18770     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18771         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18772         ParseArgsFromString(buf);
18773         ActivateTheme(TRUE); // also redo colors
18774         return;
18775     }
18776     p = nickName;
18777     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18778     {
18779         int len;
18780         q = appData.themeNames;
18781         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18782       if(appData.useBitmaps) {
18783         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18784                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18785                 appData.liteBackTextureMode,
18786                 appData.darkBackTextureMode );
18787       } else {
18788         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18789                 Col2Text(2),   // lightSquareColor
18790                 Col2Text(3) ); // darkSquareColor
18791       }
18792       if(appData.useBorder) {
18793         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18794                 appData.border);
18795       } else {
18796         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18797       }
18798       if(appData.useFont) {
18799         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18800                 appData.renderPiecesWithFont,
18801                 appData.fontToPieceTable,
18802                 Col2Text(9),    // appData.fontBackColorWhite
18803                 Col2Text(10) ); // appData.fontForeColorBlack
18804       } else {
18805         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18806                 appData.pieceDirectory);
18807         if(!appData.pieceDirectory[0])
18808           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18809                 Col2Text(0),   // whitePieceColor
18810                 Col2Text(1) ); // blackPieceColor
18811       }
18812       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18813                 Col2Text(4),   // highlightSquareColor
18814                 Col2Text(5) ); // premoveHighlightColor
18815         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18816         if(insert != q) insert[-1] = NULLCHAR;
18817         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18818         if(q)   free(q);
18819     }
18820     ActivateTheme(FALSE);
18821 }